这是什么鬼?饭店,mina? 纳尼?
一个是餐饮业的范畴,一个是java的nio网络通信框架,他们会有什么联系?
没错,虽然他们看似毫无关系,但是,在运作机制上却异曲同工,下面我们来看看小M的饭店,看他的饭店和mina的线程协作有什么相似之处。
P.S. 没用过mina的同学可以先看看
mina介绍。
小M的饭店
小M他开了家饭店,饭店虽小,但也五脏俱全,迎宾,服务员,厨师,一个都不少,而且还上了一套电子系统,通过这个系统,客人可以无纸化点菜,可以通过系统随时呼唤服务员,服务员也可以通过这个系统方便地给后台厨房下单,系统的排队机制能使服务员在忙碌的情况下,也不会忽略掉客人的请求。
小M深信,通过高效的团队运作和先进的设备能让客人有更优质的用餐体验。
通过系统分配好餐桌,并且开好点菜单,转交给指定餐桌服务员。
如果客人太多,暂时没有空闲的餐桌,则客人在门口排队等候。
大堂接客
餐桌服务员看到迎宾员把客人带到自己负责的餐桌后,然后开始进行布置餐桌的一系列标准流程(收拾餐桌,上餐具,泡茶等等)
餐桌服务员开始留意系统,如果有客人呼唤就随时响应。
点菜
客人在平板电脑上把点菜单的菜名勾选好后,点击呼唤铃叫餐桌服务员。
餐桌服务员看到后,就过来准备下单。
下单
点菜后,会经过下面流程:
1. 口味备注,服务员会和客人确认每道菜的特殊口味(如不放香菜)
2. 和客人确认菜是否正确和齐全
3. 通过下单系统,通知厨房开始做饭
做饭
收到下单请求后,一班厨师就热火朝天地做起饭来。
做完一道菜,厨师就把菜拿出到大堂的等候上菜区(按餐桌区分开),在此之前,厨师们还会根据之前客人的口味备注等特殊要求进行最后处理,之后等待餐桌服务员来取。
P.S. 饭菜可以安排先后顺序来上
上菜
餐桌服务员取好菜后,会到对应的餐桌上给客人上菜。
上菜时,如果发现此时这个餐桌上菜太频繁,会先把菜返回厨房等下次再上。毕竟在服务能力有限的情况下也需要让每桌客人都能得到及时的用户体验。
结帐
客人点击平板上的呼唤铃来呼唤服务员
服务员收到后,过来给客人走登记结帐
登记结帐后,服务员可能还要继续上菜(或者不上菜直接结帐)
付款后,饭馆就知道该餐桌已空余出来了,服务员又开始清理餐桌,准备迎接下一位客人。
如果客人太多,暂时没有空闲的餐桌,则客人在门口排队等候。
大堂接客
餐桌服务员看到迎宾员把客人带到自己负责的餐桌后,然后开始进行布置餐桌的一系列标准流程(收拾餐桌,上餐具,泡茶等等)
餐桌服务员开始留意系统,如果有客人呼唤就随时响应。
点菜
客人在平板电脑上把点菜单的菜名勾选好后,点击呼唤铃叫餐桌服务员。
餐桌服务员看到后,就过来准备下单。
下单
点菜后,会经过下面流程:
1. 口味备注,服务员会和客人确认每道菜的特殊口味(如不放香菜)
2. 和客人确认菜是否正确和齐全
3. 通过下单系统,通知厨房开始做饭
做饭
收到下单请求后,一班厨师就热火朝天地做起饭来。
做完一道菜,厨师就把菜拿出到大堂的等候上菜区(按餐桌区分开),在此之前,厨师们还会根据之前客人的口味备注等特殊要求进行最后处理,之后等待餐桌服务员来取。
P.S. 饭菜可以安排先后顺序来上
上菜
餐桌服务员取好菜后,会到对应的餐桌上给客人上菜。
上菜时,如果发现此时这个餐桌上菜太频繁,会先把菜返回厨房等下次再上。毕竟在服务能力有限的情况下也需要让每桌客人都能得到及时的用户体验。
结帐
客人点击平板上的呼唤铃来呼唤服务员
服务员收到后,过来给客人走登记结帐
登记结帐后,服务员可能还要继续上菜(或者不上菜直接结帐)
付款后,饭馆就知道该餐桌已空余出来了,服务员又开始清理餐桌,准备迎接下一位客人。
从小M的饭店运作可以看出,排队通知机制+分工明确+高效信息传递是他饭店赢得客户良好口碑的关键。其实,软件开发领域中的
reactor模式中的事件驱动机制就和饭店通知机制就非常类似,而java的nio通信框架mina,则是很好的体现了reator模式。
Mina的线程协作
下面我们把饭店的每个运作流程,结合mina服务端(2.0.7 nio socket)来看下。
饭店开门——IoAcceptor.bind()
通过registerQueue队列,主线程向IoAcceptor发送注册监听端口消息。主线程唤醒Acceptor Worker。(这里主线程就是小M,Acceptor Worker就是迎宾员。)
前台迎宾——acceptor worker工作
Acceptor Worker线程开始周期运行,注册ACCEPT事件,开始监听端口(如同迎宾员关注欢迎感应器)。
一旦被ACCEPT事件触发,worker从select中恢复,然后accept获取channel,构造IoSession。如果连接上来的客户端太多,可能会被拒绝掉(backlog默认50,如同饭店门口有50个等候位置)
通过newSessionsQueue,acceptor worker把session传递给processor,并唤醒Processor Worker。(客人交给餐桌服务员)
一旦被ACCEPT事件触发,worker从select中恢复,然后accept获取channel,构造IoSession。如果连接上来的客户端太多,可能会被拒绝掉(backlog默认50,如同饭店门口有50个等候位置)
通过newSessionsQueue,acceptor worker把session传递给processor,并唤醒Processor Worker。(客人交给餐桌服务员)
大堂接客——processor worker工作
P
rocessor Worker线程初始化从acceptor中获取的IoSession(这里会触发IoFilter中的fireSessionCreated,fireSessionOpened事件),重新注册READ事件,并把事件与IoSession关联。(如同服务员收拾餐桌,并关注饭店点单系统)
一旦READ事件触发,processor worker线程从select中恢复,然后进入process处理流程。(饭店点餐系统提示餐桌服务员客人有请求)
点菜——client发起请求
client发起请求后,
触发服务端READ事件,从而使得processor worker从select方法中唤醒。
开始执行process流程中的read方法。
客户端请求数据会被IoBuffer封装,在服务端也以IoBuffer形式处理。(IoBuffer即点菜单)
开始执行process流程中的read方法。
客户端请求数据会被IoBuffer封装,在服务端也以IoBuffer形式处理。(IoBuffer即点菜单)
下单——IoFilter执行
客户端写请求到达服务端后,服务端processor worker线程开始执行IoFilterChain。 (客人口味备注)
开发者可以用多种IoFilter(自己提供或使用mina自带)在IoFilterChain里串联执行。
如负责编解码的ProtocolCodecFilter,打日志的LoggingFilter,保活的KeepAliveFilter。
值得注意的是ExecutorFilter,这个mina自带的filter可以让后续filter的执行能放到线程池里并行执行。这样后面的处理就能突破processor线程数目。 (请一波厨师做饭,而不是一个)
IoFilterChain最末尾的链条,将会调用IoHandler,进行具体业务处理。
开发者可以用多种IoFilter(自己提供或使用mina自带)在IoFilterChain里串联执行。
如负责编解码的ProtocolCodecFilter,打日志的LoggingFilter,保活的KeepAliveFilter。
值得注意的是ExecutorFilter,这个mina自带的filter可以让后续filter的执行能放到线程池里并行执行。这样后面的处理就能突破processor线程数目。 (请一波厨师做饭,而不是一个)
IoFilterChain最末尾的链条,将会调用IoHandler,进行具体业务处理。
做饭——业务处理
业务最终由IoHandler.messageReceived()方法处理。如果没有使用ExecutorFilter,messageReceived会在processor worker中执行,否则会在ExecutorFilter提供的线程池中执行。用ExecutorFilter,最大的好处是在业务处理有外部io操作时可以不阻塞processor,并且可以使得业务并行处理。
完成业务处理后,可能需要返回数据给客户端,这时候就会通过session.write()来写数据。同样,和从客户端读数据类似 ,写数据也要经过IoFilterChain处理(注意,此时执行IoFilter的是业务线程)。但业务线程并不会直接向客户端写数据,如果前面还有数据没写完,会先写入到session内一个WriteRequestQueue中。通过WriteRequestQueue,写请求将委托给processor的flush方法来完成对客户端的写数据。
P.S. ExecutorFilter提供2种线程池,有序和无序。有序线程池可以保证在一个session内发生的io事件能按顺序处理。通常会把ExecutorFilter放到IoFilterChain的最末端。(按序上菜)
完成业务处理后,可能需要返回数据给客户端,这时候就会通过session.write()来写数据。同样,和从客户端读数据类似 ,写数据也要经过IoFilterChain处理(注意,此时执行IoFilter的是业务线程)。但业务线程并不会直接向客户端写数据,如果前面还有数据没写完,会先写入到session内一个WriteRequestQueue中。通过WriteRequestQueue,写请求将委托给processor的flush方法来完成对客户端的写数据。
P.S. ExecutorFilter提供2种线程池,有序和无序。有序线程池可以保证在一个session内发生的io事件能按顺序处理。通常会把ExecutorFilter放到IoFilterChain的最末端。(按序上菜)
上菜——session回写数据
如果有数据回写客户端,则会注册WRITE事件,进入flush流程。
processor worker响应WRITE事件,后续把session中写请求的数据发送到客户端。(厨师通知服务员上菜)
总体上,mina先把要写数据的session放入队列,然后逐个session获取写任务列表,再逐条任务去执行数据回写。
在写数据时候,会设置每个session在一次flush中最大能写多少byte数据。如果超过这个最大值(或者内核写队列写满)就会暂停写数据,留到下次再写。目的是为了均衡该processor的读写比例。(避免在1桌客人消耗太长时间)
总体上,mina先把要写数据的session放入队列,然后逐个session获取写任务列表,再逐条任务去执行数据回写。
在写数据时候,会设置每个session在一次flush中最大能写多少byte数据。如果超过这个最大值(或者内核写队列写满)就会暂停写数据,留到下次再写。目的是为了均衡该processor的读写比例。(避免在1桌客人消耗太长时间)
结帐——session关闭
客户端主动close session
服务端收到READ事件,但是读到数据-1,表明数据已结束。(客人点击平板呼唤服务员)
processor worker不会立刻关闭session,而是会将session放入removeSessions队列,等待removeSession流程进行session清除。
在关闭session前,会尝试把未完成数据回写请求的数据继续写回客户端,如果完成后还有数据未写,则会丢弃并且报WriteToClosedSessionException错误。
session关闭会关闭session相关底层资源,如SocketChannel。
最后还会触发IoFilterChain的fireSessionClosed事件。(收拾饭桌)
在关闭session前,会尝试把未完成数据回写请求的数据继续写回客户端,如果完成后还有数据未写,则会丢弃并且报WriteToClosedSessionException错误。
session关闭会关闭session相关底层资源,如SocketChannel。
最后还会触发IoFilterChain的fireSessionClosed事件。(收拾饭桌)
通过下图可以看到客户端发起连接和请求时,acceptor worker,processor worker,bizWorker线程间如何交互:
注:同一线程内的箭头颜色相同。IoFilterChain中箭头存在2种不同颜色是因为本例在messageReceived事件使用了ExecutorFilter,使得业务处理在bizWorker线程中执行。
mina的线程模型特点:
1.Reactor模式
Reactor模式,就是程序通过事件驱动,把事件分离并分发请求到各个不同处理者中。Reactor模式有Reactor,Event,Handler,Demultiplexer(事件分离器)。
Reactor 负责响应IO事件,一旦发生,Demultiplexer把事件分离,广播发送给相应的Handler去处理。Handler负责非堵塞行为,同时负责将handlers与event事件绑定。
饭店中,欢迎感应器是reactor,迎宾员是handler;点单系统是reactor,服务员是handler。
mina中,java nio的selector是reactor,acceptor和processor是handler。bizWorker是为了避免processor堵塞而单独设立一个线程池而存在。
2.线程分工明确
acceptor负责新连接的io事件,processor负责连接的读写io事件,通过executorFilter可以把业务处理分派给线程池处理
通过不同的分工,使得使用者按使用场景合理的分配计算资源,实现系统高效运行。
3.线程间通过队列实现数据传递
从上图可以看到,acceptor和processor,processor和业务线程间的数据传递都用了队列。甚至同一线程的不同处理流程中,也使用了队列来进行数据传递。
最后
本文最初目的只是为了借用饭店的故事来加深对mina设计和reactor模式的认识。事物都是相通的,不同领域的事物也有相似之处,甚至相互影响。以后有新的程序设计架构的思路,说不定也能提高传统行业的运营效率。用互联网思路改变传统行业也是如此。
参考:
- Scalable IO in Javahttp://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
- Pattern Oriented Software Architecture (Vol.2)
- mina 2.0.7