惊群的解决办法

(1)、加锁
(2)、SO_REUSEPORT

Linux内核的3.9版本带来了SO_REUSEPORT特性,该特性支持多个进程或者线程绑定到同一端口,提高服务器程序的性能,允许多个套接字bind()以及listen()同一个TCP或UDP端口,并且在内核层面实现负载均衡。

在未开启SO_REUSEPORT的时候,由一个监听socket将新接收的连接请求交给各个工作者处理,看图示:


image.png

在使用SO_REUSEPORT后,多个进程可以同时监听同一个IP:端口,然后由内核决定将新链接发送给哪个进程,显然会降低每个工人接收新链接时锁竞争


image.png

下面让我们好好比较一下多进程(线程)服务器编程传统方法和使用SO_REUSEPORT的区别
运行在Linux系统上的网络应用程序,为了利用多核的优势,一般使用以下典型的多进程(多线程)服务器模型:

1.单线程listener/accept,多个工作线程接受任务分发,虽然CPU工作负载不再成为问题,但是仍然存在问题:

   (1)、单线程listener(图一),在处理高速率海量连接的时候,一样会成为瓶颈

    (2)、cpu缓存行丢失套接字结构现象严重。

2.所有工作线程都accept()在同一个服务器套接字上呢?一样存在问题:

    (1)、多线程访问server socket锁竞争严重。

    (2)、高负载情况下,线程之间的处理不均衡,有时高达3:1。

    (3)、导致cpu缓存行跳跃(cache line bouncing)。

    (4)、在繁忙cpu上存在较大延迟。

上面两种方法共同点就是很难做到cpu之间的负载均衡,随着核数的提升,性能并没有提升。甚至服务器的吞吐量CPS(Connection Per Second)会随着核数的增加呈下降趋势。

下面我们就来看看SO_REUSEPORT解决了什么问题:

    (1)、允许多个套接字bind()/listen()同一个tcp/udp端口。每一个线程拥有自己的服务器套接字,在服务器套接字上没有锁的竞争。

    (2)、内核层面实现负载均衡

    (3)、安全层面,监听同一个端口的套接字只能位于同一个用户下面。

    (4)、处理新建连接时,查找listener的时候,能够支持在监听相同IP和端口的多个sock之间均衡选择。

当一个连接到来的时候,系统到底是怎么决定那个套接字来处理它?

对于不同内核,存在两种模式,这两种模式并不共存,一种叫做热备份模式,另一种叫做负载均衡模式,3.9内核以后,全部改为负载均衡模式。

热备份模式:一般而言,会将所有的reuseport同一个IP地址/端口的套接字挂在一个链表上,取第一个即可,工作的只有一个,其他的作为备份存在,如果该套接字挂了,它会被从链表删除,然后第二个便会成为第一个。
负载均衡模式:和热备份模式一样,所有reuseport同一个IP地址/端口的套接字会挂在一个链表上,你也可以认为是一个数组,这样会更加方便,当有连接到来时,用数据包的源IP/源端口作为一个HASH函数的输入,将结果对reuseport套接字数量取模,得到一个索引,该索引指示的数组位置对应的套接字便是工作套接字。这样就可以达到负载均衡的目的,从而降低某个服务的压力。

你可能感兴趣的:(惊群的解决办法)