秒殺系統設計2 2021 02 27

2021-10-20 13:28:12 字數 2781 閱讀 6377

redis 併發原子性原理

摘要:1、redis是單程序單執行緒的網路模型,用的是epoll網路模型,網路模型都是單執行緒非同步非阻塞處理網路請求

2、redis的單執行緒處理所有的客戶端連線請求,命令讀寫請求。(有些任務比如rdb和aof等操作是fork子程序處理的,不會影響redis主線程處理客戶端的命令)

3、redis提供的所有api操作,相對於服務端方面都是one by one執行的,命令是乙個接著乙個執行的,不存在並行執行的情況。

4、redis客戶端就可能會出現高併發出現錯誤的讀寫資料,下面我們舉個電商秒殺的例子來講解一下。

redis在併發中的表現

redis的api是原子性的操作,那麼多個命令在併發中也是原子性的嗎?

看看下面這段**:

<?php

$num = 10;   //系統庫存量

$user_id =  \session::get('user_id');//當前搶購使用者id

$len = \redis::llen('order:1');  //檢查庫存,order:1 定義為健名

if($len >= $num)

return '已經搶光了哦';

$result = \redis::lpush('order:1',$user_id);  //把搶到的使用者存入到列表中

if($result)

return '恭喜您!搶到了哦';

如果**正常執行,按照預期理解的是列表order:1中最多只能儲存10個使用者的id,因為庫存只有10個。

然而,但是,在使用jmeter工具模擬多使用者併發請求時,最後發現order:1中總是超過5個使用者,也就是出現了「超搶/超賣」。

分析問題就出在這一段**:

$len = \redis::llen('order:1');  //檢查庫存,order:1 定義為健名

if($len >= $num)

return '已經搶光了哦';

雖然llen和lpush2個操作如果單獨執行是具備原子性的,然而上面這個業務2個命令組合起來才算是完成乙個業務,但是2個命令組合起來就不具備原子性,所有在兩個命令之間其他客戶端會出現讀寫髒資料的情況。

在搶購進行到一定程度,假如現在已經有9個人搶購成功,又來了3個使用者同時搶購,三個使用者假設有先後順序,但是在第乙個使用者lpush到redis之前,其他的兩個使用者其實已經獲取push之前的長度9了,這時if條件將會被繞過(條件同時被滿足了),這三個使用者都能搶購成功。而實際上只剩下一件庫存可以搶了。

在高併發下,很多看似不大可能是問題的,都成了實際產生的問題了。要解決「超搶/超賣」的問題,核心在於保證檢查庫存時的操作是依次執行的,再形象的說就是把「多執行緒」轉成「單執行緒」。即使有很多使用者同時到達,也是乙個個檢查並給與搶購資格,一旦庫存搶盡,後面的使用者就無法繼續了。

我們需要使用redis的原子操作來實現這個「單執行緒」。首先我們把庫存存在goods_store:1這個列表中,假設有10件庫存,就往列表中push10個數,這個數沒有實際意義,僅僅只是代表一件庫存。搶購開始後,每到來乙個使用者,就從goods_store:1中pop乙個數,表示使用者搶購成功。當列表為空時,表示已經被搶光了。因為列表的pop操作是原子的,即使有很多使用者同時到達,也是依次執行的。搶購的示例**如下:

比如這裡我先把庫存(可用庫存,這裡我強調下哈,一般都是商品詳情頁搶購,後來者進來看到的庫存可能不再是後台系統配置的10個庫存數了)放入redis佇列:

$num=10; //庫存

$len=\redis::llen('goods_store:1'); //檢查庫存,goods_store:1 定義為健名

$count = $num-$len; //實際庫存-被搶購的庫存 = 剩餘可用庫存

for($i=0;$i<$count;$i++)

\redis::lpush('goods_store:1',1);//往goods_store列表中,未搶購之前這裡應該是預設滴push10個庫存數了

//echo \redis::llen('goods_store:1');//未搶購之前這裡就是10了

好吧,搶購時間到了:

/* 模擬搶購操作,搶購前判斷redis佇列庫存量 */

$count=\redis::lpop('goods_store:1');//lpop是移除並返回列表的第乙個元素。

if(!$count)

return '已經搶光了哦';

/* 下面處理搶購成功流程 */

\db::table('goods')->decrement('num', 1);//減少num庫存字段

使用者搶購成功後,上面的我們也可以稍微優化下,比如我們可用將使用者id存入了order:1列表中。接下來我們可以引導這些使用者去完成訂單的其他步驟,到這裡才涉及到與資料庫的互動。最終只有很少的人走到這一步吧,也就解決的資料庫的壓力問題。

我們再改下上面的**:

$user_id =  \session::get('user_id');//當前搶購使用者id

/* 模擬搶購操作,搶購前判斷redis佇列庫存量 */

$count=\redis::lpop('goods_store:1');

if(!$count)

return '已經搶光了哦';

$result = \redis::lpush('order:1',$user_id);

if($result)

return '恭喜您!搶到了哦';

為了檢測實際效果,我使用jmeter工具模擬100、200、1000個使用者併發進行搶購,經過大量的測試,最終搶購成功的使用者始終為10,沒有出現「超搶/超賣」。

秒殺系統設計

秒殺場景一般會在電商 舉行一些活動或者節假日在12306 上搶票時遇到。對於電商 中一些稀缺或者 商品,電商 一般會在約定時間點對其進行限量銷售,因為這些商品的特殊性,會吸引大量使用者前來搶購,並且會在約定的時間點同時在秒殺頁面進行搶購。限流 鑑於只有少部分使用者能夠秒殺成功,所以要限制大部分流量,...

秒殺系統設計

一 穩 1 前端 1 前端靜態資源快取 cdn 按鈕置灰 ip限流 一段時間內現在使用者ip 2 同一userid限制訪問頻率,超過頻率返回同乙個頁面,進行限流。利用驗證碼防止惡意攻擊。後端 1 請求丟到mq中按照訊息佇列進行處理,進行削峰 2 因為秒殺是讀多寫少,把庫存資料預先載入到redis中,...

秒殺系統設計

1 什麼是秒殺系統 秒殺系統 就是網路商家為 商品,以低 商品賣出做的限時限量搶購活動 2 秒殺系統可以解決什麼問題,用在哪些場景 解決問題 解決網路商家快速 商品,以低 商品賣出做的限時限量搶購活動 應用場景 商品搶購 3 秒殺系統會出現什麼問題,解決方案 出現的問題 1 併發量大 2 防止超賣 ...