redis秒杀(PHP版本)

前提提要

今天产品端提了个需求,院校组要求借调我去帮忙,因为我以前做过商城,现在他们需求做一个积分商城,需要做一个秒杀模块,结果毫无意外的我被借调过去了,刚好可以复习一下以前的知识,现在介绍一下redis的秒杀机制,首先大家需要了解redis的基础信息,可以参考小编的前面的文章 https://blog.csdn.net/masterphp/article/details/130728703,现在开始介绍redis秒杀,只要有两种方式来实现,锁和队列

秒杀流程概述

在进行Redis的实现之前,我们先简要介绍一下秒杀的流程:

1.用户在前端页面选择要秒杀的商品,并提交订单;

2.系统校验用户提交的订单是否合法,如商品库存是否充足、用户是否符合参加条件等;

3.系统将订单信息写入数据库,并返回给用户订单处理中的状态;

4.用户在订单处理中不断轮询订单状态,直到订单完成。

在秒杀流程中,系统需要进行多次校验和处理,其中最关键的部分就是判断商品库存是否充足,这部分需要保证数据的可靠性和并发性。

秒杀实现原理

1.使用Redis队列存储订单:秒杀活动中,会有大量的订单请求同时涌入系统,若采用传统的关系型数据库进行存储,会导致并发量过大,数据库连接数量过多,从而降低系统的性能和稳定性。为了解决这个问题,我们可以使用队列来存储订单信息。Redis中提供了List类型的数据结构,可用作队列的实现。通过将订单信息放入Redis队列中,系统可以快速处理大量的订单请求,并以先到先服务的顺序进行处理。

2.使用Redis预减库存:秒杀活动中,需要对商品库存进行实时监控,否则会因为库存不够而导致订单处理失败。但是,如果每个订单都从数据库中查询商品库存,将会增加数据库的压力,导致系统并发性能降低。

为了解决这个问题,我们可以使用Redis的预减库存策略。当用户抢购时,我们先使用Redis缓存中的商品库存信息,并实时更新该缓存信息,如果库存已经减少至0,则直接返回秒杀失败。

3.使用Redis分布式锁:在秒杀活动中,虽然我们使用Redis队列和预减库存,但是系统仍然需要面对大量的并发请求。在这些请求中,可能会有多个用户同时对同一个商品进行抢购,这就需要我们使用分布式锁来保证数据的可靠性。Redis的分布式锁使用方式非常简单,在抢购开始时,我们使用Redis的SETNX操作来尝试获取分布式锁,如果获取成功,则对库存进行操作,操作完成后,释放分布式锁。如果获取分布式锁失败,则需等待一段时间重试。

乐观锁实现方式

//添加商品库存

public function addGoodsStock(Request $request)

{

//接受数据

$goods_id = $request->input('goods_id');

$store = $request->input('store');

//设置商品库存的key

key = 'seckill_goods_id_'.goods_id;

res = Redis::set(key,$store);

return $res;

}

//用户秒杀商品

public function buy(Request $request)

{

//接受数据

$uid = $request->input('uid');

$goods_id = $request->input('goods_id');

//商品库存key

key = 'seckill_goods_id_'.goods_id;

//监听对应的key,事务提交之前,如果key被修改,则事务被打断

Redis::watch($key);

//获取商品库存

store = Redis::get(key);

//抢购成功用户集合key

$setKey = 'userGoodsSuccess';

//判断该用户是否已经抢购过(该用户id是否在抢购用户集合中)

userBuyStatus = Redis::sismember(setKey,$uid);

if($userBuyStatus){

return '您已抢过!';

}

if($store){

//记录用户信息,更新库存(保证这一组命令,要么全部成功,要么都不成功)

Redis::multi();//开始事务

Redis::decr($key);//减少库存

//将用户id添加到抢购成功用户集合中

Redis::sadd(setKey,uid);

$result = Redis::exec();//提交,判断当前的key是否被某个客户端修改了

//结果判断

if($result){

//操作数据库,修改商品库存销量

DB::table('goods')->where('id', $goods_id)->decrement('stock', 1, ['sale' => DB::raw('`sale`+1')]);

//创建订单信息

return '抢购成功!';

}else{

return '抢购失败,请重试!';

}

}else{

return '已抢光!';

}

}

队列实现方式

//初始化库存队列

public function init(Request $request)

{

//接受参数

$goods_id = $request->input('goods_id');

$store = $request->input('store');

//获取key

key = 'list_seckill_goods_id_'.goods_id;

//防止对已经设置过的商品库存进行覆盖

if(!empty(Redis::llen($key))) {

return '已经设置了库存';

}

//初始化缓存,删除抢购用户id队列key和成功信息保存key,这个是抢购时生成的,防止错乱,初始化删除

userListKey = 'user_goods_id_'.goods_id;

Redis::command('del', [$userListKey, 'success']);

// 将商品存入Redis链表中

for($i = 1; $i <= $store; $i++) {

Redis::lpush($key, $i);

}

//设置过期时间

Redis::expire($key, 120);

echo '商品存入队列成功,数量:'.Redis::llen($key);

}

//redis队列抢购

public function start(Request $request)

{

// 模拟随机登录用户

$uid = mt_rand(1, 9999);

//$uid = $request->input('uid');

$goods_id = $request->input('goods_id');

//获取key

key = 'list_seckill_goods_id_'.goods_id;

//从链表的头部删除一个元素,返回删除的元素,因为pop操作是原子性,即使很多用户同时到达,也是依次执行

count = Redis::lpop(key);

if (!$count) {

return '已抢光!';

}

//已抢购用户id队列

userListKey = 'user_goods_id_'.goods_id;

//判断该用户是否已经抢购过(该用户id是否在抢购用户集合中)

userBuyStatus = Redis::sismember(userListKey,$uid);

if($userBuyStatus){

return '您已抢过!';

}

//将用户id添加到抢购成功用户集合中

Redis::sadd(userListKey,uid);

msg = '抢到的人为:'.uid.',抢到商品的顺序为第'.$count.'个';

Redis::lpush('success', $msg);

//操作数据库,修改商品库存销量

DB::table('goods')->where('id', $goods_id)->decrement('stock', 1, ['sale' => DB::raw('`sale`+1')]);

//创建订单信息

return '恭喜您抢购成功!';

}

相关推荐
brrdg_sefg3 分钟前
Rust 在前端基建中的使用
前端·rust·状态模式
言之。13 分钟前
redis延迟队列
redis
m0_7482309428 分钟前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel
qq_5895681036 分钟前
Echarts的高级使用,动画,交互api
前端·javascript·echarts
hanbarger1 小时前
nosql,Redis,minio,elasticsearch
数据库·redis·nosql
弗罗里达老大爷2 小时前
Redis
数据库·redis·缓存
黑客老陈2 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
正小安2 小时前
Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
前端·vite
暴富的Tdy2 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js