Netty怎样切换三种IO模式

  1. 什么是经典三种IO模式 | 排队打饭 | BIO | jdk1.4之前 | | —- | —- | —- | | 点单、等待被叫 | NIO | jdk1.4(java.nio) | | 包厢模式(点菜、菜好了端上来) | AIO | jdk1.7 |

异步IO和同步的区别在于:菜好了谁来端的问题,模式二是得知菜烧好了客户自己停止在做的事去端菜;而模式三是客户一直做别的事,餐厅会把做好的菜端上来(就绪的io通过回调函数返回数据给应用进程)

  1. Netty对三种IO的支持
BIO-OIO
(Deprecated)
NIO AIO(Removed)
Common Linux MacOS/BSD
ThreadPerChannelEventLoopGroup NioEventLoopGroup EpollEventLoopGroup KQueueEventLoopGroup AioEventLoopGroup
ThreadPerChannelEventLoop NioEventLoop EpollEventLoop KQueueEventLoop AioEventLoop
OioServerSocketChannel NioServerSocketChannel EpollSeverSocketChannel KQueueServerSocketChannel AioServerSocketChannel
OioSocketChannel NioSocketChannel EpollSocketChannel KQueueSocketChannel AioSocketChannel
  1. 为什么netty仅支持NIO了
  • 为什么不使用BIO

连接数高的情况下:阻塞->耗资源、效率低下

  • 为什么移除AIO支持
    • windows对AIO实现成熟,但很少用做服务器
    • Linux AIO实现不够成熟
    • Linux下AIO相较NIO性能提升不明显
  1. 为什么Netty有多种IO实现

通用的NIO实现(Common)在Linux下也是使用epoll,为什么要自己单独实现。
实现更好:

  • netty暴露更多可控参数如
    • jdk的NIO默认实现是水平触发
    • netty是边缘触发和水平触发可切换
  • netty实现的垃圾回收更少、性能更好
    1. NIO一定优于BIO么

BIO的优点:

  1. BIO代码简单
  2. 当连接数非常少、并发度低,BIO性能不输NIO.

Netty如何支持三种Reactor?

  1. 什么是Reactor及其三种版本
BIO NIO AIO
Thread-Per-Connection Reactor Proactor

Reactor反应器模式详解

  1. 如何在Netty中使用Reactor模式

image.png

  1. 解析Netty对Reactor模式支持的常见疑问
  • Netty如何支持主从Reactor模式的
  1. // MainReactor
  2. EventLoopGroup bossGroup = new NioEventLoopGroup();
  3. // SubReactor
  4. EventLoopGroup workerGroup = new NioEventLoopGroup();
  5. ServerBootstrap bootstrap = new ServerBootstrap();
  6. bootstrap.group(bossGroup,workerGroup)

将serverSocketChannel注册绑定到bossGroup, serverSocketChannel创建的子socketChannel绑定到workerGroup.

  • 为什么说netty的main reactor大多并不能用到一个线程组,只能线程组里一个?
  • Netty给Channel分配Nio Event loop的规则是什么
  • 通用模式的NIO实现多路复用器是怎么跨平台的

netty解决TCP粘包、半包

image.png

  1. 为什么tcp会出现粘包、半包问题?

粘包的主要原因:

  • 发送方写入数据 < 套接字缓冲区大小
  • 接收方读取套接字缓冲区数据不够及时

半包的主要原因:

  • 发送方写入的数据 > 套接字缓冲区大小
  • 发送的数据大于协议的MTU(最大传输单元),必须折包

image.png
从收发角度:
一个发送可以被多次接收,多个发送可能被一次接收
从传输角度:
一个发送可能占用多个传输包,多个发送可能共用一个传输包

根本原因: TCP是流式协议,消息无边界。
  1. 如何解决粘包和半包?

根本解决手段:找出消息边界
image.png固定长度 是指不足MTU的传统空符占位补齐.
netty的解决实现
image.png

常用二次编解码

  1. 为什么需要二次解码
  • 一次编码器:ByteToMessageDecoder

解决粘包半包的问题 ByteBuf -> ByteBuf

  • 二次编码器:MessageToMessageDecoder

ByteBuf -> java.Object

  1. 常用的二次编解码方式

    • java序列化
    • Marshaling
    • XML
    • Json
    • MessagePack 占用空间少,可读性一般
    • Protobuf 占用空间更少,可读性差
    • 其他
  2. 选择编解码方式的要点

  • 空间: 编码后占用空间
  • 时间:编解码速度
  • 可读性
  • 多语言支持
  1. protobuf简介与使用

    KeepAlive与idle监测

注:这里的keepalive和tcp的KeepAlive原理相同,但和http的header中的keepavlie有本质区别,Http的keepAlive是区分长连接短连接

情景模拟

假如你是餐馆老板,有客户打电话订餐,当客户点了几个食物后,突然不说话了,
这时候,你会稍微等待一段时间倾听和等待客户再次说话(idle检测),
等待20秒后,客户还是没说话,这时候你基本认定客户那边可能出现状况(idle状态),
接下来,你会问:你还在吗?(keepalive_time: 一定时间后尝试询问连接)
仍无响应,你会再问:能听见吗?
喂!
说话啊!(keepalive_intvl:每隔一段时间询问是否连接可用)
尝试若干次后(keepalive_probes: 询问连接次数) , 仍无响应,则挂电话(关闭连接)。

  1. 为什么需要keepAlive?

订餐电话时,客户点了部分餐突然不说话了,这时候挂机就相当于keepAlive.
image.png
对于网络应用而言,keepalive是请求未使用却占用连接,这时候需要keepavlie标记连接为不可用。

  1. 怎样设计keepAlive? 以TCP为例

keepAlive特征:

  • 出现机率比较小
  • 判断连接不要用需要谨慎不能武断

TCP keepalive的核心参数:

  • net.ipv4.tcp_keepalive_time=7200
  • net.ipv4.tcp_keepalive_intvl=75
  • net.ipv4.tcp_keepalive_probes=9

以上参数的意思: 当tcp keepalive打开(tcp默认关闭)时,tcp连接没有传递数据时,7200秒后发送keepalive消息,当没有收到确认消息时,会每隔75秒重发一次,一直尝试9次仍然无响应,则认定连接失败。
也就是认定tcp长连接失败需要:2小时11分钟(7200+75*9)

  1. tcp层有keepalive,为什么还需要应用层keepalive
  • 协议分层,各层关注点不同:

传输层关注是否“通”,应用层关注服务是否可用,网络连接通畅不代表服务可用

  • tcp层的keepalive默认关闭,且经过路由中转设备keepalive包可能被丢弃
  • tcp层的keepalive时间过长

默认2小时太,虽然可配置,但属于系统参数,改动影响所有应用

  1. idle监测是什么

idle只负责监测
idea的作用:

  • 配合发送keepalive

keepalive的演进:v1定时发送keepalive;v2idle检测,无数据传输超过一定时间时判定为idle发送keepalive

  • 直接关闭连接

好处:快速释放恶意、损坏、很久不用的连接
坏处:简单粗暴,客户端需要重连

  1. 如何在netty中开启tcp keepalive和idle
    -

Netty中的那些“锁”事

  1. 分析同步问题的核心三要素
    原子性
    可见性
    线程a对数据的修改,线程b不一定能看到

有序性
  1. 锁的分类

按竞争分:
乐观锁 juc原子包
悲观锁 syncronized
按等待锁的公平性:
公平锁和非公平锁
按是否可共享:
共享和独享

  1. netty玩转锁的五个关键点
  • 减小锁的粒度

synchronized block instead of synchronized method

  • 在意锁对象本身的大小,减少空间占用

volatile long + static AtomicLongFieldUpdater 替代 AtomicLong

  • 提高锁的速度 提高并发性能

longAdder(jdk8) 规则 AtomicLong
ConcurrentHashMapV8 在用户jdk小于1.8的时候,使用netty的ConcurrentHashMapV8提高性能

  • 不同场景使用不同的并发类

CountDownLatch 替代 Object.wait/notify
Jctools’ MPSC 替代 juc.LinkedBlockingQueue (MPMC:多生产者多消费者)

  • 衡量好锁的价值 -> 能不用就不能

场景模拟:ktv包厢,
a. 一个服务员服务某几个固定包厢;
b. 多个服务员服务所有包厢。
表面上看b效率高,但b服务员之间的交流沟通成本也比较大(上下文切换)
netty的做法:
局部串行 + 整体并行 -> 一个队列+多个线程
1 降低开发难度、提升处理性能
2 避免锁带来的上下文切换和并发保护等开销

Netty如何玩转内存使用

  1. 内存使用技巧的目标
  • 内存占用少 (空间)
  • 应用速度快 (时间)

对java而言减少Full GC的Stop World时间

  1. netty内存技巧
  • 能用基本类型就不要用包装类
  • 应该定义为类变量static的就不要定义为实例变量
  • 对分配内存预估 : hashMap指定size
  • 0复制 组合模式等代替data复制
  • 堆外(off heap)内存, jvm外部内存
  • 内存池技巧
    • apach common pool
    • netty的Recycler