前言
即使这是一本小册,但基于“不提笔不读书”的理念,仍然有必要总结一下。此小册对于那些“硬杠 Netty 源码 却不曾在千万级生产环境上使用实操”的用户非常有用。当然,对那些没有 Netty 编程经验的人来说,更为有用。
放个小册地址:[
再次强烈推荐,一碗黄焖鸡/半杯 Luckin coffee/一包炫赫门 的价钱,可以让你学会使用 Java 界的 epoll 进行多路复用网络编程,不能说是不划算的 :)
本文标题含有“骚操作”,为什么这么说呢?
作者是某团某评基础架构部技术专家,长期负责后台千万级别的推送系统,而这些推送系统自然是长连接实现的。可以想象,作者的这些实践经验不可谓不好用,纵然看过源码,提过 issue,本人也觉得这些操作非常好用,非常骚气。
开始
我们挑重点讲,虽然对于强迫症来讲,每一节都有笔记才是最吼的!
1 服务端启动流程
1. 通过给 bind 方法添加监听器,用以自动绑定递增端口。算骚操作吧?2. attr 方法,为每条连接增加属性,能够实现全单例模式哟3. childOption 方法,关于 TCP连接的优化,SO_KEEPALIVE 底层心跳,TCP_NODELAY 延迟发送,SO_BACKLOG 等待队列复制代码
2 客户端启动流程
1. 还是通过监听器实现重试,但是是 connect 返回的 future,且重实间隔时间左移 1 位增加(性能优化,不使用乘二 ,优秀)。2. 重试不在主线程,而是使用 bootstrap.config().group().schedule 搞定时任务,和我想的不一样。优秀3. 客户端需要 CONNECT_TIMEOUT_MILLIS 属性复制代码
3 客户端与服务端双向通信
1.客户端在 channelActive 立刻搞事情,嗯,rpc 通信通常也会做一些处理,例如打印客户端ip之类的。复制代码
4 客户端与服务端通信协议编解码(扩展较多)
emm,这个其实就是自定义应用层协议。1. 4 字节魔数校验,例如 dubbo 就使用0xdabb进行校验,Java 字节码也使用 0xcafebabe 校验字节码。2. 版本号肯定需要的3. 序列化算法,肯定也需要的4. 指令,肯定也是需要的,不过,也可以使用别的方式。5. 后面的数据长度,也是需要的,方便拆包。其实这里可以参照 RPC 协议来看,这里更像一个简化的 RPC 协议。一般 RPC 框架首先获取协议类型,根据这个协议类型得到协议处理器,然后再处理(一个端口处理多个协议的场景)。生产级别的 RPC 通常较为复杂,以 SOFABolt 为例,需要以下字段:1. 协议版本2. 请求类型,即指令(Request,Response, oneway)3. 指令版本4. RequestID 负责数据对应5. 序列号器6. 协议开关(例如 CRC 校验,安全校验)7. 响应码,约定异常,简化异常8. 类名长度,Java rpc 框架必备9. 请求头长度(参照 http header)10. 请求体长度(参照http body)11. 类名12. 业务请求头内容(一般是 Map,SOFABolt 支持自定义,SOFARPC 里面藏着是否泛化调用等信息)13. 业务请求体内容(一般就是个Request对象或 Response对象,里面包含约定的属性,例如参数,返回值,超级多,SOFARPC 有个属性类 RemotingConstants,这里都有)14. CRC 校验码(金融场景必备)复制代码
5 实现客户端与服务端收发消息
1. 使用 hannel.attr(Attributes.LOGIN).set(true) 绑定登录标识方便哟复制代码
6 构建客户端与服务端 pipeline
1. 常用的 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 但是需要强转哦,麻烦, 建议使用 SimpleChannelInboundHandler (还帮你释放内存哟)。2. 不使用 MessageToByteEncoder ,可以自己编解码哦,虽然麻烦点复制代码
7 拆包粘包理论与解决方案
1. 常用拆包:固定长度,行拆包(有bug,我写过文章分析),分隔符,基于长度2. 最通用的就是基于长度,只要你的自定义协议中包含长度域字段,就可以使用3. LengthFieldBasedFrameDecoder 代替自己继承 ByteToMessageDecoder 哟。4. 对 LengthFieldBasedFrameDecoder 扩展一下,校验魔数关闭错误连接美滋滋。复制代码
8 channelHandler 的生命周期
1. 在 channelReadComplete 方法里执行 flush,批量刷新,性能提升美滋滋。2. channelActive 和 channelInActive 增减连接,RPC 都这么干复制代码
9 使用 channelHandler 的热插拔实现客户端身份校验
1. ctx.pipeline().remove(this) 删除没有必要的 handler 美滋滋2. handlerRemoved 回调美滋滋复制代码
10 客户端互聊原理与实现
1. Session 通过 channel.attr(Attributes.SESSION).set(session) 绑定连接美滋滋。2. channel.attr(Attributes.SESSION).set(null) 删除 session 美滋滋3. channel.attr(Attributes.SESSION).get() 美滋滋复制代码
11 群聊的发起与通知
1. ChannelGroup c = ChannelGroup channelGroup = new DefaultChannelGroup(ctx.executor()) 批量处理连接美滋滋2. channelGroup.writeAndFlush 批量写连接美滋滋复制代码
高能预警!!!!
12 牛逼的性能优化
1. 共享 handler2. 压缩 handler - 合并编解码器 —— MessageToMessageCodec3. 虽然有状态的 handler 不能搞单例,但是你可以绑定到 channel 属性上,强行单例4. 缩短事件传播路径—— 放 Map 里,在第一个 handler 里根据指令来找具体 handler。5. 更改事件传播源—— 用 ctx.writeAndFlush() 不要用 ctx.channel().writeAndFlush()6. 减少阻塞主线程的操作—— 使用业务线程池,RPC 优化重点7. 计算耗时,使用回调 Future复制代码
13 心跳和空闲检测
1. 空闲检测 IdleStateHandler 用起来很爽, channelIdle 和 userEventTriggered 都可以处理2. 定时心跳 ctx.executor().scheduleAtFixedRate 很优秀3. 通常空闲检测时间要比发送心跳的时间的两倍要长一些(3倍),这也是为了排除偶发的公网抖动,防止误判。美滋滋复制代码
总结
小小短文,无法尽数 Netty 精华,但对于新手来说,已经足够使用了。而我这里仅仅是做简单的阅读总结,更多的内容,还需要读者自己去研究小册,研究源码,研究 Netty 在 RPC 里的运用,方能成为 Netty 多路复用网络编程高手。
关于 RPC 里使用 Netty 的最佳范例,推荐蚂蚁金服开源框架 ,可以说是对 Netty 编程最佳实践的提炼,和此文相辅相成进行学习,可助汝纵横 Java 各种面试。
good luck!!!