php过载后无法恢复的原因分析(eaccelerator造成)

http://blog.csdn.net/hopingwhite/article/details/8492948


最近php机器频繁出现过载后再也无法提供服务的现象,只要一有请求发过去,负责处理该请求的php进程就是cpu占用100%。本来的负载均衡策略是一旦某机器的php请求出现连接超时就将该机器的权重降低,发向该机器的请求概率就会降低,虽然有一定滞后效应,但是最终应该能够降压并且最后恢复服务,但是这个策略在最近突然失效了。出现这个情况之后无法发送什么请求到php-fpm都会cpu100%,即使请求的是一个空的php文件。于是猜想可能是eaccelerator造成的。

我们的Php-fpm的request_terminate_timeout设置的是5s,于是只要是有请求执行超过5s就会被php-fpm将执行进程干掉,在出问题的前后出现了大量的5s超时,初步猜想可能是因为eaccelerator的共享内存造成的,子进程被干掉时共享内存被写错了,导致所有请求过来都会出错,但是这解释不了新文件也会被卡住的问题,于是去看eacceleraotr的代码,发现如下代码

  1. #define spinlock_try_lock(rw)  asm volatile("lock ; decl %0" :"=m" ((rw)->lock) : : "memory")  
  2. #define _spinlock_unlock(rw)   asm volatile("lock ; incl %0" :"=m" ((rw)->lock) : : "memory")  
  3.   
  4. static int mm_do_lock(mm_mutex* lock, int kind)   
  5. {  
  6.     while (1) {  
  7.         spinlock_try_lock(lock);  
  8.         if (lock->lock == 0) {   
  9.             lock->pid = getpid();  
  10.             lock->locked = 1;   
  11.             return 1;  
  12.         }      
  13.         _spinlock_unlock(lock);  
  14.         sched_yield();  
  15.     }      
  16.     return 1;  
  17. }  
  18.   
  19. static int mm_do_unlock(mm_mutex* lock) {  
  20.     if (lock->locked && (lock->pid == getpid())) {  
  21.         lock->pid = 0;  
  22.         lock->locked = 0;  
  23.         _spinlock_unlock(lock);  
  24.     }  
  25.     return 1;  
  26. }  
  1.   
其中mm_mutex是指向共享内存的,也就是说eac用了共享内存来当作进程间的锁,并且使用的spinlock方式,那这样一来一切都能解释的通了。设想如下一种情况,某个进程拿到锁之后被php-fpm干掉了,它没有unlock,这样一来所有的php-fpm子进程都拿不到锁,于是大家就都在这个while(1)循环里卡死了。猜想有了,怎么去证实呢?原来的想法是直接去读那片共享内存,结果发现php时IPC_PRIVATE的,所以没办法读了。于是只能等到线上出问题后gdb上去看内存,今天终于有了确凿的证据
  1. (gdb) p *mm->lock  
  2. $8 = {lock = 4294966693pid = 21775locked = 1}  
这里可以看到内存已经被进程号为21775的进程拿到了,但事实是,这个进程在很早以前就已经被干掉了。
问题得到证实了,那么再回头看一下这个问题发生的条件
1、请求执行时间很长,长到会被php-fpm干掉
2、进程被干掉时,php正在require文件,并且eac拿到了锁
 
从这里可以看到,有一些特定情形会将这个概率放大
1、request_terminate_timeout时间很短
2、使用auoload方式,或者在执行逻辑里require文件,因为如果在请求开始前就将所有的文件加载,那除非光require文件就已经超时,否则不应该会在require文件时被干掉。但是同样的使用autload方式也有一个比较丑陋的办法可以避过这个问题,那就是在autload函数里判断一下,如果执行时间过长了就直接exit而不是require
 
个人觉得,解决这个问题的最好办法是request_terminate_timeout时间设置的足够长,比如30s, 300s,而将超时判断全部放在应用层,不能通过php-fpm来处理这种问题,php-fpm事实只能用作最后一重保险,不得不使用的保险。另外php里还有一个超时设置max_execution_time,但是这个超时在cgi模式下是cpu时间,所以作用不大
 
本以为这件事情会告一段落,结果在海外的某个平台发现这个事情频繁出现,而且奇怪的是,十几个平台,4,5百机器中,只有那个平台上频繁出现,出现的原因与上面分析一样,本来这应该是一个小概率事件的,但是实在无法理解为什么会这么频繁的出现,最后无奈之下只能把那个平台换成了apc。这里再提一下apc,apc的默认实现是mmap和pthread,pthread是由操作系统维护的,因此肯定不会出现上面spinlock的问题,如果有人碰到这个问题并且上面的办法无法解决的话,还是果断换成apc吧。至于为什么频繁出现,这个实在是无法解释了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值