概述
本文介绍如何玩转 Netty.
一、服务端启动流程
- 通过 bind 方法添加监听器, 用以自动绑定递增端口.
- attr 方法, 为每条连接增加属性, 能够实现全单例模式. 类似于 request.attr 属性
- childOption 方法, 关于 TCP 连接的优化:
- SO_KEEPALIVE 底层心跳
- TCP_NODELAY 延迟发送
- SO_BACKLOG 等待队列
二、客户端启动流程
- 还是通过监听器实现重试,但是是 connect 返回的 future,且重试间隔时间左移 1 位增加(性能优化,不使用乘二 ,牛的一逼)。
- 重试不在主线程,而是使用 bootstrap.config().group().schedule 搞定时任务
- 客户端需要 CONNECT_TIMEOUT_MILLIS 属性
三、客户端与服务端双向通信
- 客户端在 channelActive 立刻搞事情,嗯,rpc 通信通常也会做一些处理,例如: 打印客户端ip 之类的。
四、客户端与服务端通信协议编解码(扩展较多)
emm,这个其实就是自定义应用层协议。
- 4 字节魔数校验,例如 dubbo 就使用0xdabb进行校验,Java 字节码也使用 0xcafebabe 校验字节码。
- 版本号肯定需要的
- 序列化算法,肯定也需要的
- 指令,肯定也是需要的,不过,也可以使用别的方式。
- 后面的 数据长度,也是需要的,方便拆包。
其实这里可以参照 RPC 协议 来看,这里更像一个简化的 RPC 协议。
一般 RPC 框架首先获取协议类型,根据这个协议类型得到协议处理器,然后再处理(一个端口处理多个协议的场景)。
生产级别的 RPC 通常较为复杂,以 SOFABolt 为例,需要以下字段:
- 协议版本
- 请求类型,即指令(Request,Response, oneway)
- 指令版本
- RequestID 负责数据对应
- 序列号器
- 协议开关(例如 CRC 校验,安全校验)
- 响应码,约定异常,简化异常
- 类名长度,Java rpc 框架必备
- 请求头长度(参照 http header)
- 请求体长度(参照http body)
- 类名
- 业务请求头内容(一般是 Map,SOFABolt 支持自定义,SOFARPC 里面藏着是否泛化调用等信息)
- 业务请求体内容(一般就是个Request对象或 Response对象,里面包含约定的属性,例如参数,返回值,超级多,SOFARPC 有个属性类 RemotingConstants,这里都有)
- CRC 校验码(金融场景必备)
五、实现客户端与服务端收发消息
- 使用 hannel.attr(Attributes.LOGIN).set(true) 绑定登录标识方便哟
六、构建客户端与服务端 pipeline
- 常用的 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 但是需要强转哦,麻烦, 建议使用 SimpleChannelInboundHandler (还帮你释放内存哟)。
- 不使用 MessageToByteEncoder ,可以自己编解码哦,虽然麻烦点
七、拆包粘包理论与解决方案
- 常用拆包:固定长度,分隔符,基于长度,行拆包(不常用)
- 最通用的就是 基于长度,只要你的自定义协议中包含长度域字段,就可以使用
- LengthFieldBasedFrameDecoder 代替自己继承 ByteToMessageDecoder 哟。
- 对 LengthFieldBasedFrameDecoder 扩展一下,校验魔数关闭错误连接。
八、channelHandler 的生命周期
- 在 channelReadComplete 方法里执行 flush,批量刷新,性能提升。
- channelActive 和 channelInActive 增减连接,RPC 都这么干
九、使用 channelHandler 的热插拔实现客户端身份校验
- ctx.pipeline().remove(this) 删除没有必要的 handler
- handlerRemoved 回调
十、客户端互聊原理与实现
- Session 通过 channel.attr(Attributes.SESSION).set(session) 绑定连接。
- channel.attr(Attributes.SESSION).set(null) 删除 session
- channel.attr(Attributes.SESSION).get()
十一、群聊的发起与通知
- ChannelGroup channelGroup = new DefaultChannelGroup(ctx.executor()) 批量处理连接
- channelGroup.writeAndFlush 批量写连接
最佳实践
牛逼的性能优化
- 共享 handler
- 压缩 handler - 合并编解码器 —— MessageToMessageCodec
- 虽然 有状态 的 handler 不能搞单例,但是你可以绑定到 channel 属性 上, 强行单例
- 缩短事件传播路径 —— 放 Map 里,在第一个 handler 里根据指令来找具体 handler。
- 更改事件传播源 —— 用 ctx.writeAndFlush() 不要用
ctx.channel().writeAndFlush() - 减少阻塞主线程的操作 —— 使用业务线程池,RPC 优化重点
- 计算耗时,使用回调 Future
心跳和空闲检测
- 空闲检测 IdleStateHandler 用起来很爽, channelIdle 和 userEventTriggered 都可以处理
- 定时心跳 ctx.executor().scheduleAtFixedRate 很优秀
- 通常 空闲检测时间 要比 发送心跳的时间 的两倍要长一些(3倍),这也是为了排除偶发的公网抖动,防止误判。
666 彩蛋
刚开始写博客, 希望大家支持, 如果有没疑问或不清楚的地方可以留言噢!
下周再见~