Redis+PHP秒杀设计
2021-05-14 13:28
标签:theme 次数 商城 xtend tput bsp 概率 err 提示 Redis+PHP秒杀设计 标签:theme 次数 商城 xtend tput bsp 概率 err 提示 原文地址:https://www.cnblogs.com/qiusanqi/p/11988445.html
1 php
2 class Base{
3 static $redisObj;
4 /*通过单例方式初始化一个redis连接*/
5 static function conRedis($config = array()){
6 if(self::$redisObj) return self::$redisObj;
7 self::$redisObj = new \Redis();
8 self::$redisObj->connect(‘127.0.0.1‘, 6379);
9 return self::$redisObj;
10 }
11 /*接口输出格式化*/
12 static function output($data = array(), $errNo = 0, $errMsg = ‘ok‘){
13 $res[‘errno‘] = $errNo;
14 $res[‘errmsg‘] = $errMsg;
15 $res[‘data‘] = $data;
16 echo json_encode($res);
17 exit();
18 }
19 }
1 php
2 include(‘base.php‘);
3 class Api extends Base
4 {
5 //共享信息,存储在redis中,以hash表的形式存储,%s变量代表的是商品id
6 static $userId;
7 static $productId;
8 static $REDIS_REMOTE_HT_KEY = "product_%s"; //共享信息key
9 static $REDIS_REMOTE_TOTAL_COUNT = "total_count"; //商品总库存
10 static $REDIS_REMOTE_USE_COUNT = "used_count"; //已售库存
11 static $REDIS_REMOTE_QUEUE = "c_order_queue"; //创建订单队列
12 static $APCU_LOCAL_STOCK = "apcu_stock_%s"; //总共剩余库存
13 static $APCU_LOCAL_USE = "apcu_stock_use_%s"; //本地已售多少
14 static $APCU_LOCAL_COUNT = "apcu_total_count_%s"; //本地分库存分摊总数
15 public function __construct($productId, $userId)
16 {
17 self::$REDIS_REMOTE_HT_KEY = sprintf(self::$REDIS_REMOTE_HT_KEY, $productId);
18 self::$APCU_LOCAL_STOCK = sprintf(self::$APCU_LOCAL_STOCK, $productId);
19 self::$APCU_LOCAL_USE = sprintf(self::$APCU_LOCAL_USE, $productId);
20 self::$APCU_LOCAL_COUNT = sprintf(self::$APCU_LOCAL_COUNT, $productId);
21 self::$APCU_LOCAL_COUNT = sprintf(self::$APCU_LOCAL_COUNT, $productId);
22 self::$userId = $userId;
23 self::$productId = $productId;
24 }
25 static function clear(){
26 apcu_delete(self::$APCU_LOCAL_STOCK);
27 apcu_delete(self::$APCU_LOCAL_USE);
28 apcu_delete(self::$APCU_LOCAL_COUNT);
29
30 }
31 /*查剩余库存*/
32 static function getStock()
33 {
34 $stockNum = apcu_fetch(self::$APCU_LOCAL_STOCK);
35 if ($stockNum === false) {
36 $stockNum = self::initStock();
37 }
38 self::output([‘stock_num‘ => $stockNum]);
39 }
40 /*抢购-减库存*/
41 static function buy()
42 {
43 $localStockNum = apcu_fetch(self::$APCU_LOCAL_COUNT);
44 if ($localStockNum === false) {
45 $localStockNum = self::init();
46 }
47 $localUse = apcu_inc(self::$APCU_LOCAL_USE);//本已卖 + 1
48 if ($localUse > $localStockNum) {//抢购失败 大部分流量在此被拦截
49 echo 1;
50 self::output([], -1, ‘该商品已售完‘);
51 }
52 //同步已售库存 + 1;
53 if (!self::incUseCount()) {//改失败,返回商品已售完
54 self::output([], -1, ‘该商品已售完‘);
55 }
56 //写入创建订单队列
57 self::conRedis()->lPush(self::$REDIS_REMOTE_QUEUE, json_encode([‘user_id‘ => self::$userId, ‘product_id‘ => self::$productId]));
58 //返回抢购成功
59 self::output([], 0, ‘抢购成功,请从订单中心查看订单‘);
60 }
61 /*创建订单*/
62 /*查询订单*/
63 /*总剩余库存同步本地,定时执行就可以*/
64 static function sync()
65 {
66 $data = self::conRedis()->hMGet(self::$REDIS_REMOTE_HT_KEY, [self::$REDIS_REMOTE_TOTAL_COUNT, self::$REDIS_REMOTE_USE_COUNT]);
67 $num = $data[‘total_count‘] - $data["used_count"];
68 apcu_add(self::$APCU_LOCAL_STOCK, $num);
69 self::output([], 0, ‘同步库存成功‘);
70 }
71 /*私有方法*/
72 //库存同步
73 private static function incUseCount()
74 {
75 //需要查远端的总库存和已经售卖的计数
76 //同步远端库存时,需要经过lua脚本,保证不会出现超卖现象
77 //因为redis是单进程模型,可有效的避免两个用户同时进行查库存和扣库存
78 $script = eof
79 local key = KEYS[1]
80 local field1 = KEYS[2]
81 local field2 = KEYS[3]
82 local field1_val = redis.call(‘hget‘, key, field1)//总库存
83 local field2_val = redis.call(‘hget‘, key, field2)//已经售卖的计数
84 if(field1_val>field2_val) then
85 return redis.call(‘HINCRBY‘, key, field2,1)
86 end
87 return 0
88 eof;
89 //eval()执行lua脚本的方法
90 return self::conRedis()->eval($script,[self::$REDIS_REMOTE_HT_KEY, self::$REDIS_REMOTE_TOTAL_COUNT, self::$REDIS_REMOTE_USE_COUNT] , 3);
91 }
92 /*初始化本地数据*/
93 private static function init()
94 {
95 apcu_add(self::$APCU_LOCAL_COUNT, 150);
96 apcu_add(self::$APCU_LOCAL_USE, 0);
97 }
98 static function initStock(){
99 $data = self::conRedis()->hMGet(self::$REDIS_REMOTE_HT_KEY, [self::$REDIS_REMOTE_TOTAL_COUNT, self::$REDIS_REMOTE_USE_COUNT]);
100 $num = $data[‘total_count‘]- $data["used_count"];
101 apcu_add(self::$APCU_LOCAL_STOCK, $num);
102 return $num;
103 }
104 }
105 try{
106 $act = $_GET[‘act‘];
107 $product_id = $_GET[‘product_id‘];
108 $user_id = $_GET[‘user_id‘];
109 $obj = new Api($product_id, $user_id);
110 if (method_exists($obj, $act)) {
111 $obj::$act();
112 die;
113 }
114 echo ‘method_error!‘;
115 } catch (\Exception $e) {
116 echo ‘exception_error!‘;
117 var_dump($e);
118 }