3

课程:PHP秒杀设计 https://www.imooc.com/video/19863

1、环境准备

a.安装压测工具ab

sudo apt-get install apache2-utils -y

Nginx下limit_req模块burst参数超详细解析

b.nginx环境配置

#创建规则:以ip限流,申请10M内存用来存储访问的频次信息、速率是1个每秒
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
#应用规则location{}: 使用规则,突发流量可以burst(2)个排队、再超过丢弃
limit_req zone=mylimit burst=2 nodelay;

nodelay:

  • 如果设置,会在瞬时提供处理(burst + rate)个请求的能力,>请求超过(burst + rate)的时候就会直接返回503,永远不存>在请求需要等待的情况。(这里的rate的单位是:r/s)
  • 如果没有设置,则所有请求会依次等待排队

c.php环境

更新

cd /home/wwwroot/cluster
sed -ri "s/root\/tmp\/dk/home\/wwwroot\/cluster/g" `grep -rl "root\/tmp\/dk" .`
vim rec.php.sh #%s/php7\:1.10/php7\:1.11/g 
# 重启n3(带防火墙的nginx)服务

d.环境测试

失败情况 Failed requests/Complete requests

ab -n1000 -c50 'http://192.168.1.111:8084/index.html' //0/1000
ab -n1000 -c50 'http://192.168.1.111:8084/a.php' //0/1000
ab -n1000 -c50 'http://192.168.1.111/phpinfo.php' //724/1000

2、问题分析

场景:

1.某商品库存1000
2.并发读100w
3.并发抢购100w

收到并发请求:

预扣库存 => 创建订单 => 
    支付 => 数据入库
    10分钟内不支付取消订单 => 回滚预扣库存

a.需求点分析

1、查库存
curl 'http://192.168.1.111:8080/seckill/api.php?act=getStock&product_id=1&user_id=1'
ab -n1000 -c50 'http://192.168.1.111:8080/seckill/api.php?act=getStock&product_id=1&user_id=1'
       ____查询功能会应对最大的请求压力,请求落到php缓存(apcu)上,无缓存时查询redis库。

2、扣库存
curl 'http://192.168.1.111:8080/seckill/api.php?act=buy&product_id=1&user_id=1'
ab -n1000 -c50 'http://192.168.1.111:8080/seckill/api.php?act=buy&product_id=1&user_id=1'
    ____(单件商品)扣库存应对秒杀订单提交,参数存有:库存总数、本机预售限额数、本机订单数:
一、本地预售缓存(APC_LOCAL_USE)加当前请求(+1)比较本地预售库存(APCU_LOCAL_STOCK)、超过则拒绝,
二、去redis库报入、看总预售数(REDIS_REMOTE_USE_COUNT)是否小于库存总数(REDIS_REMOTE_TOTAL_COUNT)、
不小于则拒绝,
三、添加用户信息到订单队列(REDIS_REMOTE_QUEUE)。

3、同步库存至本地缓存
curl 'http://192.168.1.111:8080/seckill/api.php?act=sync&product_id=1&user_id=1'
    ____把本机缓存与redis数据同步:redis上的库存总数(REDIS_REMOTE_TOTAL_COUNT)、
总预售数(REDIS_REMOTE_USE_COUNT)减去本机的预售缓存(APC_LOCAL_USE),成功则把本地预售缓存
(APC_LOCAL_USE)清零、更新本地预售库存(APCU_LOCAL_STOCK)。

4、清空本地缓存
curl 'http://192.168.1.111:8080/seckill/api.php?act=clear&product_id=1&user_id=1'
    ____清理本地缓存apcu_clear_cache()。

5、重置数据
curl 'http://192.168.1.111:8080/seckill/api.php'

b.应用知识点

扣库存同步核心代码:

//给总预售数 +1
$script = <<<eof
    local key = KEYS[1]
    local field1 = KEYS[2]
    local field2 = KEYS[3]
    local field1_val = redis.call('hget', key, field1) + 0
    local field2_val = redis.call('hget', key, field2) + 0

    if(field1_val > field2_val) then
        return redis.call('HINCRBY', key, field2, 1)
    end
    return 0
eof;
        return self::conRedis()->eval($script, [
            self::$REDIS_REMOTE_HT_KEY,
            self::$REDIS_REMOTE_TOTAL_COUNT,
            self::$REDIS_REMOTE_USE_COUNT
        ], 3);

缓存相关函数:apcu_add; apcu_inc; apcu_store; apcu_dec
redis:使用eval执行lua脚本

3、压力测试

扣库存接口 高并发时,会受限于redis的性能

a.测试结果

先关闭软件防火墙,本机的8080/8082/8084前面2个没有防火墙,上面端口使用8080的,虚拟机中docker的测试结果:

使用VM环境deepin15.11:
    mysql8 + docker_nginx*1 + docker_redis主从 + docker_php*3
[注]
-r 解决:apr_socket_recv: Connection reset by peer (104)
-c20000 客户端数默认最大是2w
接口 参数 Complete requests Failed requests Time per request(ms) qps
查询: getStock -n1000-c50
-r-n100000-c10000
-r-n1000000-c20000
1000
100000
1000000
0
102015
1002026
12
10
10890
4037
4583
1836
扣库存: buy -n1000-c50
-r-n100000-c10000
-r-n1000000-c20000
1000
100000
1000000
0
100668
1001635
13
1964
13040
3719
5090
1533
定时同步: sync -n1000-c50 1000 998 632 790

b.swoole升级版测试结果

虚拟机使用的是 docker_nginx + php :容器ngx代理9500到本地9501端口。

/** 【注意】
 * 1. php.ini apcu默认不支持cli运行
 *      apc.enable_cli=1
 * 2. 注意$request->server['request_uri'] == '/favicon.ico'的拦截处理
 * 3. nginx配置:proxy_pass    http://192.168.1.111:9501; #交给swoole代理
 */

Api2.php测试结果:

接口 参数 Complete requests Failed requests Time per request(ms) qps
查询: getStock -n1000-c50
-r-n100000-c10000
-r-n1000000-c20000
1000
100000
1000000
0
98016
98497
2
582
593
17902
17164
16854
扣库存: buy -n1000-c50
-r-n100000-c10000
-r-n1000000-c20000
1000
100000
1000000
0
95958
1000000
2
660
13354
19005
15149
1497【cpu 99%】
定时同步: sync -n1000-c50 1000 0 3 15702

c.项目小结

swoole版改写确有实实在在的提升,有思维转换,并需要对部分扩展进行调试。

【思路】
  应对高并发 <= 充分发挥挖掘cpu性能 <= 业务逻辑(计算、io)分离解耦

实例及数据库代码上传:
https://github.com/cffycls/seckill

【说明】

  • 普通php-fpm静态:不动,cli-swoole启动:执行 php api2.php (已路由)
  • 代码的composer:vendor目录是swoole-ide-helper

沧浪水
97 声望12 粉丝