课程:PHP秒杀设计 https://www.imooc.com/video/19863
1、环境准备
a.安装压测工具ab
sudo apt-get install apache2-utils -y
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 = << 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