接上一篇《如何设计一个异步Web服务——接口部分》html
Application已经将任务信息发到了Service服务器中,接下来,Service服务器改如何对自身的资源进行合理分配以知足Application对功能、性能、用户体验等各方面的需求呢?服务器
能够从以下几个方向入手去考虑:微信
如需转载,请注明转自:http://www.cnblogs.com/silenttiger/p/4135461.html多线程
根据上面的要求,咱们会产生以下的设计要求:并发
下面,咱们就根据上述的这些要求开始设计。异步
首先,咱们须要一个http服务器做为接收Application请求的接口。而后,咱们建立一个QueenAnt(蚁后)类来负责任务和资源的调度,同时还须要若干个WorkerAnt(工蚁)类来处理各个具体的task。性能
注意,这里的QueenAnt类是静态的,或者也能够用单例模式建立。前面提到咱们须要使用线程池(或进程池)技术,因此,在QueenAnt类被实例化之后首先就须要把这个线程池建立出来,并建立若干线程放入这个池中。其中,每一个线程都会实例化一个WorkerAnt类来等待QueenAnt发过来的task。这个地方还有一个问题,那就是咱们的线程池中到底建立几个线程最优?这个问题我留到后面说明。ui
当这些准备好了之后,Service就能够等待Application的请求了。spa
当Application向Service发出addTask的请求时,http服务器会将这个请求通知给QueenAnt,并返回QueenAnt返回的taskId。线程
QueenAnt在收到task请求后,除了返回taskId,还须要对这个task的相关信息进行初始化,好比设置task的状态信息,将task添加到任务队列等等。
等这些结束之后,QueenAnt就开始针对已经收到的task进行任务调度和资源分配了。咱们定义一个allocateResource方法来处理相关的逻辑。该方法将会指定threadPool中的哪一个具体线程会来处理这个task。这以后,咱们就能够把task相关的数据发给这个指定的thread进行处理了。而当有task完成时,处理该task的线程中的WorkerAnt就会发送相关信息给QueenAnt,调用QueenAnt的taskEndCallback方法,让QueenAnt从新分配资源。
当WorkerAnt完成某一个task以后,他须要将这个task的相关信息返回给QueenAnt。同时标记本身为空闲状态,以便QueenAnt再进行资源分配。
QueenAnt在收到WorkerAnt关于task完成的消息后,他也须要更新于这个task的相关状态信息,并在此根据threadPool和taskQueue的具体状况从新进行资源分配。
到这里,咱们就经过上图描述的逻辑,知足了设计要求中的第二和第四点要求。那第一和第三点要求呢,就得经过allocateResource这个方法去实现了。
下面咱们详细讲一下allocateResource这个方法的内部逻辑。
这里先声明一下后文的描述方法,咱们把Application发过来的一个任务叫作"task",而把由这个任务拆分出来的许许多多的小任务叫作"子task"。
可能有人会产生疑惑,根据设计要求中的第一点,咱们应该把task拆分为子task。可上面的设计中,咱们放入taskQueue的倒是Application传过来的task,是否是差一个拆分的步骤呢?
其实并非这样,这样的设计是由于开头的考虑方向中的第三点和设计要求中第三点,都要求一个task不能够占用全部的计算资源。这样说可能不太好理解,咱们来举个例子:
首先,Application向Service提交了task01,该task共20个子task,须要Service满负荷运行5分钟才能完成。
到第3分钟的时候,Application又向Service提交了task02,该task共4个子task,须要Service满负荷运行1分钟便可完成。
咱们来分析一下这个场景。若是咱们在将task01加入taskQueue以前,就将其拆分为许多的子task。并把threadPool中的资源依次分给这些子task。那么到第3分钟加入task02的各个子task的时候,因为task01的子task没有完成,task02只好处于等待状态。并且须要等task01的几乎全部子task都完成之后,才能进入处理中的状态,这一等就是10分钟。这显然违背了咱们考虑方向中的第三点和设计要求中第三点。
那么,怎样设计这个allocateResource的逻辑才能既知足设计要求中的第一点,又能知足第三点了?个人思路是这样的。
首先,咱们给task加上两个属性threadRequirement和runningThread。threadRequirement表示,为了完成这个task,若是给其每一个子task分配一个线程,那么一共须要多少个线程,随着子task的完成,这个数值会愈来愈小,最后变为0即表示这个task已经所有完成。runningThread表示,当前有几个线程正在处理这个task的子task。
而后,allocateResource这个方法有两个地方会调用,一是当Service收到新的task请求的时候。二是当某个子task完成,QueenAnt中的taskEndCallback被调用的时候。
allocateResource在给task分配资源的时候,应遵照如下几个准则:
这样说可能有些抽象。咱们仍是来举个上面那个例子,假设threadPool中共4个线程,task01的threadRequirement为20,task02的threadRequirement为2。过程以下:
大概的逻辑就是上面这样了。步骤看起来虽然略显复杂,但其实只有掌握了前面说的4个准则,allocateResource的逻辑仍是很好实现:
至此,关于Service任务调度和资源分配的设计也结束了。
下面,咱们来讲一下前面遗留的一个问题:线程池中到底建立几个线程最优?
为何最后要特别来谈谈这个问题,是由于市面上有一种叫作超线程的CPU虚拟化的技术。好比Intel公司的酷睿i3系列CPU,明明是两个物理核心,在Windows的任务管理中,或在Linux系统的top命令下,显示的倒是4个核心,由于CPU在硬件层面将两个物理核心模拟为4个逻辑核心了。根据咱们上面的设计,天然是但愿threadPool中的线程数量越多越好,但是也不能太多。由于多个线程同时争用一个CPU核心的资源是没有必要的。因此,若是是4核的CPU,咱们通常会起4个线程放入threadPool。但是在这种使用了超线程技术的CPU平台上,若是你把线程数目配置为与CPU逻辑核心数目一致倒是没有必要的。我在i3平台上实测数据以下:
线程数 |
总耗时(s) |
CPU 0 使用率 |
CPU 1 使用率 |
CPU 2 使用率 |
CPU 3 使用率 |
1 |
22.4 |
1 |
0 |
98 |
0 |
2 |
12.6 |
2 |
98 |
0 |
97 |
3 |
11.2 |
78 |
64 |
97 |
4 |
4 |
10.5 |
98 |
99 |
100 |
98 |
我想上面的数据已经很好地说明了问题,虽然是4个逻辑核心,虽然你可让4个线程同时运行,但其实在CPU物理层面,同时运行的指令最多就两个。也就是4个线程中每两个线程去争用一个物理核心的运算资源。
其结果就是,性能上的微小进步却带来了CPU使用率的大幅飙升,反而使得用来做为接口的httpServer响应时间变长。
如需转载,请注明转自:http://www.cnblogs.com/silenttiger/p/4135461.html
欢迎关注个人微信公众号:老虎的小窝