如果今天有上萬人在同一時間搶限量商品
,昨天分享的方案基本撐不住。
不過面對這個情境,Redis 表示終於輪到我了!今天這篇文章會以 Node.js + Redis
為範例,帶讀者一起解決這個問題。
如何解決高併發情境的商品秒殺問題
回答問題所需具備的知識
衍伸問題
這算是資料庫的常見面試題
,除了金融、電商
喜歡考這題外;社交平台、新創公司、遊戲產業
也會換個形式考類似的題目。
因為這算是職缺所需具備的技術
,如果求職者沒有相關經驗可能就會止步於這個關卡。
如果有去以上產業的打算,最好有一定的 NoSQL(ex:Redis、MongoDB) 基礎再去面試;因為在面試官的眼中
有基礎跟完全不會的差距很大
,求職者可以沒有實務經驗,但明知產業會碰到這些技術還不去學習就是態度問題了。
通常這類型的秒殺活動都有固定檔期,我會先將活動用的商品及庫存數量同步到 Redis 資料庫
;為了避免超賣,在 Client 端下單時會執行 Lua 腳本
,當秒殺到指定數量或是時間結束後就不再接受請求,活動結束後將 Redis 的資料同步到關聯式資料庫
。
如果有短時間大量訪問、需要性能提升
的需求,往往會先想到 Redis 這個記憶體資料庫。
Redis 為什麼快?
多執行緒
會耗費時間在上下文的切換
以及加鎖
上面;而單執行緒
會依照請求順序執行
,不需考慮同步以及加鎖帶來的效能問題。開多個 Redis 建立 Cluster
分散壓力。epoll IO 多路複用
,可以用一條執行緒處理併發的網路請求。
這個有點抽象,我用
繳交作業
為情境說明:
假設你是一個老師,採用多路復用的原則批改作業,那你批改的順序就是看誰先繳交作業
,而不是按照學號順序批改(此作法中間有人沒交作業就會卡住);這樣就能避免大量無用操作,為非阻塞模式的實現。
Redis 資料保存方案
可以使用 RDB 、AOF
來做持久化。
定期操作
,如果 Redis 當機會遺失部分資料
,此方案適合大規模資料恢復
。完整紀錄
所有資料的變化,因為採用日誌追加的方式,所以就算當機也不會影響已經儲存的日誌,災難復原的完成度高
;缺點是檔案比 RDB 大
、大規模資料恢復速度較 RDB 慢
。Redis 資料淘汰機制
Redis 主要保存的都是熱點資訊
,在儲存資料有限的狀態下(記憶體不足,無法寫入新資料);就要合理的設定淘汰機制:
過期時間
的資料
原則上淘汰策略以「
volatile-lru、allkeys-lru
」為主(淘汰較少使用的資料)。
目標
使用技術
如果完全沒有相關基礎,建議先參考這篇文章來安裝 Redis 以及它的 GUI 工具。
在 Redis Server 執行 EVAL 指令時,在結果回傳前只會執行當下 Lua 腳本的邏輯
,其他 Client 端的命令須等待直到 EVAL 執行完為止。
Lua 腳本的邏輯應盡量簡單以保證執行效率,否則會影響 Client 端的體驗。
程式架構
主程式:redisSecKill.js
yarn add ioredis
)。prepare
函式,以 Hash type 建立參加秒殺的產品庫存。secKill
函式模擬使用者購買行為,緩存並執行 Lua 腳本。const fs = require("fs");
const Redis = require("ioredis");
const redis = new Redis({
host: "127.0.0.1",
port: 6379,
password: "",
});
redis.on("error", function (error) {
console.error(error);
});
async function prepare(item_name) {
// 參加秒殺活動的商品庫存
await redis.hmset(item_name, "Total", 100, "Booked", 0);
}
const secKillScript = fs.readFileSync("./secKill.lua");
async function secKill(item_name, user_name) {
// 1. 緩存腳本取得 sha1 值
const sha1 = await redis.script("load", secKillScript);
// console.log(sha1);
// 2. 透過 evalsha 執行腳本
// redis Evalsha 命令基本語法如下
// EVALSHA sha1 numkeys key [key ...] arg [arg ...]
redis.evalsha(sha1, 1, item_name, 1, "order_list", user_name);
}
function main() {
console.time("secKill");
const item_name = "item_name";
prepare(item_name);
for (var i = 1; i < 10000; i++) {
const user_name = "baobao" + i;
secKill(item_name, user_name);
}
console.timeEnd("secKill");
}
main();
Lua 腳本:secKill.lua
在 Lua 腳本執行邏輯:「確認下單數量」➜「取得商品庫存」➜「如果庫存足夠就下單」➜「儲存購買者資訊(List type)」。
local item_name = KEYS[1]
local n = tonumber(ARGV[1])
local order_list = ARGV[2]
local user_name = ARGV[3]
if not n or n == 0 then
return 0
end
local vals = redis.call("HMGET", item_name, "Total", "Booked");
local total = tonumber(vals[1])
local booked = tonumber(vals[2])
if not total or not booked then
return 0
end
if booked + n <= total then
redis.call("HINCRBY", item_name, "Booked", n)
redis.call("LPUSH", order_list, user_name)
return n
end
return 0
node redisSecKill.js
模擬秒殺補充
文章程式只是 MVP,現實狀況還有很多要設計的:
- 哪些商品被列為秒殺,如何紀錄。
- 設定秒殺開始、結束時間。
- 如何將 Redis 資料存入關聯式資料庫中。
考點:對 Redis Zset 這個資料型態的認知與應用
我會使用 Redis 這個記憶體資料庫,建立一個有序集合(Zset)
來儲存用戶的資訊。
在設計上,用戶(member)是唯一值,且每個用戶都會關聯一個點數(score),這樣用戶就可以按照分數來排序。
功能實現上會透過 zrevrange Leaderboard 0 99 withscores
這段指令來顯示 TOP100 的點數排行榜。
- zrevrange:依照點數(score)檢視排行榜
- Leaderboard:排行榜名稱(可以自行定義)
- 0 99:TOP100 的意思
- withscores:連點數(score)一起顯示
考點:是否了解過同類型的技術以及差異
簡單的資料型態
,而 Redis 支援多種資料型態
(String、Hash、List、Set、Zset、Stream)。不支援資料持久化
,而 Redis 提供 RDB 跟 AOF 兩種方案
。同樣身為記憶體資料庫,Memcached 提供簡單的使用方式,而 Redis 提供豐富的功能。
感謝大家的閱讀,如果喜歡我的文章可以訂閱
接收通知;如果有幫助到你,按Like
可以讓我更有寫文的動力,我們明天見~
我在 Medium 平台 也分享了許多技術文章
❝ 主題涵蓋「MIS & DEVOPS、資料庫、前端、後端、MICROSFT 365、GOOGLE 雲端應用、自我修煉」希望可以幫助遇到相同問題、想自我成長的人。❞
在許多人的幫助下,本系列文章已成功出版,除了添加新的篇章,更完善了每個案例的應對進退;如果對現在的職涯感到迷茫,也許這本書能帶給你不一樣的觀點~
您好~很喜歡您寫的面試相關的類型文章,針對[短時間大量訪問、需要性能提升],想請教幾個問題:
1.對於大型專案來說,為甚麼不選擇oracle?
2.如果是短時間大量訪問,對於使用PHP+oracle來解決的話,不知道您有甚麼看法?
Oracle 一定也有方案可以處理高併發的情境,只是費用相對昂貴,相關的設計可以參考我之前分享的文章[面試][資料庫]面對高流量的的系統,會採取哪些措施?
但上面的方案主要是為了解決長時間高流量而做的設計;如果只針對「短時間大流量」,使用記憶體資料庫
的性價比更高。
另外 PHP 因為語言的特性,相對不適合處理短時間的大量訪問,可以參考我之前分享的文章PHP 跟 Node.js 的比較。
面對同一個問題,每個語言與資料庫通常都有對應的解法,只是要付出的代價不同;就看設計者要如何取捨。
所以對於[短於時間,大量訪問,解決長時間高流量]如電商平台,是不建議使用php比較建議使用Node.js語法,這樣的說法不知對不對;
另外oracle其實本身是能處理[短於時間,大量訪問,解決長時間高流量],只是需要如何去設計以及考量到價格,所以市場上是偏少人使用oracle再電商嗎?謝謝解惑
ps:由於小弟目前使用oracle,所以想詢問看看大大的意見謝謝您
我個人是建議用 Node.js 來做電商平台,但請考慮到團隊成員的技能組合;如果大部分的成員都對 Node.js 不熟悉,出問題時沒人知道如何解決會更傷腦筋。
電商還是會需要關聯式資料庫的,這部分看每間公司的選擇,選擇Oracle 也可以;Redis 主要是針對「短時間大流量」的資料處理,活動結束後資料還是要回存到關聯式資料庫。
恩恩了解了,還是要考慮到技能樹的組合以及資料庫的配置 ,謝謝大大~