Netty-源码学习(1)-BACKLOG参数

1,074 阅读6分钟

从入职开始,就不断从各种大佬口中听说阅读源码的好处,其中Netty是他们推荐最多的开源项目之一。奈何本人拖延症晚期,加上Netty源码庞杂,不知从何下手,因此一直没法下定决心开始学习。正好前些天在掘金摸鱼提升自我的过程中,看到了一个Netty源码共读计划,这个计划里将阅读Netty源码的过程拆解成一个个任务,通过完成这些任务,能够一步步的深入了解Netty中的设计思路和编码技巧。这不正中我的下怀?既能学习又能冲个奖,何乐而不为~。撸起袖子说干就干,所以就有了Netty源码学习的第一篇文章:Netty中的重要参数-BACKLOG。

「我正在参与掘金会员专属活动-源码共读第一期,点击参与

1、Netty中BACKLOG参数的设置方式

在创建netty服务时,我们可以使用ServerBootstrap类的optional方法为服务设置SO_BACKLOG参数。

以debug模式启动服务,跳过源码中仅进行参数传递的部分,我们在sun.nio.ch.ServerSocketChannelImpl#bind中发现了使用BACKLOG参数的地方。在代码的228行,该参数最终被Net.listen这个本地方法所使用。

2、BACKLOG参数的作用

那这个参数有什么作用呢?虽然没法看到Net.listen方法的具体实现,但是通过查阅资料得知,该方法调用了Linux系统的内核函数listen,设置了Linux系统的BACKLOG值,在Linux的帮助文档中,是这么描述BACKLOG参数的

The backlog parameter defines the maximum length for the queue of pending connections. If a connection request arrives with the queue full, the client may receive an error with an indication of ECONNREFUSED. Alternatively, if the underlying protocol supports retransmission, the request may be ignored so that retries may succeed.

简单翻译下这句话,BACKLOG参数定义了阻塞队列的最大长度,当有新的连接请求到达时,若阻塞队列的容量达到上限,服务端将无法响应这个请求。并根据底层协议是否支持重传,服务端将返回ECONNREFUSED的错误提示或是直接忽略该请求。

仅看这段话可能有些云里雾里,阻塞队列里存放的是啥?这个队列又是在什么情况下被使用?为了回答这个问题,我们需要先回顾一下TCP建立连接的全过程。众所周知,TCP通过三次握手建立服务端与客户端之间的连接。

  • 第一次握手(客户端->服务端):客户端Socket调用connect()函数,发送SYN标志位为1的TCP包,表明客户端希望建立一个TCP连接。发送成功后,客户端将处于SYN_SENT状态。
  • 第二次握手(服务端->客户端):当服务端的ServerSocket收到SYN包后,向客户端发送SYN+ACK的确认报文,此时服务端的状态从LISTEN转为SYN_RCVD
  • 第三次握手(客户端->服务端):客户端收到服务端传来的SYN+ACK的报文后,向服务端发送ACK确认报文,自身状态从SYN_SENT转为ESTABLISHED。服务端收到ACK确认报文后,自身状态从SYN_RCVD转为ESTABLISHED 至此一个TCP连接就建立了起来,客户端通过调用send()函数发送数据,服务端通过调用write()函数写入响应数据。

在TCP三次握手发生之前,服务端进程首先调用bind()函数,设置进程绑定的端口;接着,调用listen()函数,将Socket状态从CLOSED变为LISTEN。此时系统内核会给该进程创建两个队列,分别为

  • 半连接队列(SYN队列)
  • 全连接队列(ACCEPT队列)

半连接队列用于存放第二次握手中,已经向客户端发送了SYN+ACK报文,正等待收到客户端ACK报文的连接。此时的连接被称为半连接,还无法被服务端进程正常响应。当接受到客户端的ACK报文后,服务端会将对应的半连接放入全连接队列中。全连接队列用于存放三次握手成功后的连接。服务端进程只需要调用accept()函数,就能从全连接队列中取出一个连接进行消费并响应。

为了避免单个端口中传入大量TCP连接请求,占据系统资源,导致绑定了其他端口的进程无法正常响应。这两个队列都被设为有界队列,当队列被占满时,端口将不再响应连接请求。

而前文提到的BACKLOG参数正是被用于设置进程绑定socket的全连接队列大小。由此不难发现,BACKLOG参数在一定程度上将决定了进程响应连接请求的并发度。

但细心的读者一定发现,我在上面的描述中,用的是一定程度上。这是因为全连接队列的最大长度还会受到Linux系统的全局内核参数net.core.somaxconn值的影响。somaxconn规定了系统端口所能设置的全连接队列的最大长度。因此准确来说,某一端口的全连接队列长度 = min(somaxconn,BACKLOG)。

到此为止,已经基本厘清了BACKLOG参数的作用,Netty通过该参数设置服务端进程能够同时响应的TCP连接请求数量。接下来需要回答的问题是,该怎么设置合适的BACKLOG参数大小呢?

3、BACKLOG参数的设置原则

前文中提到,端口全连接队列的长度取决于somaxconn和BACKLOG参数。在Linux系统中,somaxconn的参数默认值为128,这个数量面对大型网络应用的高并发使用场景属实是小了点,因此绝大多数的服务器会调整该值,通常设置为1024或2048甚至更大。这样就能够有更多的空间,在代码中对进程绑定的单个端口的BACKLOG参数大小进行调整。

经过上面的讨论,我们可以总结出BACKLOG参数的设置原则

  1. 在面对高并发场景下时,应当适当增大BACKLOG的值,使端口可以同时建立更多的TCP请求
  2. 当服务的QPS不高,且服务端进程响应请求的时间较长时,可以适当减小BACKLOG的值,避免大量连接占据系统内存资源。
  3. 在设置BACKLOG参数时,还需要记得修改somaxconn参数,若设置的BACKLOG参数超过了系统somaxconn参数的值则无法生效。

这是我的第一篇Netty源码学习文章,后续会跟着任务目标,继续深入了解Netty,敬请期待~~。