高并发
抢购是平台常见的促销方式,牵扯到技术问题有如何处理高并发、处理商品存货。
需求估算
日活 100万
PV 5千万:每个用户打开10个页面,每个页面5次API请求
秒QPS
流量高峰 18-22点 按照4小时计算
5千万/4/3600 = 3400
单机承载最大QPS (8C8G)
8G内存预留2G给系统缓冲 6GB分配给PHP-FPM
单个PHP-FPM进程 占用大约25-40M内存, 以40M计算
6 X 1024 / 40 = 150
单个机器同时可并发处理150个请求
单个请求假设耗时0.25秒(250ms),即单机每秒可处理4个请求
则单机最大秒QPS为150 X 4 = 600
需要6台机器
3400 / 600 ≈ 6
预留一台做缓冲 比如重启项目时,轮询重启,保证可提供服务的恒定有6台.
这里的6台只是nginx+php-fpm的应用服务器 数据库和缓存之类的另做计算
3台机器
Swoole4.5 + EasySwoole 重写.
架构图
Redis 事务
提到redis的事务,相信很多初学的朋友会对它的理解和使用有些模糊不清, 料想它和我们常见的关系型数据库(mysql 、mssql等)中的事务相同,也支持回滚,但这样理解就进入了一个误区, 首先:关系型数据中的事务都是原子性的,而redis 的事务是非原子性的。 再多说一句,什么是程序原子性?简单的理解就是:整个程序中的所有操作,要么全部完成,要不全部不完成, 不会停留在中间某个环节。那么非原子性就是不满足原子性的条件就是非原子性了。我们用例子来解释一下:
原子性:数据库中的某个事务A中要更新t1表、t2表的某条记录,当事务提交,t1、t2两个表都被更新,若其中一个表操作失败,事务将回滚。
非原子性:数据库中的某个事务A中要更新t1表、t2表的某条记录,当事务提交,t1、t2两个表都被更新,若其中一个表操作失败, 另一个表操作继续,事务不会回滚。(当然对于关系型数据库不会出现非原子性)
Redis事务相关命令:
- MULTI :开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令系列。
- EXEC:执行事务中的所有操作命令。
- DISCARD:取消事务,放弃执行事务块中的所有命令。
- WATCH:监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令。
- UNWATCH:取消WATCH对所有key的监视。
说明实例参看一下 https://zhuanlan.zhihu.com/p/135241403
防止多销
防止抢购的商品多售,可以使用的技术方案有:
- 悲观锁
- 队列
- 乐观锁
我们用Redis实现乐观锁。
$store = 10; // 库存
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
// 对于某一键加锁
$redis->watch("sales"); // 监视一个键
$sales = $redis->get("sales"); // 获取销量
if ($sales >= $store) {
exit("活动结束");
}
$redis->multi(); // 开始事务
$redis->set("sales", $sales+1); // incr()
$ret = $redis->exec(); // 提交事务
if ($ret) {
// 修改mysql相关数据,可通过请求守护进程或接口实现
}
可以用压测公测模拟抢购场景,如 apach bench等。
参考资料
PHP高级 面试经典:电商平台高并发秒杀架构实现方案 https://www.bilibili.com/video/av843326819/
Redis 事务 https://www.runoob.com/redis/redis-transactions.html
彻底搞懂 Redis 事务 https://www.cnblogs.com/fengguozhong/p/12161363.html
彻底搞懂 Redis 事务 https://zhuanlan.zhihu.com/p/135241403
Redis watch命令——监控事务 http://c.biancheng.net/view/4544.html
PHP 百万日活五千万PV的系统架构 https://segmentfault.com/a/1190000022538441
有赞百亿级日志系统架构设计 https://segmentfault.com/a/1190000018867825?utm_source=sf-similar-article