Random Thoughts
Recent content on Random Thoughts
马上订阅 Random Thoughts RSS 更新: https://blog.joway.io/index.xml
RPC 漫谈: 连接问题
什么是连接
在物理世界并不存在连接这么一说,数据转换为光/电信号后,从一台机器发往另一台机器,中间设备通过信号解析出目的信息来确定如何转发包。我们日常所谓的「连接」纯粹是一个人为抽象的概念,目的是将传输进来的无状态数据通过某个固定字段作为标识,分类为不同有状态会话,从而方便在传输层去实现一些依赖状态的事情。
以 TCP 为例,一开始的三次握手用来在双方确认一个初始序列号(Initial Sequence Numbers,ISN),这个 ISN 标志了一个 TCP 会话,并且这个会话有一个独占的五元组(源 IP 地址,源端口,目的 IP 地址,目的端口,传输层协议)。在物理意义上,一个 TCP 会话等价于通往某一个服务器的相对固定路线(即固定的中间物理设备集合),正是由于这样,我们去针对每个 TCP 会话进行有状态的拥塞控制等操作才是有意义的。
连接的开销
我们常常听到运维会说某台机器连接太多所以出现了服务抖动,大多数时候我们会接受这个说法然后去尝试降低连接数。然而我们很少去思考一个问题,在一个服务连接数过多的时候,机器上的 CPU,内存,网卡往往都有大量的空余资源,为什么还会抖动?维护一个连接的具体开销是哪些?
内存开销:
TCP 协议栈一般由操作系统实现,因为连接是有状态对,所以操作系统需要在内存中保存这个会话信息,这个内存开销每个连接大概 4kb 不到。
文件描述符占用:
在 Linux 视角中,每个连接都是一个文件,都会占用一个文件描述符。文件描述符所占用的内存已经计算在上面的内存开销中,但操作系统为了保护其自身的稳定性和安全性,会限制整个系统内以及每个进程中可被同时打开的最大文件描述符数:
# 机器配置: Linux 1 核 1 GB
$ cat /proc/sys/fs/file-max
97292
$ ulimit -n
1024
上面的设置表示整个操作系统最多能同时打开 97292 个文件,每个进程最多同时打开 1024 个文件。
严格来说文件描述符根本算不上是一个资源,真正的资源是内存。如果你有明确的需要,完全可以通过设置一个极大值,让所有应用绕开这个限制。
线程开销:
有一些较老的 Server 实现采用的还是为每个连接独占(新建或从连接池中获取)一个线程提供服务的方式,对于这类服务来说,除了连接本身占用的外,还有线程的固定内存开销:
# 机器配置: Linux 1 核 1 GB
# 操作系统最大线程数
$ cat /proc/sys/kernel/threads-max
7619
# 操作系统单进程最大线程数,undef 表示未限制
$ cat /usr/include/bits/local_lim.h
/* We have no predefined limit on the number of threads. */
#undef PTHREAD_THREADS_MAX
# 单个线程栈默认大小,单位为 KB
$ ulimit -s
8192
在上面这台机器里,允许创建的线程数一方面受操作系统自身设定值限制,一方面也受内存大小限制。由于 1024MB / 8MB = 128 > 7619 , 所以这台机器中能够创建的最大线程数为 128。如果 Server 采用一个线程一个连接,那么这时 Server 同时最多也只能够为 128 个连接提供服务。