通俗地讲,Netty 能做什么?

前言:tomcat一度是web容器的标准,但是tomcat的并发量却只有200-400之间,即使现在有了aio模式,也没有提升太多。

所以现在大部分都是使用netty作为高性能服务器框架,在dubbo,vert.x,gateway等等开源项目中都使用了,那么netty为什么深受喜爱?下文将带你寻找答案

目录
(1) IO模型

(2) zero-copy

(3) 堆外内存

(4) 高性能对象池

阅读netty会发现netty对于java有着很多改进,并适配了不同版本,不用升jdk版本就实现了相关jdk功能,及时优化了Java的bug,epoll的cpu100%空转。netty也封装了java的nio,简化了代码操作。netty本身也提供很多的附加功能,比如流量整形,黑白名单,安全认证等,极大的方便了网络开发。

IO模型

c188bd1ef9124d3ca2179789687e2c5a.png
首先这个问题绕不开io,java有bio,nio,多路复用io,aio
同步异步区别:是否立即返回结果
阻塞非阻塞区别:线程是否需要等待任务完成。 

多路复用:使用linux底层的select、poll、epoll非阻塞api进行调用

信号驱动:类似事件机制进行,向linux发送系统需要调用信号,系统返回调用信号准备结果,期间主线程还是能接收其他请求过来。当返回调用信号成功后,等待数据从内核态复制到用户态,最终完成

bio 、nio、多路复用的解释

在java的nio当中,当线程读取数据的时候,当没有数据可以读取的时候,会立即返回-1给线程,此时线程就知道现在没有多余的数据可以读了,线程就可以继续往下执行。但是在bio中,一旦没有数据可以读取,此时不会返回给线程结果,而是一直阻塞在那里,线程也就无法继续执行代码了。

nio 解决了线程阻塞的问题,就是一旦没有数据可以读,就可以往下执行,但是还是有个问题,就是虽然现在没数据可以读,但是你怎么知道接下来会没有数据读写呢,所以一般都是类似于死循环这种模式去读,读不到就进行下一次循环。虽然不是阻塞,但是还是基本上属于一个线程对一个socket读写的模式。

对于io多路复用,整体的大概是这样的,就是一个或几个线程可以管理一堆socket,socket一旦有读写请求,就会通知你,然后你就可以进行io读写操作了。这些都是依赖操作系统层面。在java中也这种模型api的封装,比如 Selector , SocketChannel ,SocketChannel 可以往 Selector 注册, 一个 Selector 可以管理一堆 SocketChannel ,其实底层最后都是基于操作系统的机制操作的。io多路复用的意思也就是 多个socket 复用一个或多个线程。

所以一般都是nio + io多路复用 一起使用的。当一个线程A来管理一堆Socket,不断去选择有可以进行读写操作的socket(Selector 的 select方法就是干这个事的) ,一旦发现有读写(上面说过,操作系统会通知你哪些socket有读写操作),就将socket交给其他的线程去处理,其他线程处理完了,发现没有读写了,因为是nio,所以不会阻塞,可以继续执行,由于有通知机制,所以这个线程不需要一直循环去判断有没有数据要读取,因为一旦这个socket将来还有数据,还是会通知到线程A,线程A会再次交给子线程处理。基于这种模型,可以实现一定数量的线程就可以完成一大堆socket的io读写操作。

面试补充:

select 缺点:

用户态拷贝到内核态

内核遍历fd(文件描述符号)

支持的文件描述符限制1024

poll 改进:

fd(文件描述符号)没有限制

缺点:

和select一样,poll返回后,需要遍历fd(文件描述符号)来获取就绪的描述符

调用poll都需要大把大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降

epoll 改进:

用户态和内核态共享

回调解决轮训

从表格上来看aio无疑是最佳选择,但是实际上aio并没有带来大量性能提升,而具有reactor的netty框架是当前的性能杀招。

18645dff1c6d464c85b249098550ab32.png 

上图是主从reactor的多线程的原理,通过boss和work作为分发器分发给线程池处理。

boss负责接受请求,什么叫接收请求,就是当有客户端需要跟服务端进行通信的时候,客户端需要跟服务端进行tcp三次握手,之后服务端会创建一个跟客户端通信的Socket,java中的api是叫SocketChannel,这些事都是boss负责的。

work负责读写处理,就是当客户端和服务端进行tcp三次握手之后,成功建立连接,此时客户端向服务端发来请求,work线程就会负责从连接中读取数据,处理请求,然后响应给客户端。

2.零拷贝

有了reactor,netty还有杀招-零拷贝

所谓零拷贝指的是零cpu拷贝,相比普通的拷贝,减少了用户态和内核态的切换,也减少了2次cpu拷贝。而用户态和内核态的切换是很好性能的。

正常的io会经过如下过程

cf7ad072078f4a7eb240698d253d9499.png
而linux进行升级mmp,sendfile api,最终诞生了零拷贝,通过共享用户态避免了向用户态的切换。 

使用文件描述符,代替了内核态的修改,只需传输标识地址,无需修改大量内容。

文件描述符号(简称呼fd):标识打开的文件的记录表

b2ef55917c984a75aeaf6144bcba7cda.png
3.堆外内存 

堆内存创建快,读写慢。堆外内存,创建慢,读写快。学过c和c++的都知道如何申请一块内存,堆外内存也是一样使用,一般可以安全的使用如下api申请。

ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);

在netty中同样支持堆内存,堆外内存,池化,非池化的选择,这也是其高性能利器

4.高性能对象池

在java中对象池往往都非常重,但netty对于这块做了专门的优化,实现了Recycler做对象池,用来做池化内存。

Recycler提供了3个方法

get():获取一个对象。
recycle(T, Handle):回收一个对象,T为对象泛型。
newObject(Handle):当没有可用对象时创建对象的实现方法。
读取netty源码,发现recycler主要依靠DefaultHandler,WeakOrderQueue,Stack实现,如果threadlocal有就拿,没有就新建,DefaultHandler的pop是拿去的核心方法。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蜀州凯哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值