Nginx+PHP内存占用分析

http://www.opensoce.com/?p=1488

php-cgi内存占用

netstat -anop | grep "php" | grep -v "grep" | wc -l #check running php-cgi
#!/bin/sh
while [ 1 ]
do
  for ps1 in `ps -eo pid,fname,rss|grep php-cgi|grep -v grep|awk '{if($3>=32000) print $1}'`; do
    kill -9 $ps1
  done
done

php-cgi会在每个请求结束的时候回收脚本使用的全部内存,但是并不会释放给操作系统,而是继续持有以应对下一次PHP请求。这样做大概是为了减少内存碎片化或者解决从系统申请内存之后又释放回操作系统所需要的时间不可控问题。可是如果偶然一次PHP请求使用了诸如ftp或者zlib这样的大内存操作,那么将导致一大块系统内存被php-cgi持续占有,不能被利用。

解决这个问题的办法是在web服务器配置中降低PHP_FCGI_MAX_REQUESTS 的值。这个参数决定了一个php-cgi进程被创建出来之后,最多接受的PHP请求数,在lighttpd中默认配置是10000。也就是说这个php- cgi进程每接受10000次PHP请求后会终止,释放所有内存,并重新被管理进程启动。如果把它降低,比如改成100,那么php-cgi重启的周期会大大缩短,偶然的高内存操作造成的问题影响时间也会缩短。

另一个办法则是写一个crontab脚本,定时发现高内存占用的php-cgi进程并向它传送kill指令。

Nginx内存占用

1worker_connections 65535;将会占用25M左右
2worker_connections 4096;将会占用4M左右

php-cgi管理程序

static void* spawn_process( void* data )
{
	FILE* fp;
	fcgi_program* fcgi = (fcgi_program*)data;
	int idx = fcgi->spawn_count ++, ret;
	while( !end ){
#ifdef __WIN32__
		STARTUPINFO si={0};
		PROCESS_INFORMATION pi={0};
		ZeroMemory(&si,sizeof(STARTUPINFO));
		si.cb = sizeof(STARTUPINFO);
		si.dwFlags = STARTF_USESTDHANDLES;
		si.hStdOutput = INVALID_HANDLE_VALUE;
		si.hStdInput  = (HANDLE)fcgi->listen_fd;
		si.hStdError  = INVALID_HANDLE_VALUE;
		if( 0 == (ret=CreateProcess(NULL, fcgi->shell,
			NULL,NULL,
			TRUE, CREATE_NO_WINDOW ,
			NULL,NULL,
			&si,&pi)) ){
			printf("[fcgi] failed to create process %s, ret=%d\n", fcgi->shell, ret);
			return NULL;
		}
		fcgi->process_fp[idx] = (int)pi.hProcess;
		printf("[fcgi] starting process %s\n", fcgi->shell );
		WaitForSingleObject(pi.hProcess, INFINITE);
		fcgi->process_fp[idx] = 0;
		CloseHandle(pi.hThread);
#else
		ret = fork();
		switch(ret){
		case 0:{	//child
			/* change uid from root to other user */
			if(getuid()==0){
                struct group *grp = NULL;
                struct passwd *pwd = NULL;
				if (*fcgi->username) {
					if (NULL == (pwd = getpwnam(fcgi->username))) {
						printf("[fcgi] %s %s\n", "can't find username", fcgi->username);
						exit(-1);
					}
					if (pwd->pw_uid == 0) {
						printf("[fcgi] %s\n", "what? dest uid == 0?" );
						exit(-1);
					}
				}
				if (*fcgi->groupname) {
					if (NULL == (grp = getgrnam(fcgi->groupname))) {
						printf("[fcgi] %s %s\n", "can't find groupname", fcgi->groupname);
						exit(1);
					}
					if (grp->gr_gid == 0) {
						printf("[fcgi] %s\n", "what? dest gid == 0?" );
						exit(1);
					}
					/* do the change before we do the chroot() */
					setgid(grp->gr_gid);
					setgroups(0, NULL);
					if (fcgi->username) {
						initgroups(fcgi->username, grp->gr_gid);
					}
				}
				if (*fcgi->changeroot) {
					if (-1 == chroot(fcgi->changeroot)) {
						printf("[fcgi] %s %s\n", "can't change root", fcgi->changeroot);
						exit(1);
					}
					if (-1 == chdir("/")) {
						printf("[fcgi] %s %s\n", "can't change dir to", fcgi->changeroot);
						exit(1);
					}
				}
				/* drop root privs */
				if (*fcgi->username) {
					printf("[fcgi] setuid to %s\n", fcgi->username);
					setuid(pwd->pw_uid);
				}
			}

			int max_fd = 0, i=0;
			// Set stdin to listen_fd
			close(STDIN_FILENO);
			dup2(fcgi->listen_fd, STDIN_FILENO);
			close(fcgi->listen_fd);
			// Set stdout and stderr to dummy fd
			max_fd = open("/dev/null", O_RDWR);
			close(STDERR_FILENO);
			dup2(max_fd, STDERR_FILENO);
			close(max_fd);
			max_fd = open("/dev/null", O_RDWR);
			close(STDOUT_FILENO);
			dup2(max_fd, STDOUT_FILENO);
			close(max_fd);
			// close other handles
			for(i=3; i<max_fd; i++)
				close(i);
			char *b = malloc(strlen("exec ") + strlen(fcgi->shell) + 1);
			strcpy(b, "exec ");
			strcat(b, fcgi->shell);

			/* exec the cgi */
			execl("/bin/sh", "sh", "-c", b, (char *)NULL);
			exit(errno);
			break;
		}
		case -1:
			printf("[fcgi] fork failed\n");
			return NULL;
		default:{
			struct timeval tv = { 0, 100 * 1000 };
			int status;
			select(0, NULL, NULL, NULL, &tv);
			switch(waitpid(ret, &status, WNOHANG)){
			case 0:
				printf("[fcg] spawned process %s: %d\n", fcgi->shell, ret);
				break;
			case -1:
				printf("[fcgi] waitpid failed\n");
				return NULL;
			default:
				if (WIFEXITED(status)) {
						printf("[fcgi] child exited with: %d\n", WEXITSTATUS(status));
				} else if (WIFSIGNALED(status)) {
						printf("[fcgi] child signaled: %d\n", WTERMSIG(status));
				} else {
						printf("[fcgi] child died somehow: %d\n", status);
				}
				return NULL;
			}
			//wait for child process to exit
			fcgi->process_fp[idx] = ret;
			waitpid(ret, &status, 0);
			fcgi->process_fp[idx] = 0;
		}
		}
#endif
	}
	if(!end){
		printf("[fcgi] failed to create process %s\n", fcgi->shell );
	}
	return NULL;
}

static void delete_program( const void* v )
{
	fcgi_program * fp = (fcgi_program*)v;
	int i;
	for( i=0; i<fp->spawn_count; i++ ){
		if( fp->process_fp[i] ){
			int h = fp->process_fp[i];
			printf("[fcgi] terminating %s: %x\n", fp->shell, h );
#ifdef __WIN32__
			TerminateProcess((HANDLE)h, 0);
#else
			kill(fp->process_fp[i], SIGKILL);
#endif
//			pthread_join( fp->threads[i], (void**)0 );
		}
	}
	closesocket( fp->listen_fd );
	free( fp );
}

#ifdef __WIN32__
#define EXPORT __declspec(dllexport) __stdcall
#else
#define EXPORT
#endif
int EXPORT plugin_entry( webserver* srv )
{
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setstacksize(&attr, 64*1024); //64KB
	//load fcgi program information
	loop_create( &loop_program, MAX_FCGI_PROGRAM, delete_program );
	if( xml_redirect( srv->config, "/fastCGI/program", 0 ) ){
		do{
			int spawn_count, i;
			fcgi_program * fcgip;
			fcgip = (fcgi_program*) malloc( sizeof(fcgi_program) );
			memset( fcgip, 0, sizeof(fcgi_program) );
			loop_push_to_head( &loop_program, (void*)fcgip );
			strncpy( fcgip->extension, xml_readstr(srv->config, ":extension"), 63 );
			strncpy( fcgip->username, xml_readstr( srv->config, "spawnProcess:user" ), 31 );
			strncpy( fcgip->groupname, xml_readstr( srv->config, "spawnProcess:group" ), 31 );
			strncpy( fcgip->changeroot, xml_readstr( srv->config, "spawnProcess:root" ), 127 );
			strncpy( fcgip->shell, xml_readstr( srv->config, "spawnProcess:path" ), 127 );
			spawn_count = xml_readnum(srv->config, "spawnProcess:number");
			strncpy( fcgip->server_addr, xml_readstr( srv->config, "serverAddress" ), 63 );
			fcgip->server_port = xml_readnum(srv->config, "serverPort");
			printf("[fcgi] fcgi for %s:%d extension:%s shell:%s\n", fcgip->server_addr, fcgip->server_port,
				fcgip->extension, fcgip->shell );
			fcgip->dest_addr.sin_family = PF_INET;
			fcgip->dest_addr.sin_addr.s_addr = inet_addr( fcgip->server_addr );
			fcgip->dest_addr.sin_port = htons( fcgip->server_port );

			/* Check If we need to start the fastcgi process */
			if( fcgip->shell[0] ){
				fcgip->listen_fd = socket(AF_INET, SOCK_STREAM, 0);

				if( connect( fcgip->listen_fd, (struct sockaddr*)&fcgip->dest_addr, sizeof(struct sockaddr_in)) != -1 ){
					printf("[fcgi] %s:%d is already in use. failed to spawn in.\n", fcgip->server_addr, fcgip->server_port );
					closesocket( fcgip->listen_fd );
					return -1;
				}
				closesocket( fcgip->listen_fd );
				//reopen socket
				fcgip->listen_fd = socket(AF_INET, SOCK_STREAM, 0);
				i = 1;
				setsockopt( fcgip->listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*)&i, sizeof(i));

				if (-1 == bind( fcgip->listen_fd, (struct sockaddr*)&fcgip->dest_addr, sizeof(struct sockaddr_in)) ) {
					printf("[fcgi] failed to bind %s:%d\n", fcgip->server_addr, fcgip->server_port );
					return -1;
				}
				listen(fcgip->listen_fd, srv->max_threads);
				if( spawn_count < 1 || spawn_count > 64 )
					spawn_count = 5;
				for( i=0; i<spawn_count; i++ )
					if( pthread_create( &fcgip->threads[i], &attr, (void*)spawn_process, (void*)fcgip ) == -1 ){
						printf("[fcgi] failed to create thread: %d\n", errno);
					}
			}
		}while( xml_movenext( srv->config ) );
	}
	srv->vdir_create( srv, "/", (vdir_handler)fcgi_run );
	return 0;
}

int EXPORT plugin_cleanup( webserver* srv )
{
	end = 1;
	loop_cleanup( &loop_program );
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值