使用Redis實現搶購的一種思路

搶購是現在很常見的一個應用場景,主要須要解決的問題有兩個: php

1 高併發對數據庫產生的壓力 redis

2 競爭狀態下如何解決庫存的正確減小(「超賣」問題) 數據庫

對於第一個問題,已經很容易想到用緩存來處理搶購,避免直接操做數據庫,例如使用Redis。重點在於第二個問題,咱們看看下面一種常規的實現代碼: 緩存

<?php 

require('predis/src/Autoloader.php');

$redis = new Predis\Client(array(
	'scheme' => 'tcp',
	'host'   => '127.0.0.1',
	'port'   => '6379'
));

//redis 登陸 
$redis->auth('123456');

//庫存
$num = 10;

//用戶id
$user_id = $_SESSION['user_id'];

//檢查庫存 
$len = $redis->llen('order:1');
if($len >= $num){
	exit('已經搶光了');
}

//把搶到的用戶存入到列表中  
$result = $redis->lpush('order:1',$user_id);
if($result){
	echo '搶到了';
}

?>

若是代碼正常運行,列表order:1中最多隻能存儲10個用戶的id,由於庫存只有10個。 多線程

然而,在使用Apache AB工具模擬不少用戶併發請求時,最後發現order:1中老是超過10個用戶,也就是出現了「超賣」。 併發

問題就出在這一段代碼: tcp

//檢查庫存 
$len = $redis->llen('order:1');
if($len >= $num){
	exit('已經搶光了');
}

在搶購進行到必定程度,假如如今已經有9我的搶購成功,又來了3個用戶同時搶購,這時if條件將會被繞過,這三個用戶都能搶購成功。而實際上只有一件庫存能夠搶了。 高併發

在高併發下,不少不是問題的,都成了問題。要解決「超賣」問題,核心在於保證檢查庫存時的操做是依次執行的,形象的說就是把「多線程」轉成「單線程」。即便有不少用戶同時到達,也是一個個檢查並給與搶購資格,一旦庫存搶盡,後面的用戶就沒法繼續了。 工具

咱們須要使用Redis的原子操做來實現這個「單線程」。首先咱們把庫存存在goods:1這個列表中,假設有10件庫存,就往列表中push10個數,這個數沒有實際意義,僅僅表明一件庫存。搶購開始後,每到來一個用戶,就從goods:1中pop一個數,表示用戶搶購成功。當列表爲空時,表示已經被搶光了。由於列表的pop操做是原子的,即便有不少用戶同時到達,也是依次執行的。搶購的示例代碼以下: 測試

<?php 
//搶購 
require('predis/src/Autoloader.php');

$redis = new Predis\Client(array(
	'scheme' => 'tcp',
	'host'   => '127.0.0.1',
	'port'   => '6379'
));

$redis->auth('123456');

//用戶ID
$user_id = $_SESSION['user_id'];

$check = $redis->lpop('goods:1');
if(!$check){
	exit('搶光了');
}

$result = $redis->lpush('order:1',$user_id);
if($result){
	echo '搶購成功';
}

?>

用戶搶購成功後,咱們將用戶ID存入了order:1列表中。接下來咱們能夠引導這些用戶去完成訂單的其餘步驟,這裏才涉及到與數據庫的交互。最終只有不多的人走到這一步,也就解決的數據庫的壓力問題。

爲了檢測實際效果,我使用Apache AB工具模擬十、20、1000個用戶併發進行搶購,通過大量的測試,最終搶購成功的用戶始終爲10,沒有出現「超賣」。

相關文章
相關標籤/搜索