1、Netty那些事 - 初识

364 阅读7分钟

天高云淡又经秋,不吝青春吝白头。年少半生缺努力,老来百事付江流。

一、Netty初识

  • 作为一名Java攻城狮,无论你是从事业务开发还是从事基础架构开发亦或大数据开发,想必Netty都是一座值得各位攀登挑战的大山。笔者第一次听说Netty这个词,还是17年刚毕业的时候,加了一个qq群,里面有个美团月薪55k的大佬,闲暇时间手撸了一个RPC框架,当时真是膜拜之际(手动🐶x1),估计大家都难以想象,对于一个刚毕业的只会基础的CURD来说,有多震撼。那时也研究了其源码,可惜经验有点不足,未领悟到精髓。

  • 后来跳槽到了某社交公司,内部有一套自研的RPC框架,工作期间,也顺带看了看其实现,发现内部底层网络通信也是用的Netty,这是第二次正儿八经接触Netty。不过当时研究重心放到了网络协议和负载均衡上,依然未深入的研究Netty底层实现。

  • 这段时间在研究Dubbo源码实现,有兴趣的可以看看我的Dubbo专栏(手动🐶x2)。发现Dubbo内部的网络通信也是用的Netty,在研究Dubbo的网络协议的时候,顺带看了看一些Netty的相关知识,也算恶补了一些原理性知识。

  • 最近刚入职新公司,有个新的系统要从头开始搭建,其中就包含了基于Netty实现发布订阅。这么好的应用机会当然不能错过了,于是主动的承担了这部分开发。实际应用Netty的过程中,碰到了一些问题,踩了一些坑,因此想借此机会,好好的看下Netty实现,记录下自己学习的学习路程,也顺便沉淀下自己。

二、一些概念

2.1. Netty io模型

Netty是一款流行的Java Nio框架,底层其实也是运用了Java的Nio技术。说起IOm模型,Java其实还有BIO和AIO。BIO:阻塞式IO;NIO:多路复用IO;AIO:异步IO,底层是靠操作系统的回调。各个模式的区别这里就不展开讲了,可自行百度。简单画下Netty NIO模型:

image.png

selecotr一般来说都是单线程,可以监听多个channel,会循环检测是否有就绪的channel,并把对应的事件交给不同的worker线程去执行。说起selector就不得不提下select、poll和epoll的区别。

系统调用selectpollepoll
事件集合用户通过readset、writeset、exceptset分别传入可读、可写及异常等事件,内核通过改参数来反馈就绪事件每次调select都要重置三参数统一处理所有事件,故只需传事件集用户通过 fd.events 传入关注事件,内核通过改 pollfd.revents 反馈就绪事件内核通过一事件表管理所有事件每次调epoll_wait,无需反复传事件epoll_wait 参数events仅反馈就绪事件
最大支持的文件描述符数一般有最大值限制:1024系统允许打开的最大文件描述符数目:65535系统允许打开的最大文件描述符数目:65535
工作模式LTLTLT/ET
应用程序索引就绪文件描述符的时间复杂度O(n)O(n)O(1)
内核实现和工作效率轮询检测就绪事件,时间复杂度O(n)轮询检测就绪事件,时间复杂度O(n)回调检验就绪事件,时间复杂度O(1)|

Netty的多路IO模型默认是epoll模型,和JDK的epoll模型区别在于,JDK是水平触发,Netty的则是边缘触发。

2.2. Netty线程模型

Netty线程模型主要是由两个线程组构成,分别是Boss线程组和Worker线程组,如下图所示: image.png

当Boss线程监听到OP_ACCEPT事件发生时,会把SocketChannel封装成NioSocketChannel,并注册到Worker线程组的selector上,Boss线程一般是只开启一个线程。

Worker线程组监控I/O的读写事件,当对应的事件触发时候,会向内存池申请内存,用来处理数据流,并把对应的数据流发给Netty的编解码handler进行解析,解析出完成的数据包后,会把对应的数据转发给具体的业务handler。

2.3. Netty编解码

说到Netty的编解码,就要提下TCP的拆包和粘包,由于TCP协议是面向字节流传输的协议,不关心数据的状态。如果客户端一次传输的数据过大,可能会把数据分成多个数据包发出去,也就是拆包。这时候服务端接受的时候,就要进行数据的组装,也就是粘包。如下图所示: image.png 不同的数据在拆包之前肯定要加一些特殊的“标记”来区分,否则服务端是无非正常组装的。 这里常用的解决方案有三种

  • 将换行符或者特殊符号加入数据包中
  • 将消息分成head和body,先获取head信息,根据head信息,再去获取body信息(参考dubbo协议设计
  • 固定数据包的长度,不足用占位符补充。

Netty针对上面几种解决方案,分别提供下以下几种实现:

  • 针对方案1,Netty提供了LineBasedFrameDecoder,可以判断字符中是否包含\n 或者\r\n
  • 针对方案2,Netty提供了LengthFiledPrepender和LengthFieldBasedFrameDecoder
  • 针对方案3,Netty提供了FixedLenghtDecoder,用来解析固定长度数据包

2.4 Netty序列化

说起序列化想必大家都不陌生,由于网络只能传输二进制信息,因此java对象在进行网络传输前要进行数据格式的转换,序列化就是java对象转成二进制流的过程。最常用的序列化想必就是JSON了,数据可读性好,开源社区也有很多工具可以用入FastJson、Jackson,Gson等,使用起来也方便,一般都是一行代码。缺点则是JSON后的数据体积比较大,不太适合网络传输,毕竟带宽都是钱啊。Netty默认提供了很多编解码方式,目录为io.netty.handler.codec image.png

2.5 零Copy

零拷贝的概念这里就不展开讲了,可自行百度。对于Netty来说,零Copy主要应用在以下三个场景:

  • Netty 提供了 CompositeByteBuf 类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个 ByteBuf 之间的拷贝
  • 通过FileChannel.tranferTo 实现文件传输,可以直接将文件缓冲区的数据发送到目标 Channel, 避免了传统通过循环 write 方式导致的内存拷贝问题
  • Netty的接收和发送ByteBuffer使用直接堆外内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用JVM的堆内存进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中

三、小节

本文主要为大家介绍了一些Netty中的一些概念,这些概念对后面理解Nett源码还是很重要的,而且,面试中还会经常被问到(手动🐶x3),我就不止一次的面试官问:说说Netty的线程模型吧。组内有个同龄人,职级比我高了一级,我感觉差的最多就是Netty这部分,因此,学好Netty,真的就是在挣钱啊。感兴趣的可以先点个收藏,后面一段时间内会逐渐的为大家解读Netty的各部分设计。