当前位置:网站首页>Redis队列实现秒杀
Redis队列实现秒杀
2022-07-29 02:10:00 【陈卿诺语】
秒杀系统特点
1、抢购人数远多于库存,读写并发巨大。
2、库存少,有效写少。
3、写需强一致性,商品不能卖超。
4、读强一致性要求不高。
5、稳定性难:高并发下,某个小依赖可能直接造成雪崩、流量预期难精确,过高也造成雪崩。分布式集群,机器多,出故障的概率高。
6、准确性难:库存、抢购成功数,创建订单数之间的一致性。
7、高性能难:有限成本下需要做到极致的性能。
秒杀系统——架构原则
1、稳定性:减少第三方依赖,同时自身服务部署也需做到隔离。压测、降级、限流方案、确保核心服务可用。需健康度检测机制,整个链路避免单点。
2、高性能:缩短单请求访问路径,减少IO。减少接口数,降低吞吐数据量,请求次数减少。
秒杀服务核心实现
1、怎样设计秒杀服务:满足基本需求,做到单服务极致性能。请求链路流量优化,从客户端到服务端每层优化。稳定性建设。
2、基本需求:扣库存、查库存、排队进度。(做到单服务极致性能)。查订单详情、创建订单,支付订单。(库存少抢购人数远多于库存,读写并发高)
基本需求——扣库存方案
1、下单减库存?
并发请求——>创建订单——>扣库存——>支付 这种流程不会超卖,但问题是如果有人恶意下单不支付,占用库存。
2、支付减库存?
并发请求——>创建订单——>支付——>扣库存 这种流程是支付一次扣一次库存,如果用户把商品买完了,别的用户下不了订单或者订单超卖。
3、预扣库存?
并发请求——>扣库存——>创建订单——>支付——>10分钟内不支付取消订单,加库存。
采用预扣库存方案比较好。
wxml
<view>
<l-countdown time-type="second" time="{
{expire_time}}" bind:linend="changeBtn"/>
<view><image src="{
{Detail.image}}" bindtap="image"></image></view>
<view>{
{Detail.store_product.store_name}}</view>
<view>{
{Detail.price}}</view>
<view>
<l-button disabled="{
{ disabled }}" bind:lintap="buyGoods" type="error" data-id="{
{Detail.id}}">立即秒杀</l-button>
</view>
</view>js
// pages/Detail/Detail.js
Page({
/**
* 页面的初始数据
*/
data: {
expire_time:0,
disabled:false,
expire_time:'',
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
let that = this
let sid = options.id
wx.request({
url: 'http://www.wej.com/index.php/api/seckill/goodsDetail', //仅为示例,并非真实的接口地址
data: {
sid
},
header: {
'content-type': 'application/json' // 默认值
},
success (res) {
console.log(res.data)
let newdate = Math.round(new Date().getTime() / 1000).toString()
let expire_time = res.data.data.start_time - newdate
console.log(expire_time)
that.setData({
Detail:res.data.data,
expire_time:expire_time
})
if(expire_time > 0){
that.setData({
disabled:true
})
}else{
that.setData({
disabled:false
})
}
}
})
},
buyGoods(c){
clearTimeout(this.TimeID);
this.TimeID = setTimeout(() => {
let goods_id = c.currentTarget.dataset.id
wx.request({
url: 'http://www.wej.com/index.php/api/seckill/snap_up', //仅为示例,并非真实的接口地址
data: {
goods_id
},
header: {
'content-type': 'application/json' // 默认值
},
success (res) {
console.log(res.data)
}
})
}, 1000);
},
changeBtn(){
console.log('秒杀开始')
this.setData({
disabled:false
})
},
image(c){
wx.previewImage({
current: this.data.Detail.image, // 当前显示图片的 http 链接
urls: [this.data.Detail.image] // 需要预览的图片 http 链接列表
})
},
})json
{
"usingComponents": {
"l-countdown":"/dist/countdown",
"l-button":"/dist/button"
}
}<?php
namespace App\Http\Controllers;
use App\Models\AddressInfo;
use App\Models\StoreOrder;
use App\Server\Snowflake;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
class Seckill extends Controller
{
/**
* 数据预热
* @return \Illuminate\Http\JsonResponse
*/
public function activity()
{
$result = \App\Models\Seckill::with('StoreProduct')
->get()->toArray();
foreach ($result as $val){
//生成对应商品库存队列
$goods = "activity_goods_".$val['product_id'];
for ($i=0; $i < $val['stock']; $i++) {
Redis::lpush($goods,1);
}
}
return response()->json(['code' => 20000, 'msg' => '查询成功', 'data' => $result]);
}
/**、
* 秒杀列表
* @return \Illuminate\Http\JsonResponse
*/
public function activityList()
{
$result = \App\Models\Seckill::with('StoreProduct')
->get()->toArray();
return response()->json(['code' => 20000, 'msg' => '查询成功', 'data' => $result]);
}
/**
* 秒杀商品详情
* @return \Illuminate\Http\JsonResponse
*/
public function goodsDetail()
{
$goods_id = request()->get('sid');
$result = \App\Models\Seckill::with(['StoreProduct'])
->where('product_id',$goods_id)
->first();
return response()->json(['code' => 20000, 'data' => $result, 'msg' => '查询成功']);
}
/**
* 校验库存
* @return \Illuminate\Http\JsonResponse
*/
public function snap_up(){
//用户ID
$userID = 1;
//商品ID
$goodsID = request()->get('goods_id');
//对应商品库存队列
$goods = "activity_goods_".$goodsID;
//对应商品抢购成功用户集合 {1,3,4}
$robSuccessUser = "success_user".$goodsID;
//进行判断当前用户是否在抢成功的队列里面
$result = Redis::sismember($robSuccessUser,$userID);
//如果你在这里面,就抢完了
if ($result) {
//如果抢购成功 返回状态码,进行下单
return response()->json(['code' => 20000, 'data' => '', 'msg' => '已经抢购过了']);
}
//减库存,把队列里面的数据从左边 头
$count = Redis::lpop($goods);
if (!$count) {
//如果抢购成功 返回状态码,进行下单
return response()->json(['code' => 20001, 'data' => '', 'msg' => '已经抢光了哦']);
}
//把当前这个秒杀的uid存储到抢购成功的队列里set
$success = Redis::sadd($robSuccessUser, $userID);
if(!$success){
//已经在成功队列里了,加回库存,防止的是同个用户并发请求
Redis::lpush($goods, 1);
//如果抢购成功 返回状态码,进行下单
return response()->json(['code' => 20002, 'data' => '', 'msg' => '已经抢购过了']);
}
//如果抢购成功 返回状态码,进行下单
return response()->json(['code' => 20000, 'data' => '', 'msg' => '秒杀成功']);
}
/**
* 生成订单
* @return false|\Illuminate\Http\JsonResponse|string
*/
public function createOrder(){
//用户ID
$userID = request()->get('userID');
//商品ID
$goodsID = request()->get('goods_id');
//地址ID
$address_id = request()->get('address_id');
//对应商品抢购成功用户集合
$robSuccessUser = "success_user".$goodsID;
//进行判断当前用户是否在抢成功的队列里面
$result = Redis::sismember($robSuccessUser,$userID);
//如果你在这里面,就抢完了
if (!$result) {
//如果抢购成功 返回状态码,进行下单
return response()->json(['code' => 20003, 'data' => '', 'msg' => '手慢了!']);
}
DB::beginTransaction();
try{
//减库存
$shopData = \App\Models\Seckill::with('StoreProduct')
->where('product_id',$goodsID)
->first()->toArray();
$shopStock = \App\Models\Seckill::where('product_id',$goodsID)->update(['stock' => $shopData['stock'] - 1]);
if (!$shopStock) return json_encode(['code' => 50000,'msg' => '下单失败','data' => []]);
//地址
$address_info = AddressInfo::find($address_id)->toArray();
//生成订单
Snowflake::machineId($userID);
$order_id = substr(date('Ymd'),2).'-'.Snowflake::createOnlyId();
$data = [
'order_id' => $order_id,
'uid' => $userID,
'real_name' => $address_info['real_name'],
'user_phone' => $address_info['phone'],
'user_address' => $address_info['detail'],
'pay_price' => $shopData['store_product']['price'],
'pay_time' => time()
];
$orderAdd = StoreOrder::insert($data);
if (!$orderAdd) return json_encode(['code' => 50000,'msg' => '下单失败','data' => []]);
DB::commit();
//下单成功,跳转支付页面
return response()->json(['code' => 20000, 'data' => '', 'msg' => '下单成功!']);
}catch (\Exception $e){
DB::rollBack();
return response()->json(['code' => 50000, 'data' => '', 'msg' => $e->getMessage()]);
}
}
}
边栏推荐
- How does the Devops team defend against API attacks?
- Only when you are far away will you miss
- Never pass a request to an asynchronous thread. There is a hole
- Talk about the implementation principle of feign
- 网络基础概论
- Happy childhood
- 主从复制及其原理
- 如果非要在多线程中使用 ArrayList 会发生什么?
- Remember error scheduler once Asynceventqueue: dropping event from queue shared causes OOM
- 用于校园流浪猫信息记录和分享的小程序源码/微信云开发中大猫谱小程序源码
猜你喜欢

Explain asynchronous tasks in detail: task status and lifecycle management

CUDA details GPU architecture

Esbuild Bundler HMR

HTTP cache

用于校园流浪猫信息记录和分享的小程序源码/微信云开发中大猫谱小程序源码

Mqtt routine

Talk about the implementation principle of feign

全新UI四方聚合支付系统源码/新增USDT提现/最新更新安全升级修复XSS漏洞补单漏洞

如果非要在多线程中使用 ArrayList 会发生什么?

What should I do if excel opens a CSV file containing Chinese characters and there is garbled code?
随机推荐
Ordinary happiness
JMeter's BeanShell generates MD5 encrypted data and writes it to the database
Time pit in MySQL driver
Happy childhood
3种过期策略
深度剖析 —— 预处理
新版海螺影视主题模板M3.1全解密版本多功能苹果CMSv10后台自适应主题开源全解密版
Talk about the implementation principle of feign
“12306”的架构到底有多牛逼?
IOT components
Driverless obstacle avoidance technology
无线振弦采集系统工作流程
ES6 detailed quick start!
VR safety training of mine mining virtual reality improves employees' vigilance and protection awareness
Read the recent trends of okaleido tiger and tap the value and potential behind it
MySQL驱动中关于时间的坑
ES2022 的 8 个实用的新功能
MySQL基本操作和基于MySQL基本操作的综合实例项目
What if there is not enough time for adequate testing?
手把手教你安装VSCode(附带图解步骤)