`
bachmozart
  • 浏览: 111670 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

测试Lighttpd accept的惊群现象

阅读更多
lighttpd里面采用的是prefork的模型,在fork进程之前就已经创建好了listen socket
那么fork了进程池之后,所有进程都有一份自己独立的listen socket fd,

但实际上这个独立的fd 对应的确是一个文件表项,即实际上任然是一个共享的文件描述符

在阻塞模型中,各进程分别通过accept阻塞,等待连接到达,当一个连接到达时,所有的进程都会被唤醒,但只有其中一个进程可以成功accept该连接,其余的则继续投入睡眠,这就是所谓的惊群现象

lighttpd使用的是非阻塞IO复用模型,测试一下是否会有惊群现象呢?

先把结论给出:

1.比如有20个进程注册了listen socket的请求连接事件,当一个连接到达确实会有多个进程 被通知有事件要处理(但不是全部,大约只有5,6个进程)
2.被唤醒的这几个进程会调用accept函数,其中只有一个成功返回连接fd,其余进程均返回EAGAIN或者 EWOULDBLOCK错误(因为是非阻塞的)

测试方法,自己写了一个prefork进程 + epoll的非阻塞server,启动20个进程,client telnet,打印服务器日志

try to accept new connection,pid=29879
try to accept new connection,pid=29876
try to accept new connection,pid=29880

process 29879 accept connection

accept EAGAIN error pid=29876
try to accept new connection,pid=29875
accept EAGAIN error pid=29880
accept EAGAIN error pid=29875

四个进程被通知有事件处理,1个成功accept,3个返回EAGAIN


在lighttpd中,server当被通知有连接要处理时,server会通过循环执行
accept,直到返回错误,或者超过一个上限值

这样,当海量请求连接到达时,似乎惊群不会带来太多的性能损耗。


部分测试代码
base.h

enum conn_states {
    conn_listening,  /** the socket which listens for connections */
    conn_read,       /** reading in a command line */
    conn_write,      /** writing out a simple response */
    conn_nread,      /** reading in a fixed number of bytes */
    conn_swallow,    /** swallowing unnecessary bytes w/o storing */
    conn_closing,    /** closing this connection */
    conn_mwrite,     /** writing out many items sequentially */
};
typedef struct{
	int fd;	
 	int state;	
}conn;


server.c

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<errno.h>
#include<fcntl.h>
#include<netinet/in.h>
#include<sys/resource.h>
#include "event.h"
#include "base.h"

//forward declaration

static fdevents *ev;
static conn **conns;
static int freetotal;
static int freecurr;

static int create_listen_fd(char *addr,int port);
static int conn_init();
static conn *get_conn_from_freelist();
static int add_conn_to_freelist(conn *c);
conn *conn_new(int fd,int state);

static int conn_init(){
	freetotal=200;
	freecurr=0;
	conns=(conn **)malloc(freetotal * sizeof(*conns));
	if(!conns){
		return -1;
	}
	return 0;
}
static conn *get_conn_from_freelist(){
	conn *con;
	if(freecurr > 0){
		con=conns[--freecurr];
		conns[freecurr]=NULL;
		return con;
	}
	return NULL;
}
static int add_conn_to_freelist(conn *c){
	if(freecurr<freetotal){
		conns[freecurr++]=c;
		return 0;
	}else{
		conn **new_conns=(conn **)realloc(conns,sizeof(*new_conns)*2*freetotal);
		if(new_conns){
			freetotal*=2;
			conns=new_conns;
			conns[freecurr++]=c;
			return 0;
		}		
	}
	return -1;
}
conn *conn_new(int fd,int state){
	conn *c;
	c=get_conn_from_freelist();
	if(!c){
		c=(conn *)malloc(sizeof(*c));		
	}
	
	c->fd=fd;
	c->state=state;

	return c;
	
}
static int create_listen_fd(char *addr,int port){
	int fd,val,flags;
	struct sockaddr_in sockaddr;
	fd=socket(AF_INET,SOCK_STREAM,0);
	if(fd==-1){
		fprintf(stderr,"socket()\n");
		return -1;
	}
	val=1;
	if(setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val))<0){
		fprintf(stderr,"reuseaddr\n");
		return -1;
	}
	if((flags=fcntl(fd,F_GETFL,0)<0) || fcntl(fd,F_SETFL,flags | O_NONBLOCK) < 0){
		fprintf(stderr,"nonblocking\n");
		return -1;
	}

	bzero(&sockaddr,sizeof(sockaddr));
	sockaddr.sin_family=AF_INET;
	sockaddr.sin_port=htons(port);
	inet_pton(AF_INET,addr,&sockaddr.sin_addr);

	if(bind(fd,(struct sockaddr *)&sockaddr,sizeof(sockaddr))<0){
		fprintf(stderr,"bind error %s",strerror(errno));
		return -1;
	}
	if(listen(fd,2048)<0){
		fprintf(stderr,"listen %s",strerror(errno));
		return -1;
	}
	return fd;
}
void event_handler(int fd,void *ctx, int revents){
	struct sockaddr_in addr;
	socklen_t sock_len;
	int done=0,connfd;
	conn *c;
	c=(conn *)ctx;
	while(!done){
		switch(c->state){
			case conn_listening:
				printf("try to accept new connection,pid=%d\n",getpid());
				sock_len=sizeof(addr);
				connfd=accept(fd,(struct sockaddr *)&addr,&sock_len);
				if(connfd>0){
					printf("process %d accept connection\n",getpid());
					c = conn_new(connfd,conn_read);
					fdevent_register(ev,connfd,event_handler,c);
					fdevent_event_add(ev,connfd,FDEVENT_IN);
				}else{
						if(errno== EAGAIN || errno == EWOULDBLOCK){
							printf("accept EAGAIN error pid=%d\n",getpid());
						}
						if(errno==EINTR){
							printf("accept EINTR error pid=%d\n",getpid());
						}
						if(errno==ECONNABORTED){ /* this is a FreeBSD thingy */
							printf("accept EABORTED error pid=%d\n",getpid());
						}
						if(errno==EMFILE){
							printf("accept EMFILE error pid=%d\n",getpid());
						}
				}
				done=1;
				break;
			case conn_read:
				printf("on read");
				break;
		}
	}
}
int main(int argc,char **argv){
	int fd,o;
	char *listen_addr;
	int port,num_childs,max_fds;
	struct rlimit rlim;
	conn *c;
	port=0;
	num_childs=5;
	while(-1!=(o=getopt(argc,argv,"l:p:f:h"))){
		switch(o){
			case 'l':
				listen_addr=strdup(optarg);
				break;
			case 'p':
				port=atoi(optarg);
				break;
			case 'f':
				num_childs=atoi(optarg);
				break;
			case 'h':
				printf("Usage -l listen addr\n");
				printf("Usage -p listen port \n");
				printf("Usage -f fork num\n");
				exit(1);
		}
	}
	if(!listen_addr){
		listen_addr=strdup("127.0.0.1");
	}
	if(!port){
		printf("port is unknown\n");
		exit(1);
	}
	if(0 != getrlimit(RLIMIT_NOFILE,&rlim)){
		fprintf(stderr,"getrlimit failed.reason %s\n",strerror(errno));
		exit(1);
	}

	max_fds=rlim.rlim_cur;

	//create listen socket
	if(-1==(fd=create_listen_fd(listen_addr,port))){
		fprintf(stderr,"create listen fd failed\n");
		exit(1);
	}
	//prefork child
	if(num_childs > 0){
		int child=0;
		while(!child){
			if(num_childs >0){
				switch(fork()){
					case -1:
						return -1;
					case 0:
						child=1;
						break;
					default:
						num_childs--;
						break;
				}
			}else{
				int status;
				if(-1 !=wait(&status)){
					num_childs++;
				}else{
					//ignore
				}
			}
		}
	}
	//child process event
	conn_init();
	c=conn_new(fd,conn_listening);
	ev=fdevent_init(max_fds);
	if(!ev){
		fprintf(stderr,"fdevent_init()\n");
		exit(1);
	}	
	fdevent_register(ev,fd,event_handler,c);
	fdevent_event_add(ev,fd,FDEVENT_IN);
	fdevent_poll(ev,1000);
}

分享到:
评论
6 楼 aquester 2011-08-31  
从实践来看,惊群对性能基本没有影响。
5 楼 yidishui 2010-01-26  
好像 memcache代码哦
4 楼 flashjay 2010-01-26  
又来拜访一遍 

关于有几个进程‘受惊’的问题,我突然想问下,你用的机器几核?

因为我在虚拟机下测试没惊群现象,不知是否跟多核有关系呢?

3 楼 flashjay 2010-01-16  
引用

但实际上这个独立的fd 对应的确是一个文件表项,即实际上任然是一个共享的文件描述符


这个,貌似写错了或者你表达不够准确
刚开始我以为是这些fd能共享同一个文件表项呢
害我昨晚一直想不通:不同的进程貌似不会共享文件表吧。

正确说法是不是应该这样:
  这些fd分别对应自己的文件表项,然而这些文件表项指向了同一个 inode

lighty以多worker方式运行时参考 /proc下的 他们的df信息:
root@huanghuayang-laptop:/home/huanghuayang# ll /proc/3881/fd
total 0
lrwx------ 1 root root 64 2010-01-16 17:09 0 -> /dev/null
lrwx------ 1 root root 64 2010-01-16 17:09 1 -> /dev/pts/3
lrwx------ 1 root root 64 2010-01-16 17:09 2 -> /dev/null
l-wx------ 1 root root 64 2010-01-16 17:09 3 -> /var/log/lighttpd/error.log
lrwx------ 1 root root 64 2010-01-16 17:09 4 -> socket:[17697]
l-wx------ 1 root root 64 2010-01-16 17:09 5 -> /var/log/lighttpd/access.log
root@huanghuayang-laptop:/home/huanghuayang# ll /proc/3882/fd
total 0
lrwx------ 1 root root 64 2010-01-16 17:09 0 -> /dev/null
lrwx------ 1 root root 64 2010-01-16 17:09 1 -> /dev/pts/3
lrwx------ 1 root root 64 2010-01-16 17:09 2 -> /dev/null
l-wx------ 1 root root 64 2010-01-16 17:09 3 -> /var/log/lighttpd/error.log
lrwx------ 1 root root 64 2010-01-16 17:09 4 -> socket:[17697]
l-wx------ 1 root root 64 2010-01-16 17:09 5 -> /var/log/lighttpd/access.log
root@huanghuayang-laptop:/home/huanghuayang# ll /proc/3883/fd
total 0
lrwx------ 1 root root 64 2010-01-16 17:09 0 -> /dev/null
lrwx------ 1 root root 64 2010-01-16 17:09 1 -> /dev/pts/3
lrwx------ 1 root root 64 2010-01-16 17:09 2 -> /dev/null
l-wx------ 1 root root 64 2010-01-16 17:09 3 -> /var/log/lighttpd/error.log
lrwx------ 1 root root 64 2010-01-16 17:09 4 -> socket:[17697]
l-wx------ 1 root root 64 2010-01-16 17:09 5 -> /var/log/lighttpd/access.log


lrwx------ 1 root root 64 2010-01-16 17:09 4 -> socket:[17697] 这一行,中括号内的值是相同的,都表示对应的inode
对吧 
2 楼 bachmozart 2009-06-04  
xombat 写道

想问一下:1. accept是否是原子操作?2. 测试环境的linux内核是多少?


可能我文中对accept操作说得有点含糊,accept不是原子的

accept 操作是从 一个已完成3次握手的队列中取得对应的fd

客户端connect 发送了第一个syn后,服务器端 处于Listen的那个socket将这个连接信息放入一个未完成队列q0中,并发送服务器端syn和刚才的ack,待服务器端的syn的确认返回,则将该连接信息从q0 移到 已完成队列q中,

所以 accept的操作是从 q中取得连接,如果当前队列没有连接,阻塞模型,accept会阻塞等待,非阻塞模型返回EWOULDBLOCK

我用的是2.6内核
1 楼 xombat 2009-05-28  
想问一下:
1. accept是否是原子操作?
2. 测试环境的linux内核是多少?

相关推荐

    lighttpd-1.4.55移植配置与测试.rar

    交叉编译最新版的lighttpd-1.4.55,配置与测试CGI与HTML.内含 lighttpd-1.4.55源码,移植教程,cgi测试代码,html测试代码.测试cgi时,浏览器中应该输入192.168.100.30/cgi-bin/xx.cgi .其中 192.168.100.30为开发板的ip

    ubuntu lighttpd实现websocket

    如何在ubuntu上实现lighttpd 1、下载mongoose使用mongoose中的example中的websocket_chat,实现websocket 2、websocket_chat源码下载路径 官网:https://cesanta.com 论坛:...

    Linux Lighttpd 配置安装 运行 测试

    在Linux上安装Lighttpd,里面有遇到的一些问题的解决方法,整个安装流程,还有参考网站

    lighttpd-1.4.45_lighttpd服务器_

    《lighttpd-1.4.45:轻量级Web服务器的魅力解析》 lighttpd,这个名字在Web服务器领域中或许不如Apache或Nginx那样耳熟能详,但其独特的轻量级特性和高效性能,使得它在特定场景下成为理想的解决方案。lighttpd-...

    最新lighttpd源码 lighttpd-1.4.22

    Lighttpd是一个德国人领导的开源软件,其根本的目的是提供一个专门针对高性能网站,安全、快速、兼容性好并且灵活的web server环境。具有非常低的内存开销,cpu占用率低,效能好,以及丰富的模块等特点。lighttpd是...

    lighttpd代码阅读资料

    4. 编写测试用例:编写单元测试和集成测试,验证代码功能,同时也能加深对代码逻辑的理解。 总之,lighttpd的代码阅读是一个既挑战又富有成就感的过程。通过对源码的深入探索,开发者不仅能掌握lighttpd的工作机制...

    lighttpd配置和启动脚本

    Lighttpd是一款轻量级的Web服务器,常用于小型或者负载较低的网站,因其低内存占用和高效的性能而受到喜爱。在本压缩包中,我们重点关注`lighttpd.conf`配置文件以及用于控制Web服务器启动和停止的脚本。下面我们将...

    搭建lighttpd+cgi的代码包

    8. **测试CGI** 访问你的Web服务器,通过URL`http://your_server.com/hello.cgi`来查看CGI脚本是否正确运行。如果一切正常,你应该能看到“Hello, World!”的输出。 9. **lighttpd_cgi代码包** 压缩包文件`...

    ARM平台lighttpd服务器

    在"web开发工具"的范畴内,lighttpd可以作为开发者测试和部署静态内容的便捷工具。配合PHP、Python等脚本语言,lighttpd也能支持动态网站的运行。尽管lighttpd可能不如Apache或Nginx那样功能全面,但它的轻量级特性...

    varnish+lighttpd配置

    Varnish和Lighttpd是两个非常重要的开源Web服务器软件,它们在Web性能优化和负载均衡方面发挥着关键作用。Varnish作为一个高性能的HTTP缓存代理,常用于减轻后端服务器的压力,提高网站响应速度;而Lighttpd则是一款...

    lighttpd with H264 support

    5. **测试和优化**:重启lighttpd服务,然后通过浏览器或其他客户端测试视频流是否正常工作。如果一切顺利,你应该能看到MP4视频能正常播放并支持快进操作。根据实际性能和用户反馈,可能需要进一步调整配置以优化...

    lighttpd+php in android

    5. **测试与部署**:一旦lighttpd和PHP配置完毕,可以通过访问Android设备的IP地址和指定的端口号来测试Web服务是否正常工作。记得开启Android设备的端口转发,以便从其他设备访问。 这个压缩包文件可能包含了编译...

    lighttpd-1.4.20源代码

    《lighttpd-1.4.20源代码解析与技术深度探讨》 lighttpd是一款轻量级的Web服务器,其设计目标是低系统资源消耗、高效且安全的运行环境,尤其适合于动态内容不多的网站。在lighttpd-1.4.20版本中,我们有机会深入...

    Lighttpd源码分析_mobi

    主要内容包括:lighttpd介绍与分析准备工作、lighttpd网络服务主模型、lighttpd数据结构、伸展树、日志系统、文件状态缓存器、配置信息加载、i/o多路复用技术模型、插件链、网络请求服务响应流程、请求响应数据快速...

    Lighttpd By Andre Bogus

    ### Lighttpd Web Server: A Comprehensive Guide by Andre Bogus #### Introduction The book titled "Lighttpd" by Andre Bogus is a comprehensive guide designed to provide readers with an in-depth ...

    lighttpd-1.4.20.tar

    lighttpd-1.4.20.tar lighttpd-1.4.20.tar

    lighttpd-1.4.20-cmake

    7. **启动与管理lighttpd**:通过命令`sudo /etc/init.d/lighttpd start`来启动lighttpd,使用`sudo /etc/init.d/lighttpd stop`、`sudo /etc/init.d/lighttpd restart`来停止或重启服务。 **lighttpd的特色功能** ...

    lighttpd-1.4.49.tar.gz

    6. **测试与优化**:使用浏览器访问服务器IP,检查服务是否正常。通过调整配置参数,如缓冲区大小、连接数限制等,优化服务器性能。 此外,lighttpd与FastCGI结合使用,可以高效地运行PHP等动态语言应用。例如,...

    lighttpd源码分析

    接着,它解析请求头,获取如Cookie、Accept等信息。 3. 路由处理:lighttpd根据URL和配置规则(如URL映射、路径匹配)选择合适的处理模块和处理函数。这可能涉及到静态文件服务、CGI脚本执行、反向代理等。 4. ...

Global site tag (gtag.js) - Google Analytics