Redis 秒殺實戰

點擊藍色「Java建設者 」關注我喲html

加個「星標」,及時閱讀最新技術文章java


原文連接:https://www.cnblogs.com/chenyanbin/p/13587508.htmlmysql

已原創受權git


這是Java建設者的第 116 篇原創文章


爲啥寫這個微信搶紅包項目呢,公司 0202 年 08 月 22 日,公司週年慶,搶了100多紅包🧧,O(∩_∩)O哈哈~
github

業務流程分析


功能拆解

新建紅包

在 DB、Redis 分別新增一條記錄web

搶紅包(併發)

「使用技術」面試

Redis 中數據類型的 String 特性的原子遞減(DECR key)和減小指定值(DECRBY key decrement)redis

「業務」算法

  1. 請求 Redis ,當剩餘紅包個數大於 0,紅包個數原子遞減,隨機獲取紅包
  2. 計算金額,當最後一個紅包時,最後一個紅包金額=總金額-總已搶紅包金額
  3. 更新數據庫

「查詢紅包記錄」sql

查詢 DB 便可

數據庫設計

紅包流水錶

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位隨機數',
`total_amount` int(11) NOT NULL DEFAULT 0 COMMENT '紅包總⾦額,單位分',
`total_packet` int(11) NOT NULL DEFAULT 0 COMMENT '紅包總個數',
`remaining_amount` int(11) NOT NULL DEFAULT 0 COMMENT '剩餘紅包⾦額,單位
分',
`remaining_packet` int(11) NOT NULL DEFAULT 0 COMMENT '剩餘紅包個數',
`uid` int(20) NOT NULL DEFAULT 0 COMMENT '新建紅包⽤戶的⽤戶標識',
`create_time` timestamp COMMENT '建立時間',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_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位隨機數',
`create_time` timestamp COMMENT '建立時間',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP COMMENT '更新時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='搶紅包記
錄表,搶⼀個紅包插⼊⼀條記錄';

發紅包 API

發紅包接口開發

  • 新增一條紅包記錄
  • 往 mysql 裏面添加一條紅包記錄
  • 往 redis 裏面添加一條紅包數量記錄
  • 往redis裏面添加一條紅包金額記錄

往db中就單純存入一條記錄,Service層和Mapper層,就簡單的一條sql語句,主要是提供思路,下面會附案例源碼,不要慌

搶紅包 API

  • 搶紅包功能屬於原子減操做
  • 當大小小於 0 時原子減失敗
  • 當紅包個數爲0時,後面進來的用戶所有搶紅包失敗,並不會進入拆紅包環節
  • 搶紅包功能設計
    • 將紅包ID的請求放入請求隊列中,若是發現超過紅包的個數,直接返回
  • 注意事項
    • 搶到紅包不必定能拆成功

搶紅包算法拆解

img

經過上圖算法得出,靠前面的人,手氣最佳概率小,手氣最佳,每每在後面

  1. 發 100 元,共 10 個紅包,那麼平均值是 10 元一個,那麼發出來的紅包金額在 0.01~20 元之間波動
  2. 當前面 4 個紅包總共被領了 30 元時,剩下 70 元,總共 6 個紅包,那麼這 6 個紅包的金額在 0.01~23.3 元之間波動

搶紅包接口開發

「測試」

「發紅包」


模擬高併發搶紅包(Jmeter壓測工具)

由於我發了 10 個紅包,金額是 20000,使用壓測工具,模擬50個請求,只容許前10個請求能搶到紅包,而且金額等於20000。

布隆過濾器

介紹

布隆過濾器是1970年由布隆提出的。它其實是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器能夠用於檢索一個元素是否在一個集合中。它的優勢是空間效率和查詢時間都遠遠超過通常的算法,缺點是有必定的誤識別率和刪除困難。

優勢

相比於其餘的數據結構,布隆過濾器在空間和時間方面都有巨大的優點。布隆過濾器存儲空間和插入/查詢時間都是常數。另外三列函數相互之間沒有關係,方便由硬件並行實現。布隆過濾器不須要存儲元素自己,在某些對保密要求很是嚴格的場合有優點。

缺點

可是布隆過濾器的缺點和有點同樣明顯。誤算率是其中之一。隨着存入的元素數量增長,誤算率隨之增長。可是若是元素數量太少,則使用散列表足矣。

布隆過濾器有什麼用

  1. 黑客流量攻擊:故意訪問不存在的數據,致使查程序不斷訪問DB的數據
  2. 黑客安全阻截:當黑客訪問不存在的緩存時迅速返回避免緩存及DB掛掉
  3. 網頁爬蟲對 URL 的去重,避免爬取相同的URL地址
  4. 反垃圾郵件,從數十億個垃圾郵件列表中判斷某郵件是否垃圾郵件(同理,垃圾短信)
  5. 緩存擊穿,將已存在的緩存放到布隆中,當黑客訪問不存在的緩存時迅速返回避免緩存及 DB 掛掉

布隆過濾器實現會員轉盤抽獎

需求

一個抽獎程序,只針對會員用戶有效

經過google布隆過濾器存儲會員數據

  1. 程序啓動時將數據放入內存中
  2. google自動建立布隆過濾器
  3. 用戶ID進來以後判斷是不是會員

代碼實現

引入依賴

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>29.0-jre</version>
</dependency>

數據庫會員表

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;

初始化布隆過濾器

dao 層和 dao 映射文件,就單純的一個 sql 查詢,看核心方法,下面會附源碼滴,不要慌好嘛

控制層

測試

缺點

  1. 內存級別產部
  2. 重啓即失效
  3. 本地內存沒法用在分佈式場景
  4. 不支持大數據量存儲

Redis布隆過濾器

優勢

  1. 可擴展性 Bloom 過濾器
  2. 不存在重啓即失效或定時任務維護的成本

缺點

  1. 須要網絡IO,性能比基於內存的過濾器低

布隆過濾器安裝

「下載」

github:https://github.com/RedisBloom/RedisBloom

連接: https://pan.baidu.com/s/16DlKLm8WGFzGkoPpy8y4Aw  密碼: 25w1

「編譯」

make

「將 Rebloom 加載到 Redis 中」

先把 Redis 給停掉!!!在 redis.conf 裏面添加一行命令->加載模塊

loadmodule /usr/soft/RedisBloom-2.2.4/redisbloom.so

「測試布隆過濾器」

SpringBoot 整合 Redis 布隆過濾器

編寫兩個lua腳本

  1. 添加數據到指定名稱的布隆過濾器
  2. 從指定名稱的布隆過濾器獲取key是否存在的腳本

local bloomName = KEYS[1]
local value = KEYS[2]
--bloomFilter
local result_1 = redis.call('BF.ADD',bloomName,value)
return result_1

local bloomName = KEYS[1]
local value = KEYS[2]
--bloomFilter
local result_1 = redis.call('BF.EXISTS',bloomName,value)
return result_1

在 RedisService.java 中添加 2 個方法

驗證

秒殺

秒殺業務流程圖

數據落地存儲方案

  1. 經過分佈式redis減庫存
  2. DB存最終訂單信息數據

API性能調優

  1. 性能瓶頸在高併發秒殺
  2. 技術難題在於超賣問題

實現步驟

提早將秒殺數據緩存到 redis

set skuId_start_1 0_1554045087 --秒殺標識
set skuId_access_1 12000 --容許搶購數
set skuId_count_1 0 --搶購計數
set skuId_booked_1 0 --真實秒殺數
  1. 秒殺開始前,skuId_start爲0,表明活動未開始
  2. 當skuId_start改成1時,活動開始,開始秒殺叭
  3. 當接受下單數達到sku_count*1.2後,繼續攔截全部請求,商品剩餘數量爲0(爲啥接受搶購數爲1萬2呢,看業務流程圖,涉及到「校驗訂單信息」,通常設置的值要比總數多一點,多多少本身定)

利用 Redis 緩存加速增庫存數

"skuId_booked":10000 //從0開始累加,秒殺的個數只能加到1萬

將用戶訂單數據寫入 MQ(異步方式)。

另一臺服務器監聽 mq,將訂單信息寫入到 DB。

好了,以上就是完整的開發步驟,下面咱們開始編寫代碼

代碼實戰

網關瀏覽攔截層

一、先判斷秒殺是否已經開始

二、利用 Redis 緩存 incr 攔截流量

  • 用 incr 方法原子加
  • 經過原子加帕努單當前 skuId_access 是否達到最大值

訂單信息校驗層

一、校驗當前用戶是否已經買過這個商品

  • 須要存儲用戶的uid
  • 存數據庫效率過低
  • 存Redis value方式數據太大
  • 存布隆過濾器性能高且數據量小(推薦)

二、校驗經過直接返回搶購成功

開發lua腳本實現庫存扣除

一、庫存扣除成功,獲取當前最新庫存

二、若是庫存大於0,即立刻進行庫存扣除,而且訪問搶購成功給用戶

三、考慮原子性問題

  • 保證原子性的方式,採用 lua 腳本
  • 採用lua腳本方式保證原子性帶來缺點,性能有所降低
  • 不保證原子性缺點,放入請求量可能大於預期
  • 當前扣除庫存場景必須保證原子性,不然會致使超賣

四、返回搶購結果

  • 搶購成功
  • 庫存沒了,搶購失敗

控制層

Service 層

布隆過濾器

初始化redis緩存

set skuId_start_1 0_1554045087 --秒殺標識
set skuId_access_1 12000 --容許搶購數
set skuId_count_1 0 --搶購計數
set skuId_booked_1 0 --真實秒殺數

秒殺驗證

jmeter 配置

壓測秒殺驗證原子性

項目下載

連接: https://pan.baidu.com/s/1hZUPRAljkqO05fYluqJBhQ  密碼: 1iwr

尾聲

演示的時候,我使用的 Redis 單機的,吞吐量不是很大,感興趣的,能夠本身搭建個 Redis 主從複製+哨兵+集羣,而後再測試。

最近比較忙,沒時間完善微信搶紅包秒殺的原子性。下面那個完整案例搶庫存的,親自使用 Jmeter 壓測幾回,是原子性的,能夠拿來借鑑,感興趣的同窗,能夠借鑑下面搶庫存的代碼,把微信搶紅包的功能在完善下,我就不修改啦。


往期精選

辣雞,你怎麼每天就會 try...catch ?試試這個

來這裏薅 cxuan 和噹噹的羊毛

你想要的乾貨都在這了。

文件系統:隱匿在 Linux 背後的機制

史上最全的數據庫面試題,面試必刷!


本文分享自微信公衆號 - Java建設者(javajianshe)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索