之前做过redis处理并发的,处理效率要比直接操作mysql快1一倍左右,但其实效率还是不高,虽然redis是很快,但因为要经过nginx,单个nginx处理并发的能力也是有限的,所以这一块的瓶颈是nginx的并发能力。

既然如此,我们就玩点花的,用PHP处理并发的最强工具,Swoole

Swoole是什么?

Swoole的实际就是一个底层使用C实现的PHP协程框架,他使 PHP 开发人员可以编写高性能高并发的 TCP、UDP、Unix Socket、HTTP、 WebSocket 等服务,而这次,我们用到的就是http服务器。

下面这个是我封装好的一个Swoole类,只要装好swoole和redis扩展就能用

<?php
class Swoole
{
    function __construct()
    {
       $http = new swoole_http_server("0.0.0.0", 8888);

        $http->on("start", function ($server)
        {
            echo "Swoole http server is started at http://wrsndm:8888\n";
        });

        $http->on("request", function ($request, $response)
        {
            $this->response = $response;
            //防止Chrome请求2次的问题
            if ($request->server['path_info'] == '/favicon.ico' || $request->server['request_uri'] == '/favicon.ico') {
                $response->end();
                return;
            }
            $response->header("Content-Type", "text/plain; charset=utf-8");//中文编码
            $method = $request->get['method'] ?? 'index';
            $this->$method($request);
//            $response->end("Hello World Rand Number is:" . rand(0, 100) . "\n");
        });

        //实例化Redis
        $this->redis = new Redis();
        $this->redis->connect('redis', '6379');//redis可改成自己的IP

        //启动swoole_http服务
        $http->start();

    }

    public function index($request)
    {
        $this->response->end('剩余库存' . $this->redis->lLen('goods_list'));
    }

    public function redis_qianghuo($request){

        //查询库存
        if($this->redis->lLen('goods_list') == 0) {
            //记录失败次数
            while (true){
                if(!$this->redis->set('fail_lock',1,['EX'=>5,'NX']))//拿锁的权限,没拿到则continue
                    continue;
                $num = $this->redis->get('fail_num');
                $this->redis->set('fail_num', $num+1);
                $this->redis->del('fail_lock');//解锁
                break;
            }
            $this->ajaxReturn('商品已售完...');
        }
        $uid = $request->server['remote_port'];

        //查询是否重复购买
//        if($this->redis->sIsMember('bought_list',$uid)) {
//            //记录重复购买次数
            while (true){
                if(!$this->redis->set('bought_lock',1,['EX'=>5,'NX']))//拿锁的权限,没拿到则continue
                    continue;
                $num = $this->redis->get('bought_num');
                $this->redis->set('bought_num', $num+1);
                $this->redis->del('bought_lock');//解锁
                break;
            }
//            $this->ajaxReturn('你已经购买过了!');
//            echo "已经购买过了\n";
//            return;
//        }

        //商品出队
        $goods_id = $this->redis->rpop('goods_list');
        //uid添加到已抢购名单,防止重复抢购
        $this->redis->sAdd('bought_list',$uid);

        $value = array(
            'uid'   =>  $uid,
            'goods_id'   =>  $goods_id,
            'time'  =>  time(),
        );
        //保存订单信息
        $this->redis->hSet('order_info',$uid,json_encode($value));
        $this->ajaxReturn('购买成功。');
        echo "购买成功\n";
    }

    private function ajaxReturn($info){
        $this->response->end($info);
    }

}

$swoole = new Swoole();

首先要启动swoole_http服务器,只能使用cli模式启动,成功会输出下面这段文字

easyswoole 使用redis swoole table 和redis 性能_nginx

然后我们用ab进行1W次的并发测试

ab -c10 -n10000 localhost:8999/?method=redis_qianghuo

easyswoole 使用redis swoole table 和redis 性能_easyswoole 使用redis_02

然后是Nginx服务器的并发测试

easyswoole 使用redis swoole table 和redis 性能_php_03

可以看到,swoole处理是6.9秒,且全部成功,nginx处理是18秒,而且有276个失败请求(我之前测的,错误率还挺高),效率提升了差不多2倍,我用的最低配的阿里云,所以处理能力不是很强,rps只有1400,到生产环境部署,并发能力会更加强劲!

此外,我还测试了不同服务器代理情况,主要瓶颈还是网络,包括四种:

  1. 直接Nginx(nginx+fpm)
  2. 直接swoole(swoole)
  3. Nginx反向代理到swoole服务端(nginx+swoole)
  4. 通过Nginx,PHP再调curl请求到swoole服务端(nginx+fpm+swoole)

处理速度情况如下

2>3>>1>>4

可以看出,没有php-fpm处理的是最快的,这是因为fpm的worker进程有限,一个请求就要一个进程处理,进程用完了,其他请求则在排队。