多线程环境下调用dubbo consume rocketmq producer发送数据,为什么没有数据混乱的发生,netty分析

多线程环境下调用dubbo consume rocketmq producer(单例对象)发送数据,为什么没有数据混乱的发生,不知道大家有没有想过这个问题,我是在看的时候想到了,因为以前使用bio写过异步长连接单工,同步长连接,多线程下在发送数据的时候,都要对socket进行加锁,不然接收方接收到数据会解码错误,在看过了dubbo源码和rocketmq源码后,发现发送却不加锁,但是实际接收方也不会出现数据混乱解码失败的问题,这个是什么原因呢?

dubbo和rocketmq都是阿里系产品,使用的通讯都是netty,dubbo consume,rocketmq producer作为发送方都是nettyclient,和服务端维持长连接,以rocketmq producer为例说明(dubbo consume也是一样道理)

具体的发送是执行的io.netty.channel.Channel.writeAndFlush(Object),该方法就是先把待发送数据写入到buffer缓冲区,然后刷新缓冲区把数据通过网卡发送出去,执行io.netty.channel.Channel.writeAndFlush(Object)操作的时候此时线程是netty-client(业务)线程,然后调用TailContext.writeAndFlush()操作,把发送数据包装为WriteAndFlushTask放入到IO线程的NioEventLoop.taskQueue无界阻塞队列(LinkedBlockingQueue),继而这样切换IO线程(IO线程是单线程,一个netty client对应一个IO线程)执行。IO线程(由NioEventLoop.run()循环执行)执行runAllTasks操作从队列取出task即WriteAndFlushTask执行,先把要发送的数据写入到缓冲区ChannelOutboundBuffer(一个socketChannel对应一个ChannelOutboundBuffer),然后再flush刷新把数据发送出去。这样在多线程情况下同时调用一个netty client发送数据的时候,实际上是每次的发送都是包装为WriteAndFlushTask对象在IO线程内串行执行的,因此在看netty代码ChannelOutboundBuffer类的addMessage、addFlush方法虽然是对单例ChannelOutboundBuffer的属性修改了,但是由于是串行执行,因此不需要加锁。 这也就解释了为什么多线程下使用同一个netty client长连接发送数据,实际不需要加锁的原因。但是这样设计可能会想到有个问题,每次发送数据包装为WriteAndFlushTask对象放入IO线程的队列内,当业务线程发送速度过快而io线程处理较慢,那么会导致发送的task在IO线程的队列积压从而导致OOM,当然netty对于这种情况也考虑到了,采用了高低水位机制,可以防止在发送队列处于高水位时继续发送消息导致积压,我们只需要在自己开发的的inboud类型的ChannelHandler实现channelWritabilityChanged(ChannelHandlerContext ctx)方法,把通道channel变为不可写,这样客户端在发送前线判断channel状态为可写后再进行发送,在低水位后再改为可写,这样可以提高系统可靠性。

执行的流程图参考下面

多线程环境下调用dubbo consume rocketmq producer发送数据,为什么没有数据混乱的发生,netty分析_第1张图片

 

等后续有时间了,我会把析的netty源码总结下分享出来。

你可能感兴趣的:(dubbo)