前期准备
- 背景 相信很多在小公司打拼的小伙伴 对于秒杀系统真的是可遇不可求 我们只能通过模拟演练 一方面熟悉高并发场景、提升编码技能 另一方面,为进入大厂做好准备 此处,我主要还是阐述下设计思路 有不同见解,欢迎指摘 …
- 模拟环境
PHP7.2、CentOS7.9、Redis6.0.8、ab 压测工具
☛ 设计思路
- 首先,要明确的一点是,不能直接按照传统商品订单思路处理,毕竟大流量下不能丢失用户美好的交互性
然后,准备秒杀服务器,不影响主业务运行
用户在秒杀等待页面,使用
ajax
异步更新倒计时 - 点击
"抢购"
触发时 使用Redis
开启事务 提取用户唯一标识 ID
,首先集中到redis
的一个商品数量的集合
("kill_user_que"
) 然后,将符合要求的用户ID
,存入秒杀队列
("kill_user"
) 注意商品数量的递减变化 最终的结果是得到一个,不会超售商品数量的 秒杀队列
(kill_user
) - 设置一个或多个线程,也可以是定时任务
去秒杀
队列
(kill_user
) 中提取用户ID
,依次执行下单逻辑
具体的业务处理,要根据实际场景,再做代码优化 … 推荐文章 ——
【用 Redis 轻松实现秒杀系统】
测试参考
☛ 秒杀处理代码参考
代码语言:javascript复制假定要抢购的商品数量为
100
件,即"kill_num"
要提前设置为100
public function testFastSale(){
$redis2 = new Redis();
$redis2->connect('192.168.80.224',6379);
$killNumSet = 100;
//初始化设置秒杀商品数量
//$redis2->set('kill_num',$killNumSet);
//模拟发起请求的用户ID
$userID = rand(1111,2222);
$killNum = $redis2->get('kill_num');
if ($killNum > 0){
//TODO 此时,还有商品可进行抢购
if ($redis2->sIsMember('kill_user_que',$userID)){
//TODO 此时说明用户已经抢到了
$message = 'Sorry,一个账号只能抢一件!';
}else{
$countCanBuyer = $redis2->sCard('kill_user_que');
if ($countCanBuyer >= $killNumSet){
$message = 'Sorry,当前排队已满员!';
}else{
$redis2->watch('kill_num','kill_user','kill_user_que');
$redis2->multi(); //开启事务
$redis2->sAdd('kill_user_que',$userID); //加入集合
$redis2->decr('kill_num'); //商品数量减一
$redis2->rPush('kill_user',$userID);//将用户有序的压入队列
$redis2->exec(); //执行事务
$message = "恭喜,抢购成功!";
}
}
}else{
$message = "Sorry,商品已售完!";
}
return $message;
}
【提示】:
- 为了避免同一用户多抢商品,我使用的是集合
"kill_user_que"
- 而对符合抢购的
用户ID
,使用队列"kill_user"
进行存储 (有序性) 方便后期,对队列的弹出操作(POP
),后续下单业务处理
建议,参考文档 ——
Redis 事务|【菜鸟教程】
☛ ab 模拟高并发请求
- 在此,我使用
ab 压力测试工具
,模拟高并发的请求场景 运行命令如下:"ab -c 50 -n 3000 http://tp5pro.com/index/test"
- 执行完成后,可在
redis
中查看数据("kill_num"、"kill_user"
):
后续逻辑,就要见仁见智了 …
附录
总觉得写得太少,那就多做一点补充吧!
▶ 为什么我设置一个集合、一个队列?
- 一开始,我只是想到使用一个队列
"kill_user"
就好 但是,我发现: 如果同一个用户账号,可能不止一次能抢到商品 这在正常业务中,一般是不允许的 - 为了保证用户的唯一性,想到了改用集合
但是,集合有个缺点:不能记录顺序
为了业务合理性
先排到的人自然会先下订单
虽然,也存在有序集合(
sorted set
)可以满足需求 但是,分析后发现会有很多关于排序取值的逻辑处理,很是繁杂 - 此时想到
首先,使用一个集合
"kill_user_que"
,负责前期对用户ID
的筛选,保证唯一性 然后,将刷选符合要求的用户ID
压入一个队列"kill_user"
后期,只需操作队列"kill_user"
中的数据即可
如此一来,就出现了这种情况,当然这里主要是提供一种解决思路 !
▶ 可不是仅仅有了代码就行了!
对于秒杀类的需求,需要考虑的方面会比较多,可不只有编码
- 一般来说 秒杀最容易引来用户流量(小项目没有客户群,那就么啥讨论性了) 可能要考虑 Redis 集群的部署、负载均衡、带宽等支持 其次,还有前端页面静态化、ajax 等代码的设限 以及流量削峰、限流等操作 最后还要有薅羊毛等恶意刷单的考量、人工最终审核等等 …
- 个人拙见,谢谢 …