美文网首页
Java I/O 之Netty实战

Java I/O 之Netty实战

作者: landon30 | 来源:发表于2018-07-25 12:22 被阅读0次

    Netty实战

    landon
    资深网络游戏服务器架构师


    UNIX网络编程5种I/O模型

    image

    I/O复用

    • I/O 多路复用技术通过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求
      • 这里进程是被select阻塞但不是被socket io阻塞
    • Select vs Epoll(Linux)
      • process fd、I/O efficiency、mmap、api
    • Reactor vs Proactor
      • Dispatcher/Notifier/Hollywood principle、异步I/O
    • java Nio vs Nio2
      • Linux 2.6 kernal epoll、通过epoll模拟异步
    • Netty Native Transports
      • Since 4.0.16, Netty provides the native socket transport for Linux using JNI. This transport has higher performance and produces less garbage
      • Netty's epoll transport uses epoll edge-triggered while java's nio library uses level-triggered. Beside this the epoll transport expose configuration options that are not present with java's nio like TCP_CORK, SO_REUSEADDR and more.

    Java I/O类库的发展和改进

    • BIO
    • BIO-Improved
    • NIO
    • AIO
    • 不选择Java原生NIO编程的原因
      • epoll bug,导致Selector空轮询,最终导致CPU100%
      • 需要做很多额外工作才能网络层的稳定性
    • 区分I/O模型中的同步/异步和并发编程中的同步/异步(线程阻塞和非阻塞)
      • 同步I/O:需要进程去真正的去操作I/O
      • 异步I/O:内核在I/O操作完成后再通知应用进程操作结果
    • 线程阻塞会占用CPU吗
      • 不会、在I/O密集型的程序,采用并发方式可以提高CPU的使用率

    Netty入门#server

    image

    Netty入门#client

    image

    Netty#thread model

    image

    Netty#ChannelPipeline

    image

    服务端、客户端创建

    • Builder模式
    • Reactor线程池(EventLoopGroup)/Boss-Worker/Acceptor-I/O Processor
    • 每一个NioEventLoop持有一个Selector/一个端口对应一个boss线程
    • ChannelPipeline
      • Head、Tail
      • 父类中的handler是添加到ServerSocketChannel的pipeline的,这个handler在server启动后就行执行,如LoggingHandler
      • 子类的handler是添加导SocketChannel的pipeline的
    • DefaultEventExecutorChooserFactory-单线程
    • NioEventLoop#run
      • 包括非I/O任务
    • 时间轮/IO执行比例

    TCP参数

    • ChannelOption(区分ServerSocketChannel/SocketChannel)
      • SO_REUSEADDR、SO_TIMEOUT
      • TCP_NODELAY、SO_LINGER
      • SO_SNDBUF、SO_RCVBUF
      • CONNECT_TIMEOUT_MILLIS
      • SO_BACKLOG
    • SO_BACKLOG
      • backlog指定了内核为此套接口排队的最大连接个数,对于给定的监听套接口,内核要维护两个队列:未链接队列和已连接队列
      • 和tcp建立链接的三次握手相关
      • backlog被规定为两个队列总和的最大值
      • 长链接项目要注意此值大小

    TCP粘包/拆包问题的解决之道

    • TCP是基于字节流的,只维护发送出去多少,确认了多少,并没有维护消息与消息之间的边界
    • 粘包/拆包
      • 应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包
      • 应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包
      • 进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包
      • 接收方法不及时读取套接字缓冲区数据,这将发生粘包
    • 大压力测试/发送大报文
    • 解决方案
      • 消息定长、消息边界
      • 消息头+消息体,消息头中包含表示消息总长度(或者消息体长度)的字段

    Netty常用编解码器

    • LineBasedFrameDecoder
      • 回车换行解码器
      • 配合StringDecoder
    • DelimiterBasedFrameDecoder
      • 分隔符解码器
    • FixedLengthFrameDecoder
      • 固定长度解码器
    • LengthFieldBasedFrameDecoder
      • 基于'长度'解码器(私有协议最常用)
    • ByteToMessageDecoder
      • 自解析
    • LengthFieldPrepender
      • 编码器

    一个自解析的例子

    image

    LengthFieldBasedFrameDecoder

    • lengthFieldOffset
      • the offset of the length field
    • lengthFieldLength
      • the length of the length field
    • lengthAdjustment
      • the compensation value to add to the value of the length field
    • initialBytesToStrip
      • the number of first bytes to strip out from the

    LengthFieldBasedFrameDecoder#例子1

    image

    LengthFieldBasedFrameDecoder#例子2

    image

    LengthFieldBasedFrameDecoder#解码

    • 根据lengthFieldOffset的字节数目,找到lengthField
    • 然后根据lengthFieldLength,读出长度
    • 这个长度可能是消息体的长度,也可能是消息头+消息体的长度
    • 所以实际消息体的长度是content(lengthFieldLength) + lengthAdjustment
    • initialBytesToStrip则表示从frame中移除的起始字节长度
    • 即协议均是消息头+消息体,消息头中必须有一个Length字段,Length后面的是实际的消息体
    • 实际分析的时候需要看Length的值具体是神马长度
    • 举例某私有协议格式:dataLen(2 byte) + type(1 byte) + reserved(1 byte) + data 其中dataLen为消息体的长度;如果用netty的LengthFieldBasedFrameDecoder就很简单了.

    自解码思路1

    • image
    • mark、reset

    自解码思路2

    • image
    • prefixLength传入2,避免了指针移动

    协议

    • Json
    • Protobuf
      • Protocol buffers are an efficient binary serialization format, not a compressor
      • If you need to reduce the size, you could either gzip the data or use an application-level dictionary to substitute large strings with something smaller
      • snappy
    • Thrift
    • Msgpack
    • 跨语言
    • 内置了protobuf的编解码器

    协议栈

    • http
      • 已提供了基础的HttpServerCodec、加上业务相关的数据编解码器即可
    • websocket
      • 握手走http、提供了websocketx相关支持
    • 私有协议
      • 定义私有协议格式(消息头+消息体)、消息头可扩展
      • 编解码,MessageToByteEncoder/LengthFieldBasedFrameDecoder
      • 握手/心跳/业务 握手成功后,定时器用于定期发送心跳消息
      • 直接利用Netty的 ReadTimeoutHandler机制实现心跳超时
      • 消息缓存重发-提供通知机制,将发送失败的消息通知给业务层
      • 断线重连、重复登录保护、安全机制
      • Handler Chain的机制可以方便地实现切面拦截和定制-扩展性

    API接口1

    • ByteBuf
      • HeapByteBuf
      • DirectByteBuf
      • ByteBuf的最佳实践是在I/O通信线程的读写缓冲区使用DirectByteBuf, 后端业务消息的编解码模块使用HeapByteBuf, 这样组合可以达到性能最优
      • PooledByteBuf
    • Channel、Unsafe
      • ChannelOutboundBuffer#环形数组
      • 实际的I/O读写操作都是由Unsafe接口负责完成的
    • ChannelPipeline、ChannelHandler
      • 过滤器
      • inbound事件-通常由I/O 线程触发
      • Outbound事件-通常是由用户主动发起的网络I/O操作

    API接口2

    • EventLoop、EventLoopGroup
      • 如果业务逻辑处理复杂,不要在NIO线程上完成
      • NioEventLoop它除了负责I/O的读写之外,还负责运行很多系统task/定时任务(局部无锁化)
      • NioEventLoop需要处理网络I/O读写事件,因此它必须聚合一个多路复用器对象(Selector)
      • rebuildSelector
      • setIoRatio
    • Future、Promise
      • 通过添加监听器的方式获取I/O操作结果
      • checkDeadLock-不要在在I/O线程中调用Promise的await或者sync方法会导致死锁

    Netty架构剖析

    • 高性能之道
      • Reactor通信调度层
      • 职责链ChannelPipeline
      • 业务逻辑编排层(Service ChannelHandler)
      • 高性能/可靠性/可定制/可扩展
      • 传输、协议、线程
    • 鸣谢
      • 李林锋
      • 占小狼
      • 江南白衣
    • 彩蛋
      • TIME_WAIT、CLOSE_WAIT

    相关文章

      网友评论

          本文标题:Java I/O 之Netty实战

          本文链接:https://www.haomeiwen.com/subject/jldpmftx.html