Netty长连接的事件处理顺序问题

getPipeline参考:





最近的一个线上项目(认证服务器)老是出现服务延迟的情况。具体的问题描述:
(1)客户端发送一个请求A(长连接),在服务器端的业务层需要20秒以上才能接收到。
(2)客户端发送一个请求B(端连接),在服务器端的业务层可以迅速接收到。

     从现象大致知道问题出在服务器端的网络接收层,大量通过长连接发送过来的请求都堵塞在网络层得不到处理(在网络层排队,还没到应用层)
(友情提示:本博文章欢迎转载,但请注明出处:hankchen,http://www.blogjava.net/hankchen)

     后来经过排查,发现是Netty中的OrderedMemoryAwareThreadPoolExecutor原因。
相关代码如下:
MemoryAwareThreadPoolExecutor executor = new OrderedMemoryAwareThreadPoolExecutor(
          threadNums,
          maxChannelMemorySize,
          maxTotalMemorySize, keepAliveTime,
          TimeUnit.SECONDS);

ExecutionHandler executionHandler = new ExecutionHandler(executor);
public ChannelPipeline getPipeline() throws Exception
{
        ChannelPipeline pipeline = pipeline();
        pipeline.addLast("decoder", new AuthDecoder());
        pipeline.addLast("encoder", new AuthEncoder());
        pipeline.addLast("executor", executionHandler);
        pipeline.addLast("handler", new AuthServerHandler(commandFactory));
        return pipeline;
}

先介绍下背景知识,再来分析问题。
     大家都知道,Netty是一个基于事件的NIO框架。在Netty中,一切网络动作都是通过事件来传播并处理的,例如:Channel读、Channel写等等。
     回忆下Netty的流处理模型:
     Boss线程(一个服务器端口对于一个)--->接收到客户端连接--->生成Channel--->交给Work线程池(多个Work线程)来处理
     具体的Work线程--->读完已接收的数据到ChannelBuffer--->触发ChannelPipeline中的ChannelHandler链来处理业务逻辑。
     注意:执行ChannelHandler链的整个过程是同步的,如果业务逻辑的耗时较长,会将导致Work线程长时间被占用得不到释放,从而影响了整个服务器的并发处理能力。

     所以,为了提高并发数,一般通过ExecutionHandler线程池来异步处理ChannelHandler链(worker线程在经过ExecutionHandler后就结束了,它会被ChannelFactory的worker线程池所回收)。在Netty中,只需要增加一行代码:
public ChannelPipeline getPipeline() {
         return Channels.pipeline(
                 new DatabaseGatewayProtocolEncoder(),
                 new DatabaseGatewayProtocolDecoder(),
                 executionHandler, // Must be shared
                 new DatabaseQueryingHandler());
}
例如:
ExecutionHandler executionHandler = new ExecutionHandler(
             new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576))

对于ExecutionHandler需要的线程池模型,Netty提供了两种可选:
     1) MemoryAwareThreadPoolExecutor 通过对线程池内存的使用控制,可控制Executor中待处理任务的上限(超过上限时,后续进来的任务将被阻塞),并可控制单个Channel待处理任务的上限,防止内存溢出错误;
     2) OrderedMemoryAwareThreadPoolExecutor 是 MemoryAwareThreadPoolExecutor 的子类。除了MemoryAwareThreadPoolExecutor 的功能之外,它还可以保证同一Channel中处理的事件流的顺序性,这主要是控制事件在异步处理模式下可能出现的错误的事件顺序,但它并不保证同一Channel中的事件都在一个线程中执行(通常也没必要)。
例如:
Thread X: --- Channel A (Event A1) --.   .-- Channel B (Event B2) --- Channel B (Event B3) --->
                                      \ /
                                       X
                                      / \
Thread Y: --- Channel B (Event B1) --'   '-- Channel A (Event A2) --- Channel A (Event A3) --->

上图表达的意思有几个:
(1)对整个线程池而言,处理同一个Channel的事件,必须是按照顺序来处理的。例如,必须先处理完Channel A (Event A1) ,再处理Channel A (Event A2)、Channel A (Event A3)
(2)同一个Channel的多个事件,会分布到线程池的多个线程中去处理
(3)不同Channel的事件可以同时处理(分担到多个线程),互不影响。  

     OrderedMemoryAwareThreadPoolExecutor 的这种事件处理有序性是有意义的,因为通常情况下,请求发送端希望服务器能够按照顺序处理自己的请求,特别是需要多次握手的应用层协议。例如:XMPP协议。

 现在回到具体业务上来:
     我们这里的认证服务也使用了OrderedMemoryAwareThreadPoolExecutor。认证服务的其中一个环节是使用长连接,不断处理来自另外一个服务器的认证请求。通信的数据包都很小,一般都是200个字节以内。一般情况下,处理这个过程很快,所以没有什么问题。但是,由于认证服务需要调用第三方的接口,如果第三方接口出现延迟,将导致这个过程变慢。一旦一个事件处理不完,由于要保持事件处理的有序性,其他事件就全部堵塞了!而短连接之所以没有问题,是因为短连接一个Channel就一个请求数据包,处理完Channel就关闭了,根本不存在顺序的问题,所以在业务层可以迅速收到请求,只是由于同样的原因(第三方接口),处理时间会比较长。
     其实,认证过程都是独立的请求数据包(单个帐号),每个请求数据包之间是没有任何关系的,保持这样的顺序没有意义!

最后的改进措施:
1、去掉OrderedMemoryAwareThreadPoolExecutor,改用MemoryAwareThreadPoolExecutor。
2、减少调用第三方接口的超时时间,让处理线程尽早回归线程池。


######################################

转自: http://blog.csdn.net/thomescai(转载请保留)

Pipeline流处理的一个实例,证明了Pipeline流的执行顺序。

ChannelPipeline原理图:



    Upstream接收请求。Downstream发送请求。

代码如下:

[html]  view plain copy
  1. public class ServerTest {  
  2.     public static void main(String args[]) {  
  3.         ServerBootstrap bootsrap = new ServerBootstrap(  
  4.                 new NioServerSocketChannelFactory(Executors  
  5.                         .newCachedThreadPool(), Executors.newCachedThreadPool()));  
  6.   
  7.         bootsrap.setPipelineFactory(new PipelineFactoryTest());  
  8.   
  9.         bootsrap.bind(new InetSocketAddress(8888));  
  10.     }  
  11. }  

[html]  view plain copy
  1. public class PipelineFactoryTest implements ChannelPipelineFactory {  
  2.   
  3.     @Override  
  4.     public ChannelPipeline getPipeline() throws Exception {  
  5.         ChannelPipeline pipeline = Channels.pipeline();  
  6.         pipeline.addLast("1", new UpstreamHandlerA());  
  7.         pipeline.addLast("2", new UpstreamHandlerB());  
  8.         pipeline.addLast("3", new DownstreamHandlerA());  
  9.         pipeline.addLast("4", new DownstreamHandlerB());  
  10.         pipeline.addLast("5", new UpstreamHandlerX());  
  11.         return pipeline;  
  12.     }  
  13.   
  14. }  

[html]  view plain copy
  1. @ChannelPipelineCoverage("all")  
  2. public class UpstreamHandlerA extends SimpleChannelUpstreamHandler {  
  3.     @Override  
  4.     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)  
  5.             throws Exception {  
  6.         System.out  
  7.                 .println("UpstreamHandlerA.messageReceived:" + e.getMessage());  
  8.         super.messageReceived(ctx, e);  
  9.     }  
  10.   
  11.     @Override  
  12.     public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {  
  13.         System.out.println("UpstreamHandlerA.exceptionCaught:" + e.toString());  
  14.         e.getChannel().close();  
  15.     }  
  16. }  

[html]  view plain copy
  1. public class UpstreamHandlerB extends SimpleChannelUpstreamHandler {  
  2.     @Override  
  3.     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)  
  4.             throws Exception {  
  5.         System.out  
  6.                 .println("UpstreamHandlerB.messageReceived:" + e.getMessage());  
  7.         super.messageReceived(ctx, e);  
  8.     }  
  9.   
  10.     @Override  
  11.     public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {  
  12.         System.out.println("UpstreamHandlerB.exceptionCaught:" + e.toString());  
  13.         e.getChannel().close();  
  14.     }  
  15. }  

[html]  view plain copy
  1. public class UpstreamHandlerX extends SimpleChannelUpstreamHandler {  
  2.     @Override  
  3.     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)  
  4.             throws Exception {  
  5.         System.out.println("UpstreamHandlerX.messageReceived");  
  6.         e.getChannel().write(e.getMessage());// (2)  
  7.     }  
  8.   
  9.     @Override  
  10.     public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {  
  11.         System.out.println("UpstreamHandlerX.exceptionCaught");  
  12.         e.getChannel().close();  
  13.     }  
  14. }  

[html]  view plain copy
  1. public class DownstreamHandlerA extends SimpleChannelDownstreamHandler {  
  2.     public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)  
  3.             throws Exception {  
  4.         System.out.println("DownstreamHandlerA.handleDownstream");  
  5.         super.handleDownstream(ctx, e);  
  6.     }  
  7. }  

[html]  view plain copy
  1. public class DownstreamHandlerB extends SimpleChannelDownstreamHandler {  
  2.     public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)  
  3.             throws Exception {  
  4.         System.out.println("DownstreamHandlerB.handleDownstream");  
  5.         super.handleDownstream(ctx, e);  
  6.     }  
  7. }  

运行结果:

UpstreamHandlerA.messageReceived:BigEndianHeapChannelBuffer(ridx=0, widx=386, cap=386)
UpstreamHandlerB.messageReceived:BigEndianHeapChannelBuffer(ridx=0, widx=386, cap=386)
UpstreamHandlerX.messageReceived
DownstreamHandlerB.handleDownstream
DownstreamHandlerA.handleDownstream

Upstream: 1 ->2 ->5 顺序处理
Downstream: 4 ->3  逆序处理



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值