Netty
Netty
提示
本篇文章很多概念和思想来源于《Netty 实战》
Netty 是一款异步事件驱动的网络应用程序框架,支持快速地开发可维护的、高性能的、面向协议的服务器和客户端。
图来自官网
Netty、Jetty、Undertow、Tomcat
是 Reactor模型实践者, Tomcat、Jetty、Undertow
是以服务器形式存在的,Netty
则是被大量应到到轻量级Rpc框架中, 优秀应用案例可以从《Netty 实战》中进行了解。
名词解释
- Accept为服务器创建连接的,一般情况下为CPU密集型,不会成为阻塞点;
- Handler为服务器处理任务抽象,其包含CPU密集型、IO密集型、混合型。
- CPU密集型主要处理内存中的数据,例如Redis;
- IO密集型主要从磁盘读取数据,例如Mysql、Oracle等。
提示
一般情况下业务服务器都会涉及到磁盘读取数据,主要为IO密集型、混合型,所以在做优化时,使用Redis缓存其根本原因是将两种类型转换成CPU密集型
设计理念
设计项目,需要思考那些因素,无论是开源项目还是业务项目,一般情况下按照三个方向去考虑。
- 解耦合,业务与领域关注点分离,例如
Netty
将业务和网络解耦合; - 模块化、复用性;
- 模块化,保证包依赖的传递性,越是底层包越需要稳定,服务依赖亦是如此,可以参考《结构整洁之道》 第14章 组件耦合关于
Component
依赖讲解; - 复用性,Doug lea 《Objects in Groups》讲了
bean
的不被侵入性,保证复用性。
- 模块化,保证包依赖的传递性,越是底层包越需要稳定,服务依赖亦是如此,可以参考《结构整洁之道》 第14章 组件耦合关于
- 可测试性,可测试性是保证大型系统稳定性的保证,可以在开发、测试阶段暴露问题,而不是将问题推迟到外部引用(开源项目)、线上(业务系统)。
可伸缩性
可伸缩性可以应用到服务器层面和代码层面,服务器一般为Serverless
,可伸缩性使用k8s
的编排能力即可,程序员更多的关注代码层面处理,代码层面包含以下特征:
- 非阻塞网络调用使得我们可以不必等待一个操作的完成。完全异步的 I/O 正是基于这个特性构建的,并且更进一步异步方法会立即返回,并且在它完成时,会直接或者在稍后的某个时间点通知用户,关于异步转同步可以查看Future。
- Selector使得我们能够通过较少的线程便可监视许多连接上的事件。
IO
BIO
BIO同步阻塞IO,使用同步阻塞IO时,因为其阻塞原因,需要将Accept
的连接交接给其他线程做处理,其他线程可以使用每次创建、池话两种处理方式处理。
- 任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,会产生资源浪费;
- 需要为每个线程的调用栈都分配内存,其默认值 大小区间为 64 KB 到 1 MB,具体取决于操作系统;
- 关于这里有一些补充,例如在
Jvm
中Xss 参数
指定的情况下,如果无限制的创建线程也会导致OOM;
- 关于这里有一些补充,例如在
- Java 虚拟机在物理上可以支持非常大数量的线程,但是远在到达该极限之前,上下文切换(从内存加载数据到寄存器或虚拟机栈)所带来的开销会很大。Java代码中经典案例是Juc非公平锁,非公平锁下当被唤醒节点被唤醒到抢占锁这段时间(内核线程调度、上下文加载)会被其他线程抢占先机,这也就是为什么非公平锁性能很高。
- 寄存器数据加载,普通寄存器(加载运行数据)、CS、IP寄存器(代码运行指示器)等;
- 虚拟机栈加载,对栈帧加载,PC寄存器加载等;
在此基础上,使用池话可以降低创建线程的开销,但无法解决线程阻塞等待资源问题。
NIO
NIO同步非阻塞IO,使用非阻塞IO时,将Accept
的连接交接给其他线程做处理,其他线程可以使用每次创建、池话两种处理方式处理。
使用每次创建是不太合理的,线程在不阻塞时间段是可以处理其他任务的,所以使用池话比较合理,使用池话可以使用少量线程处理大量任务,handler
生命周期和任务固定的情况下,线程会一直运行不会被销毁,其和Juc
线程下的work
理论是一致的,所以其性能相当高,与Go
语言的GPM
基本一致。
- 使用较少的线程便可以处理许多连接,因此也减少了内存管理和上下文切换所带来开销;
- 当没有
I/O
操作需要处理的时候,线程也可以被用于其他任务。
AIO
AIO为异步非阻塞IO,在BSD、Linux系统上支持不是很好,在Windows上支持比较好,而服务器一般部署在Linux系统上。Netty5时基于AIO进行编写的,使用BenchMark
对Netty5和Netty4进行对比,发现Netty5性能上没有质变差异,并且代码量复杂高,维护成本非常高,所以社区对Netty5
进行废弃,不在维护。
池话线程多还是少
池话线程是一个需要动态调整的过程,主要包含一下原因:
- 线程较少,导致某些任务排队过长;
- 线程较多,导致休眠、上下文切换竞争加剧问题。
Netty NIO为什么性能高
- 线程常驻,不会产生上下文切换;
- BossGroup(一般情况下一个线程)监控连接,并创建连接;
- WorkGroup生命周期按照模版进行处理,Channel处理是基于Selector事件进行处理;
- BossGroup向WorkGroup异步提交任务;
- NioEventLoop无锁设计;
- 对内存数据管理机制。