Netty 3. 实战

编写网络应用程序基本步骤:

  • 需求分析 -> 定义业务数据结构 -> 实现业务逻辑
    -> 选择传输协议 -> 定义传输信息结构 ->选择编解码 -> 实现所有的编解码 -> 编写应用程序 -> 测试与改进
  • 定义传输信息结构,选择编解码:
    数据本身编解码
    压缩等编解码
    粘包/半包处理编解码
  • 编写应用程序:
    编写服务器
    编写客户端
  • 编写之后:复查,检索最佳实践,检索坑,对比经典项目实现,同行评审->检查是否可诊断,检查是否可度量->上线-> 反馈:收集错误数据,收集性能数据

案例介绍及数据结构设计:

  • Netty Client ->AuthOperation Netty Server
    <-AuthOperationResult
    ->OrderOperation
    <-OrderOperationResult
    ->KeepaliveOperation
    <-KeepaliveOperationResult
  • 定义传输信息结构:
    Frame
    Message
    MessageHeader | MessageBody(JSON)
    length |version | opCode | streamId | operation/operation result

Netty编程易错点:

  • LengthFieldBasedFrameDecoder 中 initialBytesToStrip 未考虑设置
  • ChannelHandler 顺序不正确
  • ChannelHandler 该共享不共享 不该共享却共享
  • 分配ByteBuf: 分配器直接用 ByteBufAllocator.DEFAULT 等,而不是采用 ChannelHandlerContext.alloc()
  • 未考虑ByteBuf的释放
  • 错以为ChannelHandlerContext.write(msg) 就写出数据了
  • 乱用ChannelHandlerContext.channel().writeAndFlush(msg)

实战进阶:

  • 调优参数:
    • 调整System参数:
      • Linux系统参数:
        /proc/sys/net/ipv4/tcp_keepalive_time
      • Netty支持的系统参数:
        serverBoostrap.option(ChannelOption.SO_BACKLOG, 1024);
        SocketChannel -> .childOption
        ServerSocketChannel -> .option
      • Linux 系统参数:
      • 进行TCP连接时,系统为每个TCP连接创建一个socket句柄,也就是一个文件句柄,但是Linux对每个进程打开的文件句柄数量做了限制,如果超出:报错:too many open file
        ulimit -n[xxx] ulimit命令修改的数值只对当前登录用户的目前适用环境有效,系统重启或用户推出后就会失效,所以可以作为程序启动脚本一部分,让它再程序启动前执行
      • Netty支持的系统参数(ChannelOption.[xxx]):
        不考虑UDP:
        IP_MULTICAST_TTL
        不考虑OIO编程:
        ChannelOptionSO_TIMEOUT=(“SO_TIMEOUT”);
        SocketChannel(7个: childOption):
        Netty系统相关参数 功能 默认值
        SO_SNDBUF TCP数据发送缓冲区大小 /proc/sys/net/ipv4/tcp_wmem: 4K[min,default,max]动态调整
        SO_REVBUF TCP数据接受缓冲区大小 /proc/sys/net/ipv4/tcp)rmem: 4K
        SO_KEEPALIVE TCP层keepalive 默认关闭
        SO_REUSEADDR 地址重用,解决Address already in use 默认关闭
        常用开启场景:多网卡(IP)绑定相同端口
        SO_LINGER 关闭Socket的延迟时间,默认禁用 默认不开启
        IP_TOS 设置IP头部的Type-of-Service字段 1000 minimize delay
        用于描述IP包的优先级和Qos选型 0100 maximize throughput
        0010 maximize reliability
        0001 minimize monetary cost
        0000 normal service
        TCP_NODELAY 设置是否启用Nagle算法:用将小的碎片数据 False 如果需要发送一些较小的报文,则需要禁用该算法
        连接成更大的报文来提高发送效率
        ServerSocketChannel (3个: option):
        SO_RCVBUF 为Accept创建的socket channel设置SO_RCVBUF
        SO_REUSEADDR 是否可以重用端口 默认false
        SO_BACKLOG 最大的等待连接数量 Netty再linux下值的获取:io.netty.util.NetUtil:
        先尝试: /proc/sys/net/core/somaxcon
        然后尝试:sysctl
        最终没有取到:用默认 128
        使用方式:javaChannel().bind(localAddress, config.getBacklog())
  • 权衡Netty核心参数:
    • 参数调整要点:
      option/childOption分不清:不会报错,但是不会生效
      不懂不要动,避免过早优化
      可配置(动态配置更好)
    • 需要调整的参数:
      最大打开文件数:
      TCP_NODELAY SO_BACKLOG SO_REUSEADDR(酌情处理)
    • ChannelOption
      childOption(ChannelOption.[XXX], [YYY])
      option(ChannelOption.[XXX],[YYY])
    • System property
      -Dio.netty.[XXX] = [YYY]
    • ChannelOption (非系统相关 11个)
      Netty参数 功能 默认值
      WRITE_BUFFER_WATER_MARK 高低水位先,间接放置写数据OOM 32K -> 64K
      CONNECT_TIMEOUT_MILLIS 客户端连接服务器的最大允许时间 30秒
      MAX_MESSAGE_PER_READ 最大允许“连续”读次数 16次
      WRITE_SPIN_COUNT 最大允许“连续”写次数 16次
      ALLOCATOR ByteBuf分配器 ByteBufAllocator.DEFAULT: 大多池化,堆外
      RCVBUF_ALLOCATOR 数据接收ByteBuf分配大小计算器+读次数控制器 AdaptiveRecvByteBufAllocator
      AUTO_READ 是否监听“读事件” 默认监听读事件
      AUTO_CLOSE "写数据"失败,是否关闭连接 默认打开
      MESSAGE_SIZE_ESTIMATOR 数据(ByteBuf FileRegion)大小计算器 DefaultMessageSizeEsimatro.DEFAULT
      SINGLE_EVENTEXECUTOR_PER_GROUP 当增加一个handler且指定EventExecutorGroup 默认true
      ALLOW_HALF_CLOSURE 关闭连接时,允许半关 默认:不允许半关
    • 3个费脑参数:
      SO_REUSEADDR
      SO_LINGER
      ALLOW_HALF_CLOSURE

跟踪诊断:

  • 如何让应用易诊断:
    完善”线程名“
    完善“Handler”名称
    使用好Netty的日志
    Netty日志的原理及使用:
    Netty日志框架原理
    修改JDK logger级别
    使用slf4j + log4j示例
    衡量好logging handler 的位置和级别
  • 应用可视:
    如何做Netty的可视化
    Console 日志定时输出
    JMX实时展示
    Netty值得可视化的数据
    外在:
    可视化信息 来源 备注
    连接信息统计 channelActive/channelInactive
    收数据统计 channelRead
    发数据统计 write ctx.write(msg).addListener() 更准确
    异常统计 exceptionCaught/ChannelFuture ReadTimeoutException.INSTANCE
    内在:
    可视化信息 来源 备注
    线程数 根据不同实现计算 nioEventLoopGroup.executorCount()
    待处理任务 executor.pendingTasks() 例如:Nio Event Loop 的带处理任务
    积累的数据 channelOutboundBuffer.totalPendingSize Channel级别
    可写状态切换 channelWriteabilityChanged
    触发事件统计 userEventTriggered IdleStateEvent
    ByteBuf分配细节 Pooled/UnpooledByteBufAllocator.DEFAULT.metric()
  • 让应用内存不“泄露”
    本节的Netty内存泄露:
    原因:”忘记release“
    ByteBuf buffer = ctx.alloc().buffer()
    后果:资源未释放 -> OOM
    堆外:未free (PlatformDependent.freeDirectBuffer(buffer0));
    池化:未归还 (recyclerHandler.recycle(this)
    Netty内存泄露检测核心思路
    引用计数(buffer.refCnt()) + 弱引用(Weak reference)
    引用计数:
    强引用与弱引用:
    Netty内存泄露检测核心思路:
    ByteBuf buffer = ctx.alloc().buffer -> 引用计数 + 1 -> 定义弱引用对象DefaultResourceLeak加到Set(#allLeaks)里
    buffer.release: -> 引用计数 - 1 -> 减到0时,自动执行释放资源操作,并将弱引用对象从Set里移除
    判断依据:弱引用对象在不在Set里?如果在,说明引用计数还没到0 -> 没有到0,说明没有执行释放
    判断时机:弱引用执行对象被回收时,可以把弱引用放进指定ReferenceQueue里面去,所以遍历queue拿出所有弱引用用来判断
    Netty内存泄露检测的源码解析
    全样本?抽样? PaltformDependent.threadLocalRandom().nextInt(samplingInterval)
    记录访问信息: new Record(): record extends Throwable
    级别/开关: io.netty.util.ResourceLeakDetector.Level
    信息呈现: logger.error
    触发汇报时机: AbstractByteBufAllocator#buffer(): io.netty.util.ResourceLeakDetector#track()
    示例:用Netty内存泄露检测工具做检测
    方法: -Dio.netty.leakDetaction.level=PARANOID
    注意:
    默认级别:SIMPLE 不是每次都检测
    GC后,才有可能检测到
    注意日志级别:
    上线前用最高级级别,上线后用默认

优化使用:

  • 用好注解:
    • @Sharable
      标识handler提醒可共享,不标记共享的不能重复加入pipeline
    • @Skip
      跳过handler的执行
    • @UnstableApi
      提醒不稳定,慎用
    • @SuppressJava6Requirement
      去除 Java6 需求的报警
    • @SuppressForbidden
      取出禁用报警
  • 整改线程模型,让响应健步如飞
    • 业务的两种场景:
      • CPU密集型:运算型
        保持当前线程模型:
        Runtime.getRuntime().availableProcessors() * 2
        io.netty.availableProcessors * 2
        io.netty.eventLoopThreads
      • IO密集型: 等待型
        整改线程模型:独立出 ”线程池“ 来处理业务
        在handler内部使用JDK Executors
        添加handler时,指定1个:
        EventExecutorGroup eventExecutorGroup = new UnorderedThreadPoolEventExecutor(10);
        pipeline.addLast(eventExecutorGroup, serverHandler)
  • 增强写,延迟与吞吐量的抉择:
    • “写”的问题:
      • 改进方式1:channelReadComplete
      • 改进方式2:flushConsolidationHandler
  • 如何让应用丝般”平滑“:
    • 流量整形的用途:
      • 网盘限速(主动)
      • 景点限流(被动)
    • Netty内置的三种流量整形:
      • Channel级别
      • ChannelTrafficShapingHandler
      • GlobalTrafficShapingHandler
    • Netty流量整形的源码分析与总结:
      • 读写流空判断:按一定时间段checkInterval(1s)来统计。writeLimit/readLimit设置的值为0时,表示关闭写整形/读整形
      • 等待事件范围控制:10ms(MINIMAL_WAIT) -> 15s (maxTime)
      • 读流控:取消读事件监听,让都缓存区满,然后对端写缓存区满,然后对端写不进去,对端对数据进行丢弃或减缓发送
      • 写流控:待发数据入Queue。
    • 流量整形的使用:
      • ChannelTrafficShapingHandler
      • GlobalTrafficShapingHandler: share
      • GlobalChannelTrafficShapingHandler: share
  • 为不同平台开启Native
    如何开启Native:
    修改代码:
    NioServerSocketChannel -> [Prefix]ServerSocketChannel
    NioEventLoopGroup -> [Prefix]EventLoopGroup
    准备好native库:
    java.library.path: /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
    META-INF/native
    Native相关的参数:
    io.netty.transport.noNative
    io.netty.native.workdir
    io.netty.native.deleteLibAfterLoading
    源码分析Native库的加载逻辑:
    平台:
    执行权限

安全增强:

  • 设置”高低水位线“保护
    Netty OOM的根本原因
    根源: 进(读速度) 大于出(写速度)
    表象:
    上游发送太快:任务重
    自己:处理慢/不发或发的慢:处理能力有限,流量控制等原因
    网速:卡
    下游处理速度慢:导致不及时读取接受Buffer数据,然后反馈到这边,发送速度降速
    Netty OOM ChannelOutboundBuffer
    存的对象:Linked list 存 ChannelOutboundBuffer.Entry
    解决方式:判断totalPendingSize > writeBufferWaterMark.high()设置unwritable
    ChannelOutboundBuffer
    Netty OOM TrafficShapingHandler
    存的对象:messageQueue 存 ChannelTrafficShpingHandler.ToSend
    解决方式:判断queueSize > maxWriteSize 或 delay > maxWriteDelay 设置 unwritable
    AbstractTrafficShapingHandler
    Netty OOM 对策
    设置好参数:判断channel.isWritable()
    高低水位线(默认32k-64k)
    启用流量整形时才需要考虑
    maxwrite(默认4M)
    maxGlobalWriteSize(默认400M)
    maxWriteDelay(默认4s)
  • 启用空闲监测:
    服务器加上 read idle check -服务器10s接收不到channel的请求就断掉连接
    保护自己(及时清理空闲的连接)
    客户端加上write idle check + keepalive - 客户端5s不发送数据就发一个keepalive
    避免连接被断
    启用不频繁的keepalive
  • 简单有效的黑白名单
    Netty中的 ”cidrPrefix“
    网络位 主机位
    Netty地址过滤功能源码分析:
    同一个IP只能有一个连接
    IP地址过滤:黑名单 白名单
    使用黑名单增强安全
  • 少不了的自定义授权:
    使用自定义授权:
  • 拿来即用的SSL-对话呈现表象:
    SSL
    SSL/TLS协议在传输层之上封装了应用层数据,不需要修改应用层协议的前提下提供安全保障
    TLS(传输层安全) 是更为安全的升级版SSL
    SSL的功能与设计:
    基于”单向验证+交换密钥方式为RSA方式“
    角色:
    内容的加密: 对称加密方式 (效率高)
    对称加密密钥的传递:非对称加密方式 公钥:邮箱 私钥:邮箱密码
    Netty中使用SSL: io.netty.handler.ssl.SslHandler
    单向认证
    服务器端准备证书:自签或购买
    服务器端加上SSL功能
    导入证书到客户端
    客户端加入SSL功能
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值