当前位置:网站首页>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()]);
}
}
}
边栏推荐
猜你喜欢

Redis master-slave mode, sentinel cluster, fragment cluster

矿山开采虚拟现实vr安全培训提升员工警惕性和防护意识

MySQL基本操作和基于MySQL基本操作的综合实例项目

如何把thinkphp5的项目迁移到阿里云函数计算来应对流量洪峰?

MQTT例程

3D intelligent factory process flow visualization interactive display application advantages

CUDA details GPU architecture

JMeter's BeanShell generates MD5 encrypted data and writes it to the database

Chapter 3 business function development (deletion and modification of clue remarks)

详解JS的四种异步解决方案:回调函数、Promise、Generator、async/await
随机推荐
[quality] code quality evaluation standard
如何把thinkphp5的项目迁移到阿里云函数计算来应对流量洪峰?
Redis主从模式、哨兵集群、分片集群
Ten methods to prevent blackmail software from attacking data
一文读懂Okaleido Tiger近期动态,挖掘背后价值与潜力
家庭亲戚关系计算器微信小程序源码
Altium designer outputs Gerber and other production documents
Servlet三种实现方式
一款好看的iapp捐赠榜单源码
ES6事件绑定(v-on用法)
Shell 脚本 快速入门 -01
time_wait和close_wait产生原因
What are the TCP retransmission mechanisms?
Installation guide for proftpd Secure FTP server with TLS encryption enabled
Three expiration strategies
CUDA details GPU architecture
Continuous learning / life long learning
代码随想录笔记_哈希_349两个数的交集
Experiment 2: Arduino's tricolor lamp experiment
ES6 event binding (v-on usage)