前面已经总结了Java基础IO, IO模型, 编解码等内容, 理论上我们把三者结合起来就可以完成消息收发。接下来, 结合Netty pipeline聊聊网络应用中对三者逻辑组织, 解决实际工程中链接创建, IO处理和消息处理。
首先, 逻辑上我们需要这么一种结构, 将基础IO, IO模型, 编解码组织为一个特定的序列;
其次, 此外实际应用在运维阶段需要支持日志输出, 消息统计, 消息审计等功能, 因此这个序列可以动态增加或者减少序列项;
最后, 部分序列项在客户端和服务端可以复用, 但是序列的顺序不同, 因此序列应该支持定制;
玩过设计模式的你肯定想到了责任链模式, 整过Java Servlet开发的估计想到了FilterChain, 搞过大数据的估计想到了数据处理中的Pipeline。以上如果都没玩过, 那你至少玩过Java 8的Lambda和Stream, 最后在Netty中叫pipeline。
以上过程完成了业务需要到技术需求的转化, 有了具体的技术需求, 你就可以挑选合理的技术架构。此处我不想强调架构的思想, 但是对有志于在技术领域继续前行的小伙伴, 成功技术需求转化是成功架构的一半, 因为你已经将问题讲清楚了。
与FilterChain由Filter组装而成, Netty pipeline是由ChannelHandlerContext组成, 而且两者都有个类似的地方, 那就是顺序很关键且可以条件控制。比如Filter是通过url-pattern是否匹配决定是否通过Filter, 而ChannelHandler则根据数流向(ChannelInbound和ChannelOutbound)来处理。
FilterChain示意图
有了这么个骨架,接下来得用pipeline的方式来划分逻辑, 这些逻辑又分为连接处理和消息处理两类, 接下来分别讨论。
想必你已知道Netty是基于Reactor模型的, 其中会有reactor线程和IO线程, 前者探测ready的channel, 后者去具体的channel上完成读写。但是Channel是怎么创建的呢?显然在探测channel ready之前, 首先需要创建Channel, 该过程对应咱们的连接创建。第一步是监听channel的创建, 这个是咱们代码明确声明创建的。其次, 当有新的连接接入时, 需要调用监听channel的accept来接收, 此时会创建对应的客户端连接channel。如果使用bossGroup+workerGroup的方式, bossGroup中会创建一个EventLoop作为reactor线程来处理监听channel中的ACCEPT事件, 而后创建对应的客户端连接channel, 最终注册到workerGroup的某个EventLoop中, 该EventLoop作为reactor线程获取ready的channel并读取数据。
基本的步骤如下:
消息处理至少包含消息编解码和消息的业务处理。由于消息编解码的通用性, 因此消息编解码一般独立为pipeline中的一环, 进而做到了消息编解码和消息本身的解耦。
Pipeline是由抽象的item组成, item统一了框架和业务逻辑对象, 使得业务逻辑也可以包装为item加入pipeline当中,从而完成框架逻辑和业务逻辑的衔接。在网络应用场景中, 我们既要考虑逻辑上下文(也就是现在应该执行什么逻辑), 也要考虑运行上下文(也就是当前的逻辑应该在哪个线程下运行), 毕竟消息的读取是典型的IO过程, 消息处理则可能是典型的计算过程, 两者特性不同, 分而治之是比较合理的。接下来我们聊聊Netty的实现异同。
首先, Netty也使用的是Pipeline来组织整个处理过程。
其次, Netty的Pipeline item并不是ChannelHandler, 而是ChannelHandlerContext。在Context中包含了执行上下文EventLoop和逻辑上下文prevContext和nextContext的定义。
最后, Netty将ChannelHandler细化为ChannelInBoundHandler, ChannelOutBoundHandler和ChannelDuplexHandler三种类型。按照数据的流向, 寻找下一个支持的handler进行处理。这个是在使用中需要建立的全局观。
本文我们探讨了网路应用中开发中常用的主干结构pipeline, 内容涵盖结构特点, 抽象pipeline item的意义, 以及网络应用场景下逻辑和运行上下文对item设计时的影响。最后, 本文简要介绍了Netty pipeline的具体实现。