Netty权威指南笔记

TCP黏包拆包问题

问题描述

黏包:
1. 一次从缓冲区, 读取到了多个完整数据包, 需要将这几个数据包分别解析.
拆包:
1. 一次从缓冲区, 读到了一个数据包的部分(可能由于服务端TCP接收滑窗非常小), 需要多次读取才能构建完整的数据包
黏包+拆包:
1. 一次从缓冲区, 读取到一个数据包+另一个数据包的部分

解决方案

  1. 消息定长, 例如每个报文大小固定xx个字节, 如果不够, 则用空格补充
    1. FixedLengthFrameDecoder
  2. 在包尾设定分隔符: 例如FTP协议用回车+换行分隔
    1. LineBasedFrameDecoder
    2. DelimiterBasedFrameDecoder
  3. 消息设置为消息头+消息体: 消息头中用int32字段代表消息的总长度
  4. 更复杂的应用层协议

LineBasedFrameDecoder原理

// todo

Netty编解码

现有序列化方案

Java默认序列化

缺点:

  • 无法跨语言
  • 序列化后码流太大
  • 序列化性能太差
    几乎没有主流的RPC采用

Protobuf

  • 语言无关, 平台无关, 扩展性好
  • 性能好

XML/Json

解析时间开销, 空间开销都太大; 不适合做高性能的通信协议

Netty中对Java序列化支持

ObjectEncoder
ObjectDecoder

Netty中对Protobuf序列化支持

ProtobufVarint32FrameDecoder: 用于半包处理
ProtobufDecoder: 仅负责解码, 不支持读半包, 因此需要使用: ProtobufVarint32FrameDecoder
ProtobufVarint32LengthFieldPrepender
ProtobufEncoder

Netty HTTP协议开发

链路关闭场景

  • 对方宕机或者重启, 会主动关闭链路:
    • 另一方会读取到操作系统层的通知信号, 得知对方REST链路, 需要关闭连接, 释放自身的句柄资源.
    • 是否有主动停机, 优雅关闭 类似 kill -3 ?
  • 消息读写过程, 发生了IO异常, 需要主动关闭
  • 心跳读写过程, 发生了IO异常, 需要主动关闭
  • 心跳超时, 需要主动关闭
  • 编解码等不可恢复错误时, 需要主动关闭

可靠性设计

心跳机制

  • 网络空闲持续时间达到T(连续周期T没有读写消息)时, 客户端主动发送Ping心跳给服务端(这个与zkClient机制很相似)

  • 如果在下一个周期T到来时, 客户端没有收到对方发送的Pong心跳应答消息, 或者读取到服务端发送的其他业务消息, 则心跳失败计数器+1

  • 每当客户端接受到服务端的业务消息或者Pong应答消息, 则将心跳失败计数器清零.

  • 当连续N次没有接受到服务端的Pong消息或者业务消息, 则主动关闭链路. 间隔INTERVAL之后, 发起重连操作.

  • 服务端网络空闲持续时间达到T后, 服务端心跳失败计数器+1; 只要接收到客户端的Ping消息或者其他业务消息, 计数器清零

  • 服务端连续N次没有接收到客户端的Ping消息或者其他业务消息, 则关闭链路, 释放资源, 等待客户端重连(这点与客户端机制不同).

  • 需要防止: 由于一方短时间内繁忙没有及时返回应答消息导致的误判, 所以需要连续N个心跳检测都失败, 才能认定链路已经断连, 需要关闭并重建.

  • 如果读写心跳发生IO异常, 说明链路已经中断, 此时就立刻关闭链路:

    • 如果客户端, 则需要重新发起连接.
    • 如果是服务端, 则需要清空缓存的半包信息, 等待客户端重连.

其他

// 如下代表啥意思:
Unpooled.buffer(req.lenght);
Unpooled.copiedBuffer(currentTime.getBytes());
// 根据操作系统获取换行符, 例如Linux下是\n, Windows下是\r\n(\r回车, 即把打字机针头放到行首; \n换行, 即把纸向下移动一行), Mac下是\r
// Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行; 
// 而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号
System.getProperty("line.separator")

Java应用中常用4种跨节点通信方案

  • RMI
  • Socket + Java序列化
  • 开源RPC框架, dubbo, thrift, avro
  • 公有协议: HTTP+XML, RESTful + JSON 或者 WebService

Java跨节点通信私有协议

  • 请求响应消息编解码定义
  • 控制和管理类指令
    • 链路建立的握手请求和响应消息
    • 链路检测的心跳消息

UpStream&DownStream的理解

  • UpStreamHandler, 即消息是从TCP/IP协议栈的下层往上层传输, 即从 物理层(网卡) -> 传输层(TCP) -> 应用层(Netty代码处理), 即Inbound
  • DownStreamHandler, 即消息是从TCP/IP协议栈的上层往下层传输, 即从 应用层(Netty代码处理) -> 传输层(TCP) -> 物理层(网卡), 即Outbound



ChannelHandler线程安全的理解

前提: OneHandler per Channel

  • UpStreamHandler是不必担心线程安全性问题的:
    • 单个TimeServerHandler内部, 不必担心i++共享导致的问题.
    • 两个TimeServerHandler会串行执行.

ChannelUpstreamHandler will be invoked sequentially by the same thread

// socketChannel.pipeline().addLast(new TimeServerHandler()).addLast(new TimeServerHandler());
private class TimeServerHandler extends ChannelInboundHandlerAdapter {
    int i = 0;
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        i++;
        System.out.println(i);
    }
}

-