@jewes
2015-01-04T22:08:00.000000Z
字数 1739
阅读 2850
运营一个餐厅和构建一个高性能的服务器有很多相似的地方,我们一起来看看吧。
小明的餐厅刚起步,规模很小,就只有小明一个人在忙活。当客人到来的时候,他得首先去招呼客人,等客人下单,然后去后厨炒菜,然后再把饭菜端给客人。如果在这个过程中有新的客人到来,他是没有功夫去招呼新客人,新客人就只能在那里等候,或者等不及了就离开了。这就类似于服务器在一个线程里面等待客户端的连接请求并在同一个线程中读取数据、处理请求和返回数据,在这种模式下客户端在一个时间点只能处理一个客户的请求。当然,餐厅老板不会采用这种模式,也没有服务器是这样设计的。
小明经过一段时间的努力,掌握了两个新本领:1)多任务处理能力,能在给一个客人点单或者炒菜的同时能招呼新来的客人;2)批处理能力,如果几位客人到达的时间差不多,先等他们点好单后再一起做。这样运作的餐厅,当客人多的时候,小明忙得不可开交,响应速度也变慢了。类似的服务器程序可以用java NIO中的Selector来监听多个通道上的socket请求,并且把客户端请求放入队列中以便批处理。同样的,当客户端请求一多,响应速度会变慢。
餐厅在小明的打理下,规模慢慢壮大,又招聘了几个服务员,其中一个服务员(前台)专门招呼客人,其他几个服务员负责点单和炒菜。当新客人来的时候,前台随机找一个空闲的服务员,让其专门为这位客人服务(点单和炒菜)。这样,餐厅终于能够同时服务多位客人了。类似的服务器程序就是一个主线程负责接受(accept)客户端请求,多个工作线程负责处理业务,并把结果返回给客户端。这种模式的缺点是:1)系统的利用率不高,因为工作线程的很多时间可能阻塞在网络IO上;2)系统的并发量上不去,因为这受制于系统能开启的线程数量。
在招聘了很多服务员以后,小明发明了一种全新的餐厅运作方式。让一个服务员(前台)在餐厅门口接待客户,让N个服务员(点菜员)负责点菜,然后让M个服务员在后厨专门炒菜。新的接待客人的流程为:
1. 当新客人到达餐厅时,前台随机找一个点菜员,让其接待该客人,这样前台的工作很轻量,能保证其响应客户的速度;
2. 点菜员接到新客人的时候,首先将菜单给客人,让其先看一下。这时他就可以处理别的客户的请求了。同时,他会随时注意分配给他的客人,看看客人是否可以点菜了。
3. 当客人示意点菜员可以点菜后,点菜员则去处理客人的点菜请求,把客人点的菜写在一张便签纸上,并贴到后厨的小黑板。
4. 后厨的厨师一旦看到小黑板上有新的便签纸,便立刻取下便签纸,然后去按照客人的要求去制作菜肴。
类似的服务器程序就是Reactor模式,其中包括了如下角色的线程:Acceptor,Processor和Handler,它们在系统中的数量和职责是:
1. 一个Acceptor(类似于餐厅的前台),专门监听客户端的请求,它自身并不处理客户端的请求,它只是accept客户端的连接请求,然后分配一个Processor给这个客户端。
2. N个Processor(类似于餐厅的点菜员),它处理网络读写请求,本身并不处理具体的业务,它用Selector来监听分配给它的客户端的读写请求,一旦某个客户端变成可读(类似于餐厅中的客人可以点菜了),它就去读取客户端发送来的数据并封装成Request对象,并添加到一个队列(类似于餐厅后厨的小黑板)中。
3. M个Handler,它是处理具体业务的线程(类似于餐厅中的厨师),它从队列中取出Request对象并进行处理。
当Handler处理完一个Request后,返回结果返回给客户端的时候有两种选择,一是交给读取该客户端数据的Processor,让其返回给客户端(类似于餐厅中点菜员负责给客户上菜);另外一种是交给一组专门处理返回结果的线程(类似于餐厅中有专门的上菜员来给客户上菜)。
这个模式的优点是职责明确,Acceptor和Processor都采用了非阻塞IO和Selector,从而一个线程就可以处理多个客户端的读写请求,效率非常高。这种模式在很多餐厅和分布式系统中(比如Kafka和Hadoop)都有使用,也是目前比较主流一种设计。
(全文完)