最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

【面試題】電商秒殺如何解決超賣問題?

2023-06-30 08:28 作者:ZeromaX訸  | 我要投稿

視頻文稿

問題描述

電商秒殺的超賣問題,其實往大了說主要就是要解決常見的購物網(wǎng)站上經(jīng)常出現(xiàn)的團購、秒殺、特價等活動會出現(xiàn)的一個共同問題:短時間內(nèi)大量并發(fā)請求的情況下,需要保證商品的銷售量不會超過庫存量。類似的問題也可以改成 12306 搶票如何保證不搶超過票數(shù)等,其實解決的思路都是類似的。

產(chǎn)生超賣現(xiàn)象的主要原因,就是購買過程其實最起碼會涉及到兩步:(1)檢查庫存是否已售空或足夠用戶購買。(2)未售空的話,扣除用戶購買的庫存數(shù)量。這就是一個典型的“讀后寫”的情況。

如果沒有對并發(fā)請求進行類似鎖的控制,或者說是原子性地去“讀后寫”訪問庫存數(shù)量的話,就會出現(xiàn)多個請求檢查庫存時是有庫存的,而實際進入扣除庫存邏輯時的請求數(shù)量已經(jīng)超過了庫存的情況,導致最后庫存變?yōu)樨摂?shù)。

舉例就是兩個并發(fā)請求在剩余一個庫存的時候請求服務器,執(zhí)行檢查邏輯的時候都發(fā)現(xiàn)還有一個庫存,可以購買,然后真正扣除的時候,各扣一個導致最后庫存為 -1。

問題解析

作為一個典型的高并發(fā)問題,我們解決問題的關(guān)鍵其實也就是前面提到的:讓并發(fā)請求原子性地去完成對庫存的“讀后寫”,這里可以是用鎖控制并發(fā)請求,也可以是其他使得并發(fā)請求最終順序訪問庫存數(shù)據(jù)的方式。

那么我們可以看看對于不同層級的系統(tǒng),我們有什么方式可以處理。

數(shù)據(jù)庫層

涉及到庫存,必然涉及到數(shù)據(jù)庫,那么我們可以優(yōu)先看看數(shù)據(jù)庫層如何處理。

數(shù)據(jù)庫層我們可以實現(xiàn)鎖的邏輯。首先就是可以基于 MySQL 的行鎖實現(xiàn)悲觀鎖的作用,直接使用 SELECT ... FOR UPDATE 語句來直接原子性的完成讀后寫操作。SQL 代碼類似如下:

select stock from goods_table where id = 1 for update;

update goods_table set stock = stock - 1 where id = 1;

這樣并行請求的訪問數(shù)據(jù)庫的事務就將變成串行訪問,從而避免了超賣問題。

當然,數(shù)據(jù)庫層也可以實現(xiàn)類似 CAS 的樂觀鎖,方法就是在表中增加一個版本號字段,或者直接把庫存作為版本號,然后就可以在查詢和更新時使用類似下面的語句:

select stock from goods_table where id = 1;

update goods_table

set stock = stock - 1

where id = 1 and stock = 123;

但數(shù)據(jù)庫的鎖效率較低,以上兩個方案都很容易導致整個系統(tǒng)性能瓶頸卡在數(shù)據(jù)庫。尤其是樂觀鎖方案在高并發(fā)情況下的大量重試是我們無法接受的,所以真正在較大并發(fā)時,是不會使用這兩種解決方案的。

那么比較現(xiàn)實的處理方式是:我們可以通過引入緩存的思路來優(yōu)化數(shù)據(jù)庫的性能瓶頸。比如使用 Redis 來對庫存數(shù)量進行緩存,每次庫存操作先在 Redis 驗證后再訪問數(shù)據(jù)庫。這里對 Redis 中庫存的讀后寫可以使用 lua 腳本進行實現(xiàn),以保證原子性。這樣利用 Redis 單線程高性能的特性,可以滿足現(xiàn)實場景下基本的業(yè)務需求。這是最常見的一個解決方案。

需要注意的是對于 Redis 集群,機器的增加不會提升我們秒殺業(yè)務支持的并發(fā)量,而僅僅是保證 Redis 的穩(wěn)定性。因為每次存儲庫存的 key 所在的槽的主節(jié)點都是集群中特定的某一臺 Redis 機器,瓶頸依然在這一臺機器上。

單體 Web 服務

接著我們可以看看簡單基礎的單體 Web 服務系統(tǒng)中我們?nèi)绾翁幚恚梢詫竺婕簣鼍暗姆治鲇兴鶈l(fā)。

因為Web 服務層是單體,我們可以直接對多個并發(fā)的請求進程在代碼上使用多線程的進行控制:

(1)使用 notify/wait、synchronized、Lock 等等各種經(jīng)典的多線程悲觀鎖。

(2)使用 CAS 實現(xiàn)樂觀鎖。但同樣大量并發(fā)時的重試請求鎖的問題,也使得這個解決方案不太現(xiàn)實。

除了鎖,還有一個特別的利用異步的思路,就是我們使用一個顯式的并發(fā)隊列,比如 ConcurrentLinkedQueue,每個并發(fā)請求來的時候我們先把請求用戶和購買數(shù)量入隊。真正搶商品的任務工作線程異步執(zhí)行去消費隊列中的記錄,由它真正去數(shù)據(jù)庫扣減庫存。那么只要這個工作線程是單線程的,就不會出現(xiàn)超賣。

但很顯然,我們的單體服務也是很難用于真正的秒殺場景的。畢竟基于單體 Web 服務架構(gòu)的代碼控制鎖的設計很難進行水平擴容(即增加機器數(shù)量以接受更大并發(fā)訪問量)。以上分析主要是讓大家領(lǐng)會到解決問題的核心思路。

Web 服務集群

那么當我們的服務是集群時,我們應該如何處理超賣問題呢?

之前代碼上鎖的思路,我們擴展一下,在當前集群場景下就可以變成使用分布式鎖。這樣邏輯上其實就和使用 Java 代碼的悲觀鎖類似,只是鎖的邏輯使用分布式鎖進行替換。那么即使我們使用 Redis 來實現(xiàn)分布式鎖的話,鎖導致大量請求等待占用系統(tǒng)資源的問題也是存在的,因為這里的瓶頸依然是在數(shù)據(jù)庫訪問速度。此外,同樣需要注意水平擴容 Redis 集群無法提高并發(fā)量的問題。所以其實這個方案應該也是比較少使用。

而代碼通過隊列異步處理的思路,我們可以通過應用消息隊列在集群場景下防止超賣。要做的就是把單體的并發(fā)隊列相關(guān)邏輯改為使用消息隊列。然后保證消費者的單線程性即可防止超賣。

我個人感覺消息隊列的實現(xiàn)應該是在可以在高并發(fā)場景下支持大訪問并發(fā)量的。這樣充分利用了消息隊列異步、解耦、削峰的好處,使得用戶并發(fā)的請求可以盡快返回給前端,并不會像有鎖時在等待訪問數(shù)據(jù)庫的過程中占用服務器資源。但這個方案下,查詢剩余庫存的問題就需要考慮,是同樣引入緩存?還是在單線程的消費者處維護剩余庫存?抑或是在消息隊列先積壓到超過庫存數(shù)量再統(tǒng)一消費處理?設計起來相對復雜。實際實踐中,需要根據(jù)用戶規(guī)模、消息隊列響應時間、設施成本、后續(xù)運維復雜度等因素綜合考慮是否有必要使用該解決方案。

總結(jié)

通過上面的分析,我們可以清楚的體會到,超賣問題的實質(zhì),其實就是解決高并發(fā)下對某個數(shù)據(jù)的讀后寫操作不相互干擾、保證原子性的問題。那么我們就有“使用鎖直接控制請求”和“并發(fā)入隊然后異步單線程處理”兩種思路,對于“數(shù)據(jù)庫層和服務層”、“單體和集群場景”我們也就會有相應的不同實現(xiàn)。基于此,大家應該可以在回答類似問題的時候保持一個清晰的思路進行回答。

各個方法對比的話,最常見的解決方案就是 Redis 緩存的方案了,充分利用 Redis 單線程高性能的特性,足以面對大部分的情況。其次就是消息隊列的方式,應該也是可以比較完美地解決問題,但就是方案相對復雜一些,且需要單獨考慮查詢剩余庫存的問題在這種解決方案下如何實現(xiàn)。

其他單體和基于數(shù)據(jù)庫的方法,在超賣問題上我基本給了不及格的推薦指數(shù),主要就是它們雖然都能防止庫存扣超的問題,但在這個實際的業(yè)務問題上不太適合。原因就是要避免最后瓶頸集中在數(shù)據(jù)庫上,并且要考慮機器的水平擴容問題。之所以介紹,主要還是介紹一下并發(fā)場景上的解決思路,以后遇到其他的問題可以自己自行舉一反三進行分析。

真正面試的話,估計直接回答 Redis 緩存的解決方案即可。

【面試題】電商秒殺如何解決超賣問題?的評論 (共 條)

分享到微博請遵守國家法律
永兴县| 策勒县| 赫章县| 桂林市| 和田市| 新邵县| 郸城县| 揭东县| 福泉市| 荥经县| 茂名市| 黄石市| 郑州市| 丽水市| 岳西县| 宁武县| 陵水| 璧山县| 祁东县| 栾川县| 南汇区| 吴桥县| 曲水县| 咸宁市| 鄂托克前旗| 武穴市| 武汉市| 崇信县| 营口市| 竹北市| 获嘉县| 正定县| 巫溪县| 阿拉善左旗| 卓尼县| 海阳市| 玛曲县| 拉孜县| 甘南县| 怀集县| 昌吉市|