前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >惊群效应

惊群效应

原创
作者头像
mariolu
发布2018-09-18 02:07:40
3.2K0
发布2018-09-18 02:07:40
举报

一、服务器网络模型和惊群

传统的服务器使用“listen-accept-创建通信socket”完成客户端的一次请求服务。在高并发服务模型中,服务器创建很多进程-单线程(比如apache mpm)或者n进程:m线程比例创建服务线程(比如nginx event)。机器上运行着不等数量的服务进程或线程。这些进程监听着同一个socket。这个socket是和客户端通信的唯一地址。服务器父子进程或者多线程模型都accept该socket,有几率同时调用accept。当一个请求进来,accept同时唤醒等待socket的多个进程,但是只有一个进程能accept到新的socket,其他进程accept不到任何东西,只好继续回到accept流程。这就是惊群效应。如果使用的是select/epoll+accept,则把惊群提前到了select/epoll这一步,多个进程只有一个进程能acxept到连接,因为是非阻塞socket,其他进程返回EAGAIN。

二、accept惊群的解决

所有监听同一个socket的进程在内核中中都会被放在这个socket的wait queue中。当一个tcp socket有IO事件变化,都会产生一个wake_up_interruptible()。该系统调用会唤醒wait queue的所有进程。所以修复linux内核的办法是只唤醒一个进程,比如说替换wake函数为wake_one_interruptoble()。

2.1、改进版本accept+reuse port

没有开启reuse选项的socket只有一个wait queue,假设在开启了socket REUSE_PORT选项,内核中为每个进程分配了单独的accept wait queue,每次唤醒wait queue只唤醒有请求的进程。协议栈将socket请求均匀分配给每个accept wait queue。reuse部分解决了惊群问题,但是本身存在一些缺点或bug,比如REUSE实现是根据客户端ip端口实现哈希,对同一个客户请求哈希到同一个服务器进程,但是没有实现一致性哈希。在进程数量扩展新的进程,由于缺少一致性哈希,当listen socket的数目发生变化(比如新服务上线、已存在服务终止)的时候,根据SO_REUSEPORT的路由算法,在客户端和服务端正在进行三次握手的阶段,最终的ACK可能不能正确送达到对应的socket,导致客户端连接发生Connection Reset,所以有些请求会握手失败。

三、select/epoll模型

在一个高并发的服务器模型中,每秒accept的连接数很多。accept成为一个占用cpu很高的系统调用。考虑使用多进程来accept。select由于可扩展性能比如epoll,select遍历所有socket,select对每次操作都是要循环遍历所有的fd,所以在高并发场景下,select性能差。在高并发场景epoll使用场景更多。

3.1、epoll的EPOLL_EXCLUSIVE选项

liunx 4.5内核在epoll已经新增了EPOLL_EXCLUSIVE选项,在多个进程同时监听同一个socket,只有一个被唤醒。

四、应用层解决

同一时间只让一个进程accept/select/epoll一个监听端口。这是应用层解决惊群的办法,伪代码如下

semop(...); // lock

epoll_wait(...);

accept(...);

semop(...); // unlock

... // manage the request

多进程使用sysv实现semop,多线程则使用mutex。比如说mpm模式下的httpd,nginx都是这种实现办法。但是这种办法sysv是个固定的内存大小。比如在终端敲入ipcs。ipcs是机器共享固定大小空间。所以一旦有很多进程分配忘了手动释放,有内存泄漏风险。

4.1、httpd

for (;;) {

accept_mutex_on ();

for (;;) {

fd_set accept_fds;

...

rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);

...

for (i = first_socket; i <= last_socket; ++i) {

if (FD_ISSET (i, &accept_fds)) {

new_connection = accept (i, NULL, NULL);

}

accept_mutex_off ();

process the new_connection;

}

}

4.2、nginx

void ngx_process_events_and_timers(ngx_cycle_t *cycle) { ...

if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {

return;

}

...

if (ngx_accept_mutex_held) {

flags |= NGX_POST_EVENTS;

}

...

(void) ngx_process_events(cycle, timer, flags);

ngx_event_process_posted(cycle, &ngx_posted_accept_events);

if (ngx_accept_mutex_held) {

ngx_shmtx_unlock(&ngx_accept_mutex);

}

ngx_event_process_posted(cycle, &ngx_posted_events);

}

4.3、少量进程监听同一个socket

当然在低负债的环境下,也可以分配少量的进程,即使有惊群,影响也是不大的。

五、tornado/golang/其他

5.1、tornado

tornado使用IOLoop模块,在Python3,IOLoop是个asyncio event循环。Python 2则使用了epoll (Linux) or kqueue (BSD and Mac OS X) 否则选用select()。所以python tornado在面对惊群问题其实是没有解决的。所以就是系统不解决惊群问题丢给应用层解决,应用层不解决丢给用户解决。笔者在tornado模拟业务源站行为,曾经开启了几百个进程。模拟行为很纯粹,就是根据X-Flux头的指定的大小,返回给用户相应大小的2xx响应。该程序不涉及磁盘io,不涉及内存大量拷贝操作,本应是cpu运算型,但是发现客户端在压测tornado,并发度1w左右,却服务端cpu跑满,并且连接超时的概率竟然有百分之四五十。使用python分析程序发现epoll wait函数占用了40%左右的cpu时间。很显然就是遇到了惊群响应。后面用golang重新实现了服务器,就没有了惊群。

5.2、golang

为啥golang就没有惊群响应呢?笔者查看了一个关键包netFD的accept实现。

func (fd *netFD) accept() (netfd *netFD, err error) {

//在这里序列化accept,避免惊群效应

if err := fd.readLock(); err != nil {

return nil, er

}

defer fd.readUnlock()

......

for {

s, rsa, err = accept(fd.sysfd)

if err != nil {

if err == syscall.EAGAIN {

//tcp还没三次握手成功,阻塞读直到成功,同时调度控制权下放给gorontine

if err = fd.pd.WaitRead(); err == nil {

continue

}

} else if err == syscall.ECONNABORTED {

//被对端关闭

continue

}

}

break

}

netfd, err = newFD(s, fd.family, fd.sotype, fd.net)

......

//fd添加到epoll队列中

err = netfd.init()

......

lsa, _ := syscall.Getsockname(netfd.sysfd)

netfd.setAddr(netfd.addrFunc()(lsa), netfd.addrFunc()(rsa))

return netfd, nil

}

5.3 lighttpd及其他

linghttpd使用场景建议只有一个worker,多个worker会有些功能兼容上的问题,所以lighttpd官方其实也没有解决惊群问题。其他服务器tomcat、nodejs等等因为其实在高并发上会搭配apache或者nginx协同使用,所以研究意义不大。

六、总结

管中窥豹、惊群问题说大不大,但是如果碰到,可能是限制高并发性能的重要一个瓶颈,在探索惊群问题解决上,对各个服务器模型的分析以及内核层调研中整理了这些想法,希望对大家有所帮助。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、服务器网络模型和惊群
  • 二、accept惊群的解决
    • 2.1、改进版本accept+reuse port
    • 三、select/epoll模型
      • 3.1、epoll的EPOLL_EXCLUSIVE选项
      • 四、应用层解决
        • 4.1、httpd
          • 4.2、nginx
            • }
              • 4.3、少量进程监听同一个socket
              • 五、tornado/golang/其他
                • 5.1、tornado
                  • 5.2、golang
                    • 5.3 lighttpd及其他
                    • 六、总结
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档