Redis秒殺實戰(zhàn):微信搶紅包(附源碼)
時間:2023-05-18 09:03:02 | 來源:網(wǎng)站運營
時間:2023-05-18 09:03:02 來源:網(wǎng)站運營
Redis秒殺實戰(zhàn):微信搶紅包(附源碼):
一、導讀
為啥寫這個微信搶紅包項目呢,公司 0202 年 08 月 22 日,公司周年慶,搶了100多紅包 ,O(∩_∩)O哈哈~
二、微信搶紅包實現(xiàn)原理
2.1 業(yè)務(wù)流程分析
2.2 功能拆解2.2.1 新建紅包在 DB、Redis 分別新增一條記錄
2.2.2 搶紅包(并發(fā))請求Redis,紅包剩余個數(shù),大于0才可以拆,等會0時,提示用戶,紅包已搶完
2.2.3. 拆紅包(并發(fā))「用到技術(shù)」
Redis 中數(shù)據(jù)類型的 String 特性的原子遞減(DECR key)和減少指定值(DECRBY key decrement)
「業(yè)務(wù)」
- 請求 Redis ,當剩余紅包個數(shù)大于 0,紅包個數(shù)原子遞減,隨機獲取紅包
- 計算金額,當最后一個紅包時,最后一個紅包金額=總金額-總已搶紅包金額
- 更新數(shù)據(jù)庫
2.2.4. 查看紅包記錄查詢 DB 即可
2.3 數(shù)據(jù)庫設(shè)計紅包流水表
CREATE TABLE `red_packet_info` ( `id` int(11) NOT NULL AUTO_INCREMENT, `red_packet_id` bigint(11) NOT NULL DEFAULT 0 COMMENT '紅包id,采?timestamp+5位隨機數(shù)', `total_amount` int(11) NOT NULL DEFAULT 0 COMMENT '紅包總?額,單位分', `total_packet` int(11) NOT NULL DEFAULT 0 COMMENT '紅包總個數(shù)', `remaining_amount` int(11) NOT NULL DEFAULT 0 COMMENT '剩余紅包?額,單位分', `remaining_packet` int(11) NOT NULL DEFAULT 0 COMMENT '剩余紅包個數(shù)', `uid` int(20) NOT NULL DEFAULT 0 COMMENT '新建紅包?戶的?戶標識', `create_time` timestamp COMMENT '創(chuàng)建時間', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATECURRENT_TIMESTAMP COMMENT '更新時間', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='紅包信息表,新建?個紅包插??條記錄';
紅包記錄表
CREATE TABLE `red_packet_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `amount` int(11) NOT NULL DEFAULT '0' COMMENT '搶到紅包的?額', `nick_name` varchar(32) NOT NULL DEFAULT '0' COMMENT '搶到紅包的?戶的?戶名', `img_url` varchar(255) NOT NULL DEFAULT '0' COMMENT '搶到紅包的?戶的頭像', `uid` int(20) NOT NULL DEFAULT '0' COMMENT '搶到紅包?戶的?戶標識', `red_packet_id` bigint(11) NOT NULL DEFAULT '0' COMMENT '紅包id,采?timestamp+5位隨機數(shù)', `create_time` timestamp COMMENT '創(chuàng)建時間', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATECURRENT_TIMESTAMP COMMENT '更新時間', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='搶紅包記錄表,搶?個紅包插??條記錄';
2.4 發(fā)紅包 API發(fā)紅包接口開發(fā)
- 新增一條紅包記錄
- 往 mysql 里面添加一條紅包記錄
- 往 redis 里面添加一條紅包數(shù)量記錄
- 往redis里面添加一條紅包金額記錄
?往db中就單純存入一條記錄,Service層和Mapper層,就簡單的一條sql語句,主要是提供思路,下面會附案例源碼,不要慌
?
2.5 搶紅包 API- 搶紅包功能屬于原子減操作
- 當大小小于 0 時原子減失敗
- 當紅包個數(shù)為0時,后面進來的用戶全部搶紅包失敗,并不會進入拆紅包環(huán)節(jié)
- 搶紅包功能設(shè)計
- 將紅包ID的請求放入請求隊列中,如果發(fā)現(xiàn)超過紅包的個數(shù),直接返回
- 注意事項
搶紅包算法拆解通過上圖算法得出,靠前面的人,手氣最佳幾率小,手氣最佳,往往在后面
- 發(fā) 100 元,共 10 個紅包,那么平均值是 10 元一個,那么發(fā)出來的紅包金額在 0.01~20 元之間波動
- 當前面 4 個紅包總共被領(lǐng)了 30 元時,剩下 70 元,總共 6 個紅包,那么這 6 個紅包的金額在 0.01~23.3 元之間波動
搶紅包接口開發(fā)2.6 測試2.6.1. 發(fā)紅包2.6.2 模擬高并發(fā)搶紅包(Jmeter壓測工具)因為我發(fā)了 10 個紅包,金額是 20000,使用壓測工具,模擬50個請求,只允許前10個請求能搶到紅包,并且金額等于20000。
三、布隆過濾器(重要)
3.1 介紹布隆過濾器是1970年由布隆提出的。它實際上是一個很長的二進制向量和一系列隨機映射函數(shù)。布隆過濾器可以用于檢索一個元素是否在一個集合中。它的優(yōu)點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率和刪除困難。
3.1.1 優(yōu)點相比于其他的數(shù)據(jù)結(jié)構(gòu),布隆過濾器在空間和時間方面都有巨大的優(yōu)勢。布隆過濾器存儲空間和插入/查詢時間都是常數(shù)。另外三列函數(shù)相互之間沒有關(guān)系,方便由硬件并行實現(xiàn)。布隆過濾器不需要存儲元素本身,在某些對保密要求非常嚴格的場合有優(yōu)勢。
3.1.2 缺點但是布隆過濾器的缺點和有點一樣明顯。誤算率是其中之一。隨著存入的元素數(shù)量增加,誤算率隨之增加。但是如果元素數(shù)量太少,則使用散列表足矣。
3.2 布隆過濾器有什么用- 黑客流量攻擊:故意訪問不存在的數(shù)據(jù),導致查程序不斷訪問DB的數(shù)據(jù)
- 黑客安全阻截:當黑客訪問不存在的緩存時迅速返回避免緩存及DB掛掉
- 網(wǎng)頁爬蟲對 URL 的去重,避免爬取相同的URL地址
- 反垃圾郵件,從數(shù)十億個垃圾郵件列表中判斷某郵件是否垃圾郵件(同理,垃圾短信)
- 緩存擊穿,將已存在的緩存放到布隆中,當黑客訪問不存在的緩存時迅速返回避免緩存及 DB 掛掉
四、布隆過濾器實現(xiàn)會員轉(zhuǎn)盤抽獎
4.1. 需求一個抽獎程序,只針對會員用戶有效
4.2 通過google布隆過濾器存儲會員數(shù)據(jù)- 程序啟動時將數(shù)據(jù)放入內(nèi)存中
- google自動創(chuàng)建布隆過濾器
- 用戶ID進來之后判斷是否是會員
4.3 代碼實現(xiàn)4.3.1 引入依賴<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>29.0-jre</version></dependency>
4.3.2 數(shù)據(jù)庫會員表CREATE TABLE `sys_user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `user_name` varchar(11) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '?戶名', `image` varchar(11) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '?戶頭像', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
4.3.3 初始化布隆過濾器dao 層和 dao 映射文件,就單純的一個 sql 查詢,看核心方法,下面會附源碼滴,不要慌好嘛
4.3.4 控制層4.3.5 測試4.4 缺點- 內(nèi)存級別產(chǎn)部
- 重啟即失效
- 本地內(nèi)存無法用在分布式場景
- 不支持大數(shù)據(jù)量存儲
五、Redis布隆過濾器
5.1 優(yōu)點- 可擴展性 Bloom 過濾器
- 不存在重啟即失效或定時任務(wù)維護的成本
5.2 缺點- 需要網(wǎng)絡(luò)IO,性能比基于內(nèi)存的過濾器低
5.3 布隆過濾器安裝5.3.1. 下載github:
https://github.com/RedisBloom/RedisBloom鏈接: https://pan.baidu.com/s/16DlKLm8WGFzGkoPpy8y4Aw
密碼: 25w1
5.3.2. 編譯make
5.3.3 將 Rebloom 加載到 Redis 中先把 Redis 給停掉?。?!在 redis.conf 里面添加一行命令->加載模塊
loadmodule /usr/soft/RedisBloom-2.2.4/redisbloom.so
5.3.4 測試布隆過濾器六、SpringBoot 整合 Redis 布隆過濾器
6.1 編寫兩個lua腳本- 添加數(shù)據(jù)到指定名稱的布隆過濾器
- 從指定名稱的布隆過濾器獲取key是否存在的腳本
local bloomName = KEYS[1]local value = KEYS[2]--bloomFilterlocal result_1 = redis.call('BF.ADD',bloomName,value)return result_1
local bloomName = KEYS[1]local value = KEYS[2]--bloomFilterlocal result_1 = redis.call('BF.EXISTS',bloomName,value)return result_1
6.2 在 RedisService.java 中添加 2 個方法6.3 驗證七、秒殺系統(tǒng)設(shè)計
7.1 秒殺業(yè)務(wù)流程圖7.2 數(shù)據(jù)落地存儲方案- 通過分布式redis減庫存
- DB存最終訂單信息數(shù)據(jù)
7.3 API性能調(diào)優(yōu)- 性能瓶頸在高并發(fā)秒殺
- 技術(shù)難題在于超賣問題
7.4 實現(xiàn)步驟提前將秒殺數(shù)據(jù)緩存到 redis
set skuId_start_1 0_1554045087 --秒殺標識set skuId_access_1 12000 --允許搶購數(shù)set skuId_count_1 0 --搶購計數(shù)set skuId_booked_1 0 --真實秒殺數(shù)
- 秒殺開始前,skuId_start為0,代表活動未開始
- 當skuId_start改為1時,活動開始,開始秒殺叭
- 當接受下單數(shù)達到sku_count*1.2后,繼續(xù)攔截所有請求,商品剩余數(shù)量為0(為啥接受搶購數(shù)為1萬2呢,看業(yè)務(wù)流程圖,涉及到“校驗訂單信息”,一般設(shè)置的值要比總數(shù)多一點,多多少自己定)
利用 Redis 緩存加速增庫存數(shù)
"skuId_booked":10000 //從0開始累加,秒殺的個數(shù)只能加到1萬
將用戶訂單數(shù)據(jù)寫入 MQ(異步方式)。
另外一臺服務(wù)器監(jiān)聽 mq,將訂單信息寫入到 DB。
好了,以上就是完整的開發(fā)步驟,下面我們開始編寫代碼
7.5 代碼實戰(zhàn)7.5.1. 網(wǎng)關(guān)瀏覽攔截層1、先判斷秒殺是否已經(jīng)開始
2、利用 Redis 緩存 incr 攔截流量
- 用 incr 方法原子加
- 通過原子加帕努單當前 skuId_access 是否達到最大值
7.5.2. 訂單信息校驗層1、校驗當前用戶是否已經(jīng)買過這個商品
- 需要存儲用戶的uid
- 存數(shù)據(jù)庫效率太低
- 存Redis value方式數(shù)據(jù)太大
- 存布隆過濾器性能高且數(shù)據(jù)量小(推薦)
2、校驗通過直接返回搶購成功
7.5.3 開發(fā)lua腳本實現(xiàn)庫存扣除1、庫存扣除成功,獲取當前最新庫存
2、如果庫存大于0,即馬上進行庫存扣除,并且訪問搶購成功給用戶
3、考慮原子性問題
- 保證原子性的方式,采用 lua 腳本
- 采用lua腳本方式保證原子性帶來缺點,性能有所下降
- 不保證原子性缺點,放入請求量可能大于預(yù)期
- 當前扣除庫存場景必須保證原子性,否則會導致超賣
4、返回搶購結(jié)果
控制層
Service 層
布隆過濾器
7.6 初始化redis緩存set skuId_start_1 0_1554045087 --秒殺標識set skuId_access_1 12000 --允許搶購數(shù)set skuId_count_1 0 --搶購計數(shù)set skuId_booked_1 0 --真實秒殺數(shù)
7.7 秒殺驗證jmeter 配置
壓測秒殺驗證原子性
八、項目下載
鏈接(奶牛 快傳): https://cowtransfer.com/s/74998eaf64da44
取件碼: rqzbyj
九、尾聲
演示的時候,我使用的 Redis 單機的,吞吐量不是很大,感興趣的,可以自己搭建個 Redis 主從復制+哨兵+集群,然后再測試。
最近比較忙,沒時間完善微信搶紅包秒殺的原子性。下面那個完整案例搶庫存的,親自使用 Jmeter 壓測幾次,是原子性的,可以拿來借鑒,感興趣的同學,可以借鑒下面搶庫存的代碼,把微信搶紅包的功能在完善下,我就不修改啦。
作者:陳彥斌
鏈接:https://www.cnblogs.com/chenyanbin/p/13587508.html
來源:博客園