卡路里计算器中的“惊群”问题? 作为运维人,你竟然会不知道Linux-达内Linux云计算培训

中的“惊群”问题? 作为运维人,你竟然会不知道Linux-达内Linux云计算培训


Tips:
达内Linux云计算本月免费课程开讲时间7月24日,点击文末“阅读原文”快速抢!
上一篇文章:7月福利来喽!Linux云计算免费训练营课程火热抢报中,手慢无~
推荐阅读:超强战报来袭 | 达内Linux云计算学员高薪入职世界排名第二的“天河二号”陈锡联简历 !

1
前言
我从事Linux系统下网络开发将近4年了,经常还是遇到一些问题,只是知其然而不知其所以然,有时候和其他人交流长相依简谱,搞得非常尴尬。如今计算机都是多核了,杨肸子 网络编程框架也逐步丰富多了,我所知道的有多进程、多线程、异步事件驱动常用的三种模型。最经典的模型就是Nginx中所用的Master-Worker多进程异步驱动模型。今天和大家一起讨论一下网络开发中遇到的“惊群”现象。之前只是听说过这个现象,网上查资料也了解了基本概念,在实际的工作中还真没有遇到过。今天周末,结合自己的理解和网上的资料,彻底将“惊群”弄明白。
需要弄清楚如下几个问题:
(1)什么是“惊群”,会产生什么问题?
(2)“惊群”的现象怎么用代码模拟出来?
(3)如何处理“惊群”问题,处理“惊群”后的现象又是怎么样呢?
2
何为惊群
如今网络编程中经常用到多进程或多线程模型,大概的思路是父进程创建socket,bind、listen后,通过fork创建多个子进程,每个子进程继承了父进程的socket,调用accpet开始监听等待网络连接。这个时候有多个进程同时等待网络的连接事件,当这个事件发生时,这些进程被同时唤醒,就是“惊群”。这样会导致什么问题呢?我们知道进程被唤醒,需要进行内核重新调度,这样每个进程同时去响应这一个事件,而最终只有一个进程能处理事件成功,其他的进程在处理该事件失败后重新休眠或其他。网络模型如下图所示:

简而言之,惊群现象(thundering herd)就是当多个进程和线程在同时阻塞等待同一个事件时,如果这个事件发生,会唤醒所有的进程,但最终只可能有一个进程/线程对该事件进行处理,其他进程/线程会在失败后重新休眠,这种性能浪费就是惊群。
3
编码模拟“惊群”现象
我们已经知道了“惊群”是怎么回事,那么就按照上面的图编码实现看一下效果。我尝试使用多进程模型,创建一个父进程绑定一个端口监听socket,然后fork出多个子进程,子进程们开始循环处理(比如accept)这个socket。测试代码如下所示:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <sys/socket.h>
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7 #include <assert.h>
8 #include <sys/wait.h>
9 #include <string.h>
10 #include <errno.h>
11
12 #define IP "127.0.0.1"
13 #define PORT 8888
14 #define WORKER 4
15
16 int worker(int listenfd, int i)
17 {
18while (1) {
19 printf("I am worker %d, begin to accept connection. ", i);
20 struct sockaddr_in client_addr;
21 socklen_t client_addrlen = sizeof( client_addr );
22 int connfd = accept( listenfd, ( struct sockaddr* )&client_addr, &client_addrlen );
23 if (connfd != -1) {
24 printf("worker %d accept a connection success. ", i);
25 printf("ip :%s ",inet_ntoa(client_addr.sin_addr));
26 printf("port: %d ",client_addr.sin_port);
27 } else {
28 printf("worker %d accept a connection failed,error:%s", i, strerror(errno));
close(connfd);
29 }
30}
31return 0;
32 }
33
34 int main()
35 {
36int i = 0;
37struct sockaddr_in address;
38bzero(&address, sizeof(address));
39address.sin_family = AF_INET;
40inet_pton( AF_INET空战极限 , IP俞慧文 , &address.sin_addr);
41address.sin_port = htons(PORT);
42int listenfd = socket(PF_INET, SOCK_STREAM, 0);
43assert(listenfd >= 0);
44
45int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
46assert(ret != -1);
47
48ret = listen(listenfd, 5);
49assert(ret != -1);
50
51for (i = 0; i < WORKER; i++) {
52 printf("Create worker %d ", i+1);
53 pid_t pid = fork();
54
55 if (pid == 0) {
56 worker(listenfd, i);
57 }
58
59 if (pid < 0) {
60 printf("fork error");
61 }
62}
63
64
65int status;
66wait(&status);
67return 0;
68 }
编译执行呼儿嘿呦 ,在本机上使用telnet 127.0.0.1 8888测试,结果如下所示:

按照“惊群"现象,期望结果应该是4个子进程都会accpet到请求,其中只有一个成功,卡路里计算器 另外三个失败的情况。而实际的结果显示,父进程开始创建4个子进程,每个子进程开始等待accept连接。当telnet连接来的时候,只有worker2 子进程accpet到请求黑色神幻 ,而其他的三个进程并没有接收到请求。
这是什么原因呢?难道惊群现象是假的吗?于是赶紧google查一下,惊群到底是怎么出现的。
其实在Linux2.6版本以后,内核内核已经解决了accept()函数的“惊群”问题,大概的处理方式就是,当内核接收到一个客户连接后,只会唤醒等待队列上的第一个进程或线程。所以,如果服务器采用accept阻塞调用方式,在最新的Linux系统上,已经没有“惊群”的问题了。
但是,对于实际工程中常见的服务器程序,大都使用select、poll或epoll机制,此时,服务器不是阻塞在accept,而是阻塞在select、poll或epoll_wait,这种情况下的“惊群”仍然需要考虑。接下来以epoll为例分析:
使用epoll非阻塞实现代码如下所示:
1 #include <sys/types.h>
2 #include <sys/socket.h>
3 #include <sys/epoll.h>
4 #include <netdb.h>
5 #include <string.h>
6 #include <stdio.h>
7 #include <unistd.h>
8 #include <fcntl.h>
9 #include <stdlib.h>
10 #include <errno.h>
11 #include <sys/wait.h>
12 #include <unistd.h>
13
14 #define IP "127.0.0.1"
15 #define PORT 8888
16 #define PROCESS_NUM 4
17 #define MAXEVENTS 64
18
19 static int create_and_bind ()
20 {
21int fd = socket(PF_INET, SOCK_STREAM, 0);
22struct sockaddr_in serveraddr;
23serveraddr.sin_family = AF_INET;
24inet_pton( AF_INET, IP, &serveraddr.sin_addr);
25serveraddr.sin_port = htons(PORT);
26bind(fd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
27return fd;
28 }
29
30 static int make_socket_non_blocking (int sfd)
31 {
32int flags, s;
33flags = fcntl (sfd, F_GETFL方想新书 , 0);
34if (flags == -1) {
35 perror ("fcntl");
36 return -1;
37}
38flags |= O_NONBLOCK;
39s = fcntl (sfd, F_SETFL, flags);
40if (s == -1) {
41 perror ("fcntl");
42 return -1;
43}
44return 0;
45 }
46
47 void worker(int sfd, int efd解伟苓, struct epoll_event *events, int k) {
48
49while (1) {
50 int n, i;
51 n = epoll_wait(efd, events, MAXEVENTS, -1);
52 printf("worker %d return from epoll_wait! ", k);
53 for (i = 0; i < n; i++) {
54 if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events &EPOLLIN))) {
55
56fprintf (stderr, "epoll error ");
57close (events[i].data.fd);
58continue;
59 } else if (sfd == events[i].data.fd) {
60
61struct sockaddr in_addr;
62socklen_t in_len;
63int infd;
64char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
65in_len = sizeof in_addr;
66infd = accept(sfd, ∈_addr, ∈_len);
67if (infd == -1) {
68 printf("worker %d accept failed! "华商书院, k);
69 break;
70}
71printf("worker %d accept successed! ", k);
72
73close(infd);
74 }
75 }
76}
77 }
78
79 int main (int argc, char *argv[])
80 {
81int sfd, s;
82int efd;
83struct epoll_event event;
84struct epoll_event *events;
85sfd = create_and_bind();
86if (sfd == -1) {
87 abort ();
88}
89s = make_socket_non_blocking (sfd);
90if (s == -1) {
91 abort ();
92}
93s = listen(sfd, SOMAXCONN);
94if (s == -1) {
95 perror ("listen");
96 abort ();
97}
98efd = epoll_create(MAXEVENTS);
99if (efd == -1) {
100 perror("epoll_create");
101 abort();
102}
103event.data.fd = sfd;
104event.events = EPOLLIN;
105s = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
106if (s == -1) {
107 perror("epoll_ctl");
108 abort();
109}
110
111
112events = calloc(MAXEVENTS, sizeof event);
113int k;
114for(k = 0; k < PROCESS_NUM; k++) {
115 printf("Create worker %d ", k+1);
116 int pid = fork();
117 if(pid == 0) {
118 worker(sfd, efd, events, k);
119 }
120}
121int status;
122wait(&status);
123free (events);
124close (sfd);
125return EXIT_SUCCESS;
126 }
父进程中创建套接字,并设置为非阻塞,开始listen。然后fork出4个子进程,在worker中调用epoll_wait开始accpet连接。使用telnet测试结果如下:

从结果看出,与上面是一样的,只有一个进程接收到连接,其他三个没有收到,说明没有发生惊群现象。这又是为什么呢?
在早期的Linux版本中,内核对于阻塞在epoll_wait的进程,也是采用全部唤醒的机制,所以存在和accept相似的“惊群”问题。新版本的的解决方案也是只会唤醒等待队列上的第一个进程或线程,所以,新版本Linux部分的解决了epoll的“惊群”问题。所谓部分的解决,意思就是:对于部分特殊场景,使用epoll机制,已经不存在“惊群”的问题了,但是对于大多数场景,epoll机制仍然存在“惊群”。
epoll存在惊群的场景如下:在worker保持工作的状态下艾曼纽贝阿,都会被唤醒,例如在epoll_wait后调用sleep一次。改写woker函数如下:
void worker(int sfd, int efd, struct epoll_event *events, int k) {

while (1) {
int n, i;
n = epoll_wait(efd, events, MAXEVENTS, -1);

sleep(2);
printf("worker %d return from epoll_wait! ", k);
for (i = 0; i < n; i++) {
if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events &EPOLLIN))) {

fprintf (stderr, "epoll error ");
close (events[i].data.fd);
continue;
} else if (sfd == events[i].data.fd) {

struct sockaddr in_addr;
socklen_t in_len;
int infd;
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
in_len = sizeof in_addr;
infd = accept(sfd, ∈_addr, ∈_len);
if (infd == -1) {
printf("worker %d accept failed,error:%s ", k, strerror(errno));
break;
}
printf("worker %d accept successed! ", k);

close(infd);
}
}
}
}
测试结果如下所示:

终于看到惊群现象的出现了金牌师姐。
4
解决惊群问题
Nginx中使用mutex互斥锁解决这个问题,具体措施有使用全局互斥锁,每个子进程在epoll_wait()之前先去申请锁,申请到则继续处理,获取不到则等待,并设置了一个负载均衡的算法(当某一个子进程的任务量达到总设置量的7/8时,则不会再尝试去申请锁)来均衡各个进程的任务量。后面深入学习一下Nginx的惊群处理过程。
作者:Anker
来源:http://www.cnblogs.com/Anker/p/7071849.html
为了能够让更多零基础的小伙伴可以有条理、有方向的从0到1开始学习内容Linux基础知识、命令规范等技术,了解运维就业方向前景,掌握最新运维发展“钱景”动态,达内特推出的Linux云计算免费训练营,5天真课免费试听,火热抢报中,速速来约~~~
本期Linux云计算免费训练营课程7月24日火热开讲,点击文末“阅读原文”或者后台回复:姓名+电话+城市即可报名!更多运维技术干货,Linux云计算快速上手技巧,通通免费分享,不可错过~

咨询联系达妹QQ:3535503962。
*声明:推送内容与图片均来源于网络,部分内容会有所改动,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜陈平安。
为什么要选择达内?
1、上市公司,品牌保障:
达内教育集团(以下简称“达内”)成立于2002年9月。2014年4月3日成功在美国纳斯达克上市,成为中心赴美上市的职业教育公司,也是引领行业的职业教育公司孟兰节 。
2、发展规模大,区域涵盖广,可选择任意中心,就近听课:
145家中心,覆盖42个城市,6000名员工,年培训量已达10万人次,凭借雄厚的技术研发实力,过硬的教学质量、成熟的就业服务团队,为学员提供强大的职业竞争力,在用人企业中树立良好的口碑。
3、根据学员能力分级培训,因材施教:
历时一年, 耗资千万,2016重磅推出因材施教, 分级培优创新教学模式!达内根据学习不同课程学员的特点, 通过基础阶段的课程学习后进行分级考试或分阶段考试,根据学生的学习能力因材施教、分级培优进行差异化教学,使同一水平的学生能同步实现逐级提高,让同一基础的学生能够紧跟进度,保障所有的学员都能达到更好的学习效果。
4、就业范围广,就业速度快:
7万家招聘雇主合作企业在达内招聘学员,提供更多招聘机会让学员就业。达内已与70000家一流公司建立了人才合作联盟,达内优秀学员已入职微软、百度、亚信、甲骨文、惠普等知名企业,学员入职遍布世界500强、国内500强、中国软件百强企业、上市公司及国有企业、国内新兴名企等。
- END -
每年10万人选择达内教育
选择的人多,自然是好培训

长 按 二 维 码, 一 键 关 注
更多精彩文章1毕业1天全部就业,最高薪资达12000元宜宾正健医院 ,平均薪资8470元!2达内Linux云计算学员就业率34.8%,最高薪资22000元3千万千万不可运行的 Linux 命令4对Linux新手非常有用的20个命令5为什么程序员千万不要重写代码?
点击“阅读原文”抢报Linux云计算免费课程!
文章归档