<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## 抽獎 抽獎類似秒殺,但又不同,雖然抽獎針對的是單個活動,但是有不同的獎品,不同的庫存。 也類似群紅包,有概率的成分,但獎品不是臨時制成的而是提前設置好概率而已,并且不是每個人都能中獎。 ---- ### 活動初始化: 每日 0點初始化: 1. 活動數據 放到 redis key: activity.1 data: dataJson 2. 獎品庫存 放到 redis key: activity:prize:stock.1 data: (total - issued_num) key: activity:prize:day_stock.1 data: (day_max_issued_num - 今日已發放數量) > 相當于有兩個庫存 3. 獎品列表 放到 redis key: activity:prizes.1 data: dataJson (id,status,probability,type,attr_json) > 這些數據如果后臺有更新時,需要有相應的機制同步跟更新 redis ------ ### 偽代碼 ``` if 活動時間 if 用戶今日參與次數 INCR(k) < num 允許參與 搖獎 概率計算 ( 1. 取得全部 有庫存 且 今日發放次數沒超限 的獎品 2. 根據概率搖獎 // 獎品列表如果沒有,那么中獎概率一直都是0 ) if 未中獎 參與記錄落盤(未中獎) else 中獎 獎品發放 if 目標獎品是否有庫存 && 是否發放超限 if 扣減庫存:兩個庫存扣減成功 參與記錄落盤(中獎) else 重新搖獎(注意恢復庫存扣減) else 重新搖獎 ``` > 每日發放數量有限,其實也相當于一個庫存,每日庫存 ---- ### 分析 其實單純用數據庫也能控制不超賣,但數據庫在并發場景下性能非常差,update都是鎖,所以主要目的是將請求擋在mysql之上,盡量不與數據庫直接操作,只在真正要落盤時才去讀寫數據庫。 秒殺方案中,如果 redis 掛了怎么辦,其實沒問題,掛了只是 程序活數據 崩了,這和 程序執行過程中的上下文 沒什么區別,我們 業務數據只要完整落盤了 就沒問題,就是安全的,加載到 redis 中的只是程序程序運行中要使用到的數據而已,可以叫 程序數據 或者是 方案數據 ,運行數據 都行,這并不是業務數據,理解這點很重要。類似mq消息也是這樣,只要業務數據安全,業務就是安全的。 redis 掛了就是服務不可用了,活動就不能進行,就需要暫停活動,暫停業務了,等人工修復完成了才能繼續活動。 `INCR`/`DECR`相當于mysql的 update 和 select 并且是加鎖了的,相當于兩條命令合成一條原子性命令了。 ---- ### 方案實現 上面使用incr太麻煩了,還要處理”減回來“,如果要操作多個key更麻煩,還是使用執行lua腳本的方式更好,簡單直觀。 **表定義:** ```sql # 抽獎活動 表 DROP TABLE IF EXISTS `s_luck_activity`; CREATE TABLE `s_luck_activity` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL DEFAULT '' COMMENT '活動標題', `start_time` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '活動有效期:開始', `end_time` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '活動有效期:結束', `desc` varchar(500) NOT NULL DEFAULT '' COMMENT '活動備注', `create_time` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '創建時間', `update_time` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '更新時間', `no_prize` smallint(1) NOT NULL DEFAULT 0 COMMENT '活動不中獎概率(1 ~ 10000)', `rule_json` varchar(500) NOT NULL DEFAULT '' COMMENT '規則配置', `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '活動狀態:0-正常,1-停用', PRIMARY KEY (`id`) ) ENGINE=innodb AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='抽獎活動 表'; -- 抽獎活動 獎品 表 DROP TABLE IF EXISTS `s_luck_prize`; CREATE TABLE `s_luck_prize` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `activity_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '活動id', `title` varchar(255) NOT NULL DEFAULT '' COMMENT '獎品名稱', `img` varchar(255) NOT NULL DEFAULT '' COMMENT '獎品圖片', `desc` varchar(500) NOT NULL DEFAULT '' COMMENT '獎品描述', `total` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '總數量', `day_max_issued_num` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '每日最多發放數量', `issued_num` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '已發放數量', `probability` tinyint(1) NOT NULL DEFAULT 0 COMMENT '獎品中獎概率(1 ~ 100)', `type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '獎品類型:0-普通獎品,1-紅包(中獎后會自動發放)', `attr_json` text NULL COMMENT '屬性,普通紅包沒有這個', `create_time` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '創建時間', `update_time` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '更新時間', `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '獎品狀態:0-正常,1-下架', PRIMARY KEY (`id`) ) ENGINE=innodb AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='抽獎活動 獎品 表'; # 抽獎記錄 表 DROP TABLE IF EXISTS `s_luck_record`; CREATE TABLE `s_luck_record` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `activity_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '活動id', `user_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '用戶id', `prize_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '獎品id', `create_time` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '創建時間', `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '活動狀態:0-未中獎,1-中獎', PRIMARY KEY (`id`) ) ENGINE=innodb AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='抽獎記錄 表'; ``` **lua腳本(解決并發問題,保證不超賣的核心):** lua腳本執行方式保證了批量的命令被整體的串行化了,所以不會有并發問題,本質上可以理解為用了鎖,鎖的本質就是串行排隊。 為什么 lua可以做到批量命令的串行? > 不是lua提供了批量命令的串行,而是 `redis.exex` 命令以 lua腳本為參數了而已,本質上 `redis.exex` 是一個命令,和其他命令一樣,其本身就是串行的,而現在這個命令就是以 批量命令為參數 ```lua -- lua腳本: 抽獎功能 -- last update: 2019-08-16 18:51:52 -- 獎品:單個獎品 庫存 local prizeSurplus -- 獎品:單個獎品 每日 發放數量 local prizeIssuedDay -- 活動:單個活動 每日每用戶 中獎次數 local activityWinningDay local day = tostring(ARGV[1]) local userId = tonumber(ARGV[2]) local activityId = tonumber(ARGV[3]) local prizeId = tonumber(ARGV[4]) -- 活動:沒人每日中獎次數限額 local activityWinningDayQuota = tonumber(ARGV[5]) -- 獎品每日發放限額 local prizeIssuedDayQuota = tonumber(ARGV[6]) -- 獎品:單個獎品 庫存 key local prizeSurplusKey = "luck:prizeSurplus:" .. prizeId -- 獎品:單個獎品 每日 發放數量 key local prizeIssuedDayKey = "luck:prizeIssuedDayKey:" .. day .. '_' .. prizeId -- 活動:單個活動 每日每用戶 中獎次數 key local activityWinningDayKey = "luck:activityWinningDay:" .. day .. '_' .. activityId .. '_' .. userId ------------------------------------------[1]------------------------------------------ -- 活動: 初始化 每日中獎次數 activityWinningDay = redis.call("GET", activityWinningDayKey); if activityWinningDay == false then activityWinningDay = 0 end activityWinningDay = tonumber(activityWinningDay) -- 活動: 判斷每日中獎次數是否超額 if activityWinningDayQuota > 0 then if activityWinningDay >= activityWinningDayQuota then -- 活動: 每日中獎次數超額 return 1 end end ------------------------------------------[2]------------------------------------------ -- 獎品: 初始化 今日已發放數量 prizeIssuedDay = redis.call("GET", prizeIssuedDayKey); if prizeIssuedDay == false then prizeIssuedDay = 0 end prizeIssuedDay = tonumber(prizeIssuedDay) -- 獎品: 判斷今日已發放數量 if prizeIssuedDayQuota > 0 then if prizeIssuedDay >= prizeIssuedDayQuota then -- 獎品: 超過每日最大發放數量 return 2 end end ------------------------------------------[3]------------------------------------------ -- 獎品: 初始化 庫存 prizeSurplus = redis.call("GET", prizeSurplusKey); if prizeSurplus == false then -- 獎品: 庫存還沒準備 return 3 end prizeSurplus = tonumber(prizeSurplus) -- 獎品: 判斷獎品是否有庫存 if prizeSurplus <= 0 then -- 獎品: 沒有庫存了 return 4 end ------------------------------------------[4]------------------------------------------ -- 獎品: 扣減庫存 redis.call("DECR", prizeSurplusKey); -- 獎品: 今日已發放數量 +1 redis.call("INCR", prizeIssuedDayKey); -- 活動: 增加中獎次數 +1 redis.call("INCR", activityWinningDayKey); return 0 ``` **抽獎邏輯:** ```php <?php namespace app\includes\luck; // todo: 解決更新活動,獎品數據時 如何更新正在進行的活動數據 /** * 紅包基礎類 */ class Luck { protected $db; protected $redis; protected $userId; protected $activityId; protected $activity; protected $prizeList; protected $day; protected $time; public function __construct($activityId) { $this->db = $GLOBALS['db']; $this->redis = $GLOBALS['redis']; $this->userId = $_SESSION['user_id']; $this->activityId = $activityId; $this->day = local_date('Y-m-d'); $this->time = time(); // 用戶參與抽獎時,實例化此類時,活動數據就從redis加載到此類中作為屬性了(私有內存數據,程序數據),與所有外部隔離 // 除了庫存外,不會再從外部讀任何數據了 // 即使想更新redis也請在 此類被初始化之前,否則這里過后是不會再讀活動數據了,就沒有機會再干預正在進行的抽獎了 // 當然如果外部修改了庫存,則情況會復雜一些,后面再詳細討論 $this->activity = $this->redis->get('luck:activity:' . $this->activityId); $this->prizeList = $this->redis->get('luck:prizes:' . $this->activityId); } public function setActivityId($activityId) { $this->activityId = $activityId; } // 初始化活動,給定時任務用的 public function init() { $this->_loadData(); } // 緩存腳本 public function _scriptLoad() { $scriptStr = file_get_contents(__DIR__ . '/luck.lua'); $tag = $this->redis->script('load', $scriptStr); // 這個 $tag 其實時 scriptStr 哈希 $this->redis->set('luck:script_luck_lua_tag', $tag); return $tag; } // 執行lua腳本 public function _evalSha($prizeId) { $tag = $this->redis->get('luck:script_luck_lua_tag'); // 現在需要調試lua代碼,等上線時再打開 // if (!$tag || !$this->redis->script('exists', $tag)[0]) { $tag = $this->_scriptLoad(); // } return $this->redis->evalSha($tag, [ $this->day, $this->userId, $this->activityId, $prizeId, $this->activity['rule_data']['u_d_winning_num'], // 活動每日最大參與次數 $this->prizeList[$prizeId]['day_max_issued_num'], // 獎品每日最大發放數量 ], 0); } // 搖獎 public function shake() { $db = $this->db; $time = $this->time; if (true !== ($_check = $this->_checkActivityQualification())) { return $_check; } $res = []; // 搖獎 $_prizeList = $this->_getPrizeList(); // 獎品完了,提示沒中獎就行了 if (empty($_prizeList)) { return ['error' => 6, 'message' => '很遺憾,未中獎']; } $_probabilityRes = $this->_probability($_prizeList); // 是否中獎標記 $_isLuck = false; if (!$_probabilityRes) { // 沒中 $res = ['error' => 6, 'message' => '很遺憾,未中獎']; } else { $prizeId = $_probabilityRes['id']; // 扣減庫存 // 扣減成功 中獎 $code = $this->_evalSha($prizeId); $scriptRes = $this->_parseScriptRes($code); if ($scriptRes['error'] === 0) { // 這里才是真正的中獎 $_isLuck = true; } else { $res = $scriptRes; } } if ($_isLuck) { // 中獎 // 開啟事務,數據落盤(更新獎品的已發放數量 +1) // 參與人很多,中獎概率很小,所以這里訪問量不大,直接落盤應該沒事,后面有性能問題再用MQ去落盤就行了 $errMsg = ''; try { $db->startTrans(); $db->lock(true)->getRow("SELECT id FROM s_luck_prize WHERE id = {$prizeId} "); $db->autoExecute('s_luck_prize', [ 'issued_num' => ['exp', '`issued_num` + 1'], ], 'UPDATE', " id = {$prizeId} "); $db->commit(); } catch (Exception $e) { $db->rollback(); $errCode = $e->getCode(); $errMsg = $e->getMessage(); } $num = count($list); if ($errMsg == '') { $res = ['error' => 0, 'message' => '恭喜,您中獎了', 'data' => $this->prizeList[$prizeId]]; } else { // 更新db數據失敗,應該不會出現這種清空,redis庫存扣減成功了,db落盤失敗了,上線了不允許出現這種問題 $res = ['error' => $errCode, 'errMsg' => $errMsg]; // 嚴重錯誤:做個日志 trace($res, '>luck-error'); } } $insertData = [ 'activity_id' => $this->activityId, 'user_id' => $this->userId, 'prize_id' => $prizeId, 'create_time' => $time, 'status' => $_isLuck ? 1 : 0, ]; if ($_isLuck) { // 中獎時,所中獎品,隨之快照落盤,因為后面獎品可能會被編輯更新,所以必須進行快照 $insertData['prize_json'] = json_encode($this->prizeList[$prizeId], JSON_UNESCAPED_UNICODE); } // 記錄抽獎記錄,順序插入性能應該也不是問題,(后面有性能問題時可以移到MQ中去) // db性能問題主要是更新 $db->autoExecute('s_luck_record', $insertData, 'INSERT'); // 寫入抽獎記錄 return $res; } // 解析lua腳本返回的狀態碼 public function _parseScriptRes($code) { $_list = [ 0 => '庫存扣減成功', // 只有這種狀態才認為是本次真正中獎了 1 => '活動: 超過每日中獎次數限制', 2 => '獎品: 超過每日最大發放數量', 3 => '庫存還沒準備', // 活動沒有初始化好 // 好巧不巧,中的獎品沒有庫存了,這種情況很少,相當于是兩個人都中同一個獎品了,但獎品只剩下一個,第二個人就自認倒霉了 4 => '來遲一步,獎品派發完了', ]; if ($code === false) { // 腳本錯誤 return ['error' => 444, 'message' => '服務忙,請稍后再試']; } if (isset($_list[$code])) { return ['error' => $code === 0 ? $code : (500 + $code), 'message' => $_list[$code]]; } } // 裝載數據 public function _loadData() { $this->_loadActivityData(); $this->_loadPrizeData(); } // 裝載活動數據 public function _loadActivityData() { $activity = $this->db->lock(true)->getRow("SELECT * FROM s_luck_activity WHERE id = {$this->activityId} AND state = 1 AND status = 0 "); if ($activity) { $activity['rule_data'] = json_decode($activity['rule_json'] ?: '{}', true); // 活動信息緩存 $this->redis->set('luck:activity:' . $this->activityId, json_encode($activity, JSON_UNESCAPED_UNICODE)); } } // 裝載獎品數據 public function _loadPrizeData() { $_prizeList = []; $prizeList = $this->db->lock(true)->getAll("SELECT * FROM s_luck_prize WHERE activity_id = {$this->activityId} AND status = 0 "); foreach ($prizeList as $item) { $item['attr_data'] = json_decode($item['attr_json'] ?: '{}', true); $_prizeList[$item['id']] = $item; // 每個獎品的庫存 入庫 $prizeSurplus = $item['total'] - $item['issued_num']; // 總數 - 已發放 $this->redis->set("luck:prizeSurplus:" . $item['id'], $prizeSurplus); // 注意: 每日已發放不能初始化 } if ($_prizeList) { // 獎品列表緩存 $this->redis->set('luck:prizes:' . $this->activityId, json_encode($_prizeList, JSON_UNESCAPED_UNICODE)); } } // 清除活動緩存數據 public function clean() { $this->redis->del('luck:activity:' . $this->activityId); $this->redis->del('luck:prizes:' . $this->activityId); // 活動參與計數暫時不清除(如果活動參與計數被清空,會導致計數重置,跳過參與次數攔截) // 獎品庫存數據暫時不清除(如果庫存被清空了,會導致提示活動未準備) } // 檢測活動參與資格 public function _checkActivityQualification() { $time = $this->time; if (empty($this->activity) || empty($this->prizeList)) { return ['error' => 1, 'message' => '活動暫未開始']; } if ($this->activity['start_time'] > $time) { return ['error' => 2, 'message' => '活動暫未開始']; } if ($this->activity['end_time'] < $time) { return ['error' => 3, 'message' => '活動已經結束']; } if ($this->activity['status'] != 0 || $this->activity['state'] != 1) { return ['error' => 4, 'message' => '活動已經結束']; } // todo:參與資格檢測 $participationKey = 'luck:participation:' . $this->day . '_' . $this->activityId . '_' . $this->userId; // 注意: 這樣有并發問題 // if ($this->redis->get($participationKey) >= 5) { // return ['error' => 5, 'message' => '今天的抽獎機會用完了,請明天再來']; // } // 0 為不限制 $u_d_participation = $this->activity['rule_data']['u_d_participation']; // incr = set && get 是原子性的 也就沒有并發問題了 if ($u_d_participation != 0 && ($this->redis->incr($participationKey) > $u_d_participation)) { // 參與額度用完了, 不應該計數, 注意減回來 $this->redis->decr($participationKey); return ['error' => 5, 'message' => '今天的抽獎機會用完了,請明天再來']; } return true; } // 取得參與搖獎的獎品列表 public function _getPrizeList() { // 獎品滿足條件:總庫存 > 0 && 今日已發放數量 < 每日最多發放數量 $_prizeList = []; foreach ($this->prizeList as $item) { $prizeSurplusKey = "luck:prizeSurplus:" . $item['id']; $prizeIssuedDayKey = "luck:prizeIssuedDayKey:" . $this->day . '_' . $item['id']; $prizeIssuedDay = $this->redis->get($prizeIssuedDayKey) ?: 0; // 沒開始發時未0 // 注意: 這里其實有并發問題,不過沒事,這里只是初步的攔截,真正搖獎在lua腳本中,是串行的,所以最終是沒有并發問題的 // 排除沒有庫存的 if ($this->redis->get($prizeSurplusKey) > 0 && $prizeIssuedDay < $item['day_max_issued_num']) { $_prizeList[] = $item; } } return $_prizeList; } // 概率計算 public function _probability($prizes) { $_probability = []; foreach ($prizes as $item) { $_probability[] = $item['probability']; } // 最后加上活動的不中獎概率 $_probability[] = $this->activity['no_prize']; $index = $this->_get_rand($_probability); // 獲取中獎 index if ($index == (count($_probability) - 1)) { return false; // 沒中獎 } return $prizes[$index]; // 返回中獎商品 } /** * 概率算法 * proArr array(10, 20, 30, 40) */ public function _get_rand($proArr) { $result = ''; $proSum = array_sum($proArr); foreach ($proArr as $key => $proCur) { $randNum = mt_rand(1, $proSum); if ($randNum <= $proCur) { $result = $key; break; } else { $proSum -= $proCur; } } unset($proArr); return $result; } } ``` **上面的概率算法其實存在問題:** 概率算法有問題,如果幾個連著的獎品的中獎率一樣,則相同的后面的一個不會中獎,因為遍歷就決定了返回順序,用于是第一個就返回了,后面的沒有機會,可以解決這個問題,利用:array_count_values array_rand 可以做到,出現相同值的項時,在進行一次隨機 概率算法雖然有缺陷,但我們也可以繞過這個缺陷,_probability() 里面 自己將 獎品列表 shuffle 打亂一次既可,這樣就相當于提前有一次隨機了,也算是公平了(而不是從數據庫查出來的順序或者其他情況決定,這是算法之外不公平的根本原因),并且考慮到 中 不中獎的概率很大,可以把 它放在最前面,也能提高效率 ---- ### 參考資料 [Redis Lua實戰 - 簡書](https://www.jianshu.com/p/366d1b4f0d13) [利用Redis和Lua的原子性實現搶紅包功能 - 簡書](https://www.jianshu.com/p/b58ed2fe6976?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation) [42丨如何使用Redis來實現多用戶搶票問題 | 極客時間](https://time.geekbang.org/column/article/132851) [(3) 秒殺系統優化方案之緩存、隊列、鎖設計思路 - 唐成勇 - SegmentFault 思否](https://segmentfault.com/a/1190000008888926) [(3) Redis使用lua腳本 - 飛鴻影下 - SegmentFault 思否](https://segmentfault.com/a/1190000016753833) [(3) php和redis設計秒殺活動 - - SegmentFault 思否](https://segmentfault.com/a/1190000019778733) [(3) 【redis進階(1)】redis的Lua腳本控制(原子性) - 菜問專欄 - SegmentFault 思否](https://segmentfault.com/a/1190000019676878) [Redis遞增遞減功能 - 亻也倔強小男人 - CSDN博客](https://blog.csdn.net/qq_25218095/article/details/79723531) [(3) Redis 中 Lua 腳本的應用和實踐 - 燕南飛和你一起聊技術 - SegmentFault 思否](https://segmentfault.com/a/1190000018070172) [事務(transaction) — Redis 命令參考](http://redisdoc.com/topic/transaction.html#redis) [redis中如何保證原子性_Redis](https://www.sohu.com/a/324014689_120047065#cmid=249183) > 糾錯,redis事務并不是原子性的,單個命令是串行的原子的(lua腳本除外,也不是原子性的) [高性能分布式鎖-redisson的使用 - webwangbao - 博客園](https://www.cnblogs.com/webwangbao/p/9247318.html) [分布式鎖設計與實現](https://mp.weixin.qq.com/s?__biz=MzIwNTI2ODY5OA==&mid=2649938438&idx=1&sn=ec19c1f1cdd161dad8d5cf8fd89637f4&chksm=8f3509b3b84280a5c6750343a094657817b72b92f70787253e1accfa636e61db1316c79d0955&scene=21#wechat_redirect) [從Redis異步到反應式架構 - 知乎](https://zhuanlan.zhihu.com/p/77328969) ---- ### 思考:活動進行中修改活動數據 假設活動正在進行中,活動、獎品數據已經裝載到redis了,此時更新活動、獎品數據會發生什么: 1. 更新活動數據(名稱,規則配置,狀態等) 2. 更新獎品數據(總數,概率,名稱,下架狀態,刪除) 3. 新增獎品 所有更新操作都是,先更新數據庫,再更新redis(假設redis都能更新成功,不成功也有補償機制保證最新數據一定會從db刷到redis) 這里問題的關鍵在于,活動進行中可能也有db落盤,也有redis更新,而我們更新活動數據也會有db更新和redis操作,這之間可能出現并問題,導致設計的并發方案出問題 中獎后,活動數據和獎品數據需要隨之快照,不能只存獎品id因為獎品后面可能會更新 另外,活動獎品數據都不會物理刪除的 用戶參與抽獎時,實例化 `Luck類` 時,活動數據就從redis加載到此類中作為屬性了(私有內存數據,程序數據),與所有外部隔離 除了庫存外,不會再從外部讀任何數據了 即使想更新redis也請在 此類被初始化之前,否則這里過后是不會再讀活動數據了 當然如果外部修改了庫存,則情況會復雜一些,后面再詳細討論 #### 相對先后性理論 >[tip] 任何事物都有個相對先后性,通常以最后一次確認為證,當然最大程度的確認能保證最大程度的準確性,但這也是有極限和代價的,這里最大程度的確認就是 `luck類` 實例化時從redis加載進來的數據,和 搖獎時 時間狀態等再判斷了,畢竟到這里不能再查數據庫了(整個抽獎就是要設計成不查數據庫),所以這是這里為 **“最大程度的確認”** 所能盡的最大努力了(也是最后的努力)。 > > 如果redis數據裝載進來后,已經執行到抽獎概率算法那里時(假設執行了幾秒),活動剛好到期了,這里也無視,仍是以當前活動是有效的為準。這是基本的事實,任何討論都應該基于這個事實,否則再無法進行下去,就又回到了時間極限的問題上去了。 ---- last update: 2019-08-16 18:51:52
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看