Netty个人总结

Netty个人总结

netty模型是什么样的

首先netty里面会有两个线程池,一个是

我们按照netty服务端的启动方法逐行解释

如下图:
在这里插入图片描述
ChannelOption.SO_BACKLOG
我们先来说下option(ChannelOption.SO_BACKLOG, 128),这句话中的ChannelOption.SOBACKLOG表示的是什么呢?
在描述这个功能之前,首先需要知道客户端和服务器端是怎么建立连接的?客户端和服务器端会通过tcp协议三次握手建立连接。建立连接之后通过什么来传输数据呢?在操作系统里面有个网络套接字Socket,这个关键字主要是用来网络传输使用的,比如在TCP/IP七层通信架构中,在传输层会把Socket关键字融入到tcp协议里面,然后Socket套接字里面就包含了我们应用层http协议请求所包含的参数信息,也就是包含了客户端传输的信息。到了客户端也就是对端的传输层,暂且理解为服务器的传输层,这个时候会使用ServerSocket服务器端的套接字,因为都是在传输层都是使用的tcp协议,所以客户端传递的信息就会从Socket被解析到ServerSocket中,这样服务器就通过ServerSocket套接字拿到了客户端Socket套接字中传递过来的请求信息了。

在描述这个功能之前,还需要知道在Netty模型中,BossGroup线程池成功连接客户端请求之后,会怎么传递给WorkerGroup线程池去处理请求?
首先在我们的netty网络模型中,客户端使用的套接字不是Socket,而是NioSocketChannel;服务端使用的套接字也不是ServerSocket,而是NioServerSocketChannel;
所以在netty的套接字中既包含了操作系统的套接字,又可以通过我们的套接字获取到对应的客户端信息,我们可以知道处理的客户端是谁,因为netty中的套接字包含了Channel,因此我们就可以通过NioSocketChannel或者NioServerSocketChannel套接字去获取到Channel通道信息,也就是客户端的具体信息。

所以当客户端和服务端通过tcp协议三次握手,成功建立了连接之后,BossGroup线程池也就是这个负责处理客户端连接的线程池会把客户端相关信息通过NioServerSocketChannel注册到WorkerGroup线程池里面去,那具体这个NioServerSokcetChannel会注册到哪里去呢?它会注册到WorkerGroup中的某一个空闲线程的队列中去,当成一个队列任务。那么WorkerGroup线程池中的线程是谁呢?是NioEventLoop线程。这个线程内部是使用的Nio相关的封装,类似与NIO中的IO多路复用,一个Thread线程对应一个Seletor选择器,然后一个选择器对应多个Channel,也就相当于一个Thread线程可以处理多个客户端连接。再回到我们的nttey中,当建立tcp连接之后,当BossGroup线程池把对应的客户端执行任务NioServerSocketChannel注册到WorkerGroup线程池里面的时候,那假如说WorkerGroup线程池里面的每一个线程都在处理某个客户端请求,这个时候不能再接收新的客户端请求处理任务了,那要怎么办呢?

普及完上面的两个知识点之后,我们引出了一个问题,也就引出了我们的ChannelOption.SO_BACKLOG的作用了?
ChannelOption.SO_BACKLOG的作用是设置一个队列大小,这个队列里面存储的是处理客户端请求的任务,其实也就是在客户端和服务端经过tcp三次握手建立连接之后,BossGroup会把NioServerSocketChannel套接字注入到WorkerGroup中,我们的队列里面就是存储的这个套接字。
在什么情况下会把这个套接字加入到这个等待队列里面呢?就是当tcp通过三次握手连接之后,发现这个时候WorkerGroup线程池里面的所有线程都在忙碌,也就是所有线程都在处理某一个客户端请求,那么这个时候我们就会把NioServerSocketChannel套接字加入到一个等待队列里面,当WorkerGroup中的某一个NioEventLoop线程处理完了当前客户端的所有任务了,就会接收新的客户端任务了,也就会去消费我们等待队列中的套接字了。
而ChannelOption.SO_BACKLOG的作用就是来设置这个等待队列的大小的。

最后说一下ChannelOption.SO_BACKLOG这个WorkerGroup线程池中的等待队列的大小该怎么设置?
当我们的客户端请求并发数非常多的时候,这个时候WorkerGroup线程池中的所有NioEventLoop线程都处于忙碌状态,都有一个对应的客户端请求处理,那么新建立连接的客户端请求对应的套接字任务就会有很多,这个时候我们的等待队列的大小就需要设置的大一些。但是也不能太大,因为如果太大的话,我们维护等待队列里面的任务也是需要耗费系统性能的。所以需要设置一个合理的值,那么这个合理的值怎么判断呢?这个我们可能要实际情况分析一下了,比方说现在客户端连接有600个,而我们WorkerGroup线程池里面的线程有60个,那么最坏的情况下我们需要把这个队列的大小设置为540,因为60个线程可能同时再处理60个客户端,所以新来的540个任务都需要放到等待队列里面。但是实际情况处理客户端请求任务要比这快的多,所以这种情况我觉得把等待队列大小设置为100个应该就足够用了。

注意等待队列的大小不能设置的太小,如果设置的太小的话就会丢失客户端请求任务。

然后来说下NioEventLoop
NioEventLoop的本质是一个Thread线程,它是BossGroup线程池或者WorkerGroup线程池里面使用的线程。那为什么要叫做NioEventLoop呢?因为它内部是一个一直循环轮询实行的循环机制。如下图:
在这里插入图片描述
可以发现我们的NIOEventLoop本质上就是一个Thread线程,因为它会关联一个Thread线程,然后这个线程会一直轮询的去执行,那怎么理解这个轮询呢?比如说我们现在是BossGroup中的NIOEventLoop线程轮询,轮询就是会去循环监听每个客户端Channel通道中有没有OP_ACCEPT事件发生,如果有的话证明我们的客户端和服务器端tcp协议三次握手建立连接成功了,这个时候BossGroup会把处理客户端请求的套接字NIOServerSocketChannel注册到WorkerGroup线程池中;那假如说我们现在是WorkerGroup中的NIOEventLoop线程轮询,轮询就是一直处理我们当前正在处理的那个客户端请求中的IO事件,任务逻辑等。

在BossGroup连接客户端的线程池中,NIOEventLoop中为什么可以一直轮询的查看每个客户端是否有连接请求呢?这主要是因为用到了IO多路复用技术,其实也就是NIO中主要用到的技术,相比与阻塞BIO,NIO一个线程可以处理多个客户端连接请求,IO多路复用技术如下图:
在这里插入图片描述
Netty中的NioEventLoop底层原理也是用的IO多路复用技术,和NIO是一样的,那么为什么不直接用NIO而要使用Netty呢?因为NIO的各种API使用起来太费劲太麻烦了,所以后来就衍生出了一种对NIO的封装,其实就是我们的Netty框架。Netty框架操作简单,但底层原理还是用的NIO的那一套。那么再上图中BossGroup线程池中的NioEventLoop是怎么轮询得到的客户端的连接呢?就是NioEventLoop线程会去轮询Selector选择器中的Channel通道,从第一个到最后一个,然后再从第一个到最后一个,反复的进行,比方说现在第二个客户端连接到了服务器,那么这个客户端就会给Selector选择器发送一个OP_ACCEPT事件,当NioEventLoop轮询我们的Selector选择器的时候,当轮询到第二个Channel的时候,它就会接收到一个OP_ACCEPT事件,然后BossGroup线程池就会把一个客户端请求任务对应的套接字NioServerSocketChannel注册给WorkerGroup线程池。

注意Netty肯定是线程安全的,因为一个NioEventLoop在一个客户端任务没有处理完之前,它不会去处理其他客户端的任务。也就是每个客户端在它的生命周期内都只会与一个NioEventLoop线程连接,所以不会有其他的线程去分享客户端的共享资源,所以使用Netty框架处理客户端请求的时候一定是线程安全的。比如说如果是在BossGroup线程池中,那么当NioEventLoop线程成功处理结束一个客户端连接之前,它不会去处理另外一个客户端连接,或者说也不会有其他的NioEventLoop线程过来处理当前的客户端连接;
如果是在WorkerGroup线程池中,那么当NioEventLoop线程处理结束一个客户端对应的IO事件,任务逻辑之前,这个NioEventLoop线程不会去处理其他客户端的任务或者IO事件,同时也不会有其他的NioEventLoop线程过来处理当前客户端的IO事件和任务逻辑。

接着说一下BossGroup和WorkerGroup
这两个其实都是EventLoopGroup,它们的本质是一个线程池。然后既然是线程池了,那么它们内部的线程是什么呢?是NioEventLoop。
对于BossGroup呢,主要是负责处理客户端连接的;而对于WorkerGroup呢,主要是负责处理客户端请求任务的;
对于BossGroup线程池呢,我们不用设置的太大,一般就设置一个线程就可以了,因为对于客户端连接处理是很快的,不像处理客户端请求任务那样需要很长的过程,处理客户端连接的时候可能也就一眨眼的功夫。
而对于WorkerGroup线程池,大小一般都是设置为cpu的核数加一,比如如果你到cpu是4核的,那么我们就把线程池大小设置为8,这样cpu的资源利用率、效率方面都非常的高。

然后说下ChannelOption.SO_KEEPALIVE
在描述ChannelOption.SO_KEEPALIVE之前,我们先来说一些扩展知识。

什么是文件描述符?以及操作系统会对文件描述符有什么限制?
文件描述符可以理解成是我们打开的计算机的文件的文件id。一个进程可能会打开好多个文件,所以一个进程拥有多个文件描述符,并且一个进程中的每个文件描述符都是唯一的。在进程内部会有个映射表格,是进程id和文件描述符对应的映射关系,这样当我们在进程的线程中需要读取某个文件的时候,都是会先去找对应的文件描述符,然后再去读取磁盘文件。
文件描述符你可以理解成是一个整型的数字,相当于一个索引,通过这个索引你可以找到计算机中的文件。

但是我们的操作系统为了保证其可以正常的运行,它会限制文件描述符的数量,比如说当我们的系统中打开了1000个文件之后,操作系统就不允许再打开新的文件了,也就不再允许新的文件描述符创建了。这样可以保证操作系统的性能。

是不是只要客户端和服务端两端之间建立了tcp连接,这两端就可以传输数据?
不是的。原因如下:

  • 第一个原因是,tcp连接要求双端都正常,有一端不正常就不能传输数据,即便你们已经建立了tcp连接。比如客户端和服务端建立了tcp连接之后,客户端想要给服务端传输数据,正常情况下客户端给服务端发送请求之后服务端会响应,但是如果由于服务端网络问题或者是服务端运行错误问题,导致服务端不能响应客户端,那么这个时候就相当于虽然客户端和服务端两者之间存在tcp连接,但是二者的tcp连接并不能传输数据。
  • 第二个原因是,操作系统性能限制。比如说操作系统最多允许出现1000个文件描述符,当前系统中已经有了1000个了。此时客户端和服务端之间有个tcp连接,然后此时客户端想要读取一个新的文件的内容,所以客户端就给服务端发送一个请求,但是由于操作系统中此时达到了最大文件描述符数量,所以此时服务器就不能再给客户端响应了。那么这种情况下也相当于客户端和服务端存在客户端连接,但是二者不能传输数据。

什么叫做僵尸连接?
僵尸连接是指,明明客户端和服务器端存在tcp连接,但是这个tcp连接却不能让二者传输数据,而且这个tcp连接还会占用内存的资源。为什么会产生僵尸连接呢?原因就是上面描述的点。

僵尸连接的危害?

  • 首先呢,僵尸连接会占用内存资源。比如说现在客户端和服务端之间存在tcp连接,但是呢这个tcp连接不能够传输数据了,但是这个tcp连接之前是用过好多次的,也牵涉到了很多的文件描述符,之前用这个tcp连接服务端打开了很多磁盘文件,而这些磁盘文件在这个tcp连接的操作中都会加载到相关的内存中。因此呢,当前的这个僵尸连接就会占用很多内存资源,必须使用一定的策略断开,去释放内存资源。
  • 僵尸连接会伤害玩家的体验感。比如说举个例子,在在线购物程序中,假如用户和服务器之间的tcp是个僵尸连接。那么可能会出现一种情况,就是用户在商城里面选了好多的商品,把这些商品都加入到了购物车中,但是最后点击付钱的时候,客户端会通过tcp连接往服务端传输数据,就是把购物车信息会传输给客户端,但是这个时候因为用户和服务器之间的连接是僵尸连接,因此用户就不能正常付钱,可能用户会被强制退出然后重新登录重新选择商品,那这就比较影响用户体验感了。

现在可以说下ChannelOption.SO_KEEPALIVE的作用了
它的作用主要是检测僵尸连接,并且主动断开僵尸连接的tcp连接的。
如果把ChannelOption.SO_KEEPALIVE设置为true,那么当客户端与服务器端的tcp连接处于不活跃状态的时候,比如说两个小时没有进行通讯,那么客户端tcp会采取一定的措施去检测一下当前的tcp连接是否失效,如果失效了就断开相应的tcp连接。具体是使用什么措施呢?就是TCP会自动TCP会自动发送一个活动探测数据报文来测试连接的状态,其实就可以理解成给服务端发送了一个请求,如果在11分钟内服务端仍然没有响应,那么当前的客户端和服务端之间的tcp连接就会被当成是一个僵尸连接。然后tcp会通过四次挥手主动把这个tcp连接断开。
这样就可以释放掉tcp连接占用的内存资源了,比如这个tcp连接之前可能往内存中加载了很多的文件资源,当这个tcp连接断开之后,这些内存中的文件资源就可以被释放了。

如果把ChannelOption.SO_KEEPALIVE设置为fasle的话,那么tcp就不会主动的检测并断开僵尸连接了。

那么问题来了,既然把ChannelOption.SO_KEEPALIVE设置成true可以主动的断开僵尸连接,那么我们一直设置成true不就行了,为什么它还可以被设置成fasle呢?因为在主动检测僵尸连接也是需要耗费资源的,但有一些场景我们服务器会自动的释放资源,比如每次执行代码的后面都有句释放资源的代码,这种情况即便有僵尸连接也不会占用内存资源,所以这种情况把ChannelOption.SO_KEEPALIVE设置为false就可以了。

客户端和服务器端之间的tcp连接是一旦断开,客户端对应的用户就会自动退出吗?
也不一定,这个主要是看你的应用程序是怎么处理的。
有的应用程序在tcp连接断开后,会自动的尝试新建一个tcp连接;有的应用程序会提示网络异常请检查网络;有的应用程序可能会让用户退出重新登录。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Mr-X~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值