xiaozhuaisnow 发表于 2014-11-4 13:10:41

PHP支持多线程与异步请求造成伪DOS攻击解决方案

最近根据项目需求出发,需要大量的异步请求第三方(这个第三方和我们平时了解的像新浪api等是有本质区别的)数据。下面把自己遇到的问题和解决方案分享一下,回馈群里面的兄弟们。

@1 需要解决php多线程问题,因为php不支持多线程,那就只能模拟了。

   @1.1在实际项目中 ,一个操作可能需要6--7个步骤的异步请求数据(是有严格逻辑关系和执行顺序的),刚开始我试着把它封装到一个js类库里面,结果js类库是能够很好的完整整个流程,但是如果用户强制刷新或者网络问题,请求的数据无法准确反馈给客户端。结果此方法放弃.... 需要进一步优化

      @1.2 换种思维,在碰到这种一个操作需要6--7个步骤请求数据的(是有严格逻辑关系和执行顺序的),可以放在php后端执行,前端只需要发起一条请求,前端不需要知道当前执行了那个步骤(用户其实不关心执行到了那个步骤,每个步骤的数据是什么,他只关心当前操作有没有成功)。

       那么问题来了,6--7步这样的操作,可能需要1-5分钟才能够执行完成(可能会更长如20分钟)。后端执行的connection_status 可能会断开 或者超时。
当然我们可以设置set_time_limit(0) ,但是客户端也有可能断开的,时间长了可能不会完整的执行,这个是无法接受的。
那么理想的状态是 应该每一个请求会是一个时事的线程,当这个操作结束的时候线程自动结束。
php又不支持多线程,又不能够实用计划任务(因为要时事添加或者销毁线程)。开始了漫长的查找之旅.........
好了略过过程,直接说出解决方案:


//模拟线程
function test_check1(){
    ignore_user_abort(true);
    set_time_limit(0);
    $i = 0;
    $str = '';
    do {
      if($i > 10){
            break;
      }else{
            $i++;
            $str .= time()."--".$i."\r\n";
            file_put_contents(FCPATH.'uploadfile/abcd.txt',$str);
            sleep(1);
      }
    } while ($i <= 100);
}


ignore_user_abort 方法比较冷门,不太经常用到,意思是设置客户端断开是否继续执行脚本,默认false不执行,那么与set_time_limit(0)正好就类似于线
程,
下列是测试结果跑了已完成执行了38个线程,总数据30多万 条全部跑完。并且执行一个会自动结束一个(图片为什么上传不了?)算了 后续你要要求看的话补上去
补上去:
http://www.codeigniter.org.cn/forums/data/attachment/album/201411/04/132419aln1g19nvv07ttd5.pnghttp://www.codeigniter.org.cn/forums/data/attachment/album/201411/04/133023pu7nyo7pggppqutn.png@3 php多线程问题解决了,前台调用一下还是挺高效的 ,后续测试发现产生了新的问题,前端发送一个ajax请求 。后端由于已经执行,但是还没有结束,前端ajax一直处于请求中的状态。这种情况可以造成多个问题,以一个就是ajax多的情况下会造成浏览器假死(异步或者放在html最底部也一样,因为浏览器的同域并发连接数限制),并且通过onreadystatechange 测试 tcp3次握手,(其实ajax是4次状态的改变)到第二次就卡着不动,我XX,这要是用户量上来了,这样的大量操作(虽然可能性较低,但是根据墨菲定理肯定会发生的)就会造成dos攻击而导致服务器垮掉。

补上去:http://www.codeigniter.org.cn/forums/data/attachment/album/201411/04/132420rp4ji8ihxn1j8ohb.jpghttp://www.codeigniter.org.cn/forums/data/attachment/album/201411/04/132420rp4ji8ihxn1j8ohb.jpg

   那么新的需求出来了,能不能够ajax请求的时候立即给出响应,让客户端知道后端去执行了(只要执行了就一定会成功(广义上),因为我们是模拟的一个线程)。想偷懒在群里问有没有这样的解决方案 .....结果你知道的........
于是重新开始大量的查找资料    最终找到3个解决方案:1 雅虎曾经提出过快速输出的概念,这里就不细说了,给出连接自己去看 https://developer.yahoo.com/performance/rules.html
2 运用ob或者register_shutdown_function先说ob的 不像传统运用的那种模式,把缓存“挤”出来,这样肯定是不行的,我就直接粘贴可用的代码吧,


if ( ! function_exists('custom_ob_flush')){
    function cunstom_ob_flush($var = 1){
      ob_start();
      ob_end_clean();
      header("Connection: close");
      ignore_user_abort();
      ob_start();
      echo ($var);
      $size = ob_get_length();
      header("Content-Length: $size");
      ob_end_flush();
      flush();
    }
}



同上上述方法调用线程前时调用,可立即输出,并且不会影响到后续操作,有多快响应?看你的服务器和客户端速度有多快了{:soso_e113:}

3 最好解决的解决方案根据PHP-FPM记得一定要开启,不然没法用php fastcgi 开启的时候这个方法会生效 叫做fastcgi_finish_request() 这是解释 special function to finish request and flush all data while continuing to do something time-consuming (video converting, stats processing etc.);
例如在视频转换用的,php版本要求 5.3以上


不擅长语言组织,如果各位有更好的方法,欢迎提出,谢谢!
页: [1]
查看完整版本: PHP支持多线程与异步请求造成伪DOS攻击解决方案