redis隊列和秒殺應(yīng)用方式
1.簡述
redis隊列一般用于緩解數(shù)據(jù)庫壓力 ,諸如秒殺,郵件群發(fā),消息推送等等
redis的加入能很好的 幫助系統(tǒng)中 各個模塊解耦。
而Redis不僅可作為緩存服務(wù)器,還可用作消息隊列。它的列表類型天生支持用作消息隊列。如下圖所示:

對于服務(wù)器減少io 壓力 有一定的幫助
2.秒殺的原理

秒殺基本原理比較簡單
用戶點擊搶購按鈕 -> 把uid 和時間存入redis的隊列中 -> 服務(wù)器中有一個入庫程序不停輪詢redis隊列是否有數(shù)據(jù) -> 如果有存入數(shù)據(jù)庫
這里面有2點需要注意一下:
- 1. 插入隊列的時候 ,需要判斷庫傳,不能出現(xiàn)多插入
- 2. 在入庫的時候 如果出現(xiàn)數(shù)據(jù)插入失敗的情況 需要進行回滾
3.秒殺的代碼實現(xiàn)
用戶操作秒殺:
header("Content-type: text/html; charset=utf-8");
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis_name= "miaosha";
//庫存
$nums = 10;
//用戶id
$user_id = $_GET['uid'];
if(($redis->llen($redis_name)) < $nums ){
$redis->lpush($redis_name,json_encode(array('uid'=>$user_id,'time'=>microtime())));
echo $user_id."秒殺成功!";
exit();
}else{
echo "秒殺失敗!";
exit();
}
$redis->close();后臺處理秒殺隊列:
header("Content-type: text/html; charset=utf-8");
error_reporting(E_ALL);
require_once './db.php';
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis_name = "miaosha";
//數(shù)據(jù)庫
$configs =array('host'=>'127.0.0.1','port'=>'3306','user'=>'***','passwd'=>'','dbname'=>'test');
$mysql = new MMysql($configs);
//處理開始
while ($count = $redis->lLen($redis_name)) {
$task = $redis->rpop($redis_name);
$taskdata = json_decode($task, true);
$data = array(
'uid'=>$taskdata['uid'],
'time'=>$taskdata['time'],
);
$rs = $mysql->insert('redis',$data);
if(!$rs){
//由于我們是在右邊取,所以如果數(shù)據(jù)插入失敗了要從左邊放回去(重新排隊),以免影響隊列中其他元素的處理
$redis->lpush($redis_name,$task);
echo "處理失敗<br>";
}else{
echo "處理成功<br>";
}
sleep(1);
}
$redis->close();4.關(guān)于redis里的鎖
4.1 先說一下樂觀鎖
樂觀鎖,顧名思義,樂觀的認(rèn)為數(shù)據(jù)不會被修改,只有當(dāng)更新時才去判斷數(shù)據(jù)是否被修改過,通常用版本號或時間戳來實現(xiàn)。
redis中的事務(wù)通過watch和multi來實現(xiàn)。
WATCH命令可以監(jiān)控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之后的事務(wù)就不會執(zhí)行。監(jiān)控一直持續(xù)到EXEC命令(事務(wù)中的命令是在EXEC之后才執(zhí)行的,所以在MULTI命令后可以修改WATCH監(jiān)控的鍵值)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 60);
//
$tnums = $redis->get('goods_stock_nums');
//設(shè)置商品的庫存
if($tnums==0){
echo "活動已經(jīng)結(jié)束,明天請早end!";
exit();
}
//監(jiān)視該key
$redis->watch('goods_stock_nums');
//sleep(5);
//開啟事務(wù)
$redis->multi();
//修改庫存數(shù)
$redis->decr('goods_stock_nums');
//提交事務(wù),如果在此期間有其他請求修改了該key,那么事務(wù)會失敗
if ($redis->exec()) {
echo '搶購成功suc';
} else {
echo '數(shù)據(jù)錯誤,請重新再試fail';
}可以看到我在程序中加入了sleep(5) 這行代碼。
這是方便我在客戶端去實驗

如果我在運行上面這段代碼過程中,我用客戶端修改了這個值。
那么上面這段代碼就會失敗,返回 數(shù)據(jù)錯誤,請重新再試fail
4.2 再說一下 悲觀鎖
function getRedis()
{
$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 60);
return $redis;
}
function lock($key, $random)
{
$redis = getRedis();
return $redis->set($key, $random, ['nx', 'ex' => 3]);
}
function unlock($key, $random)
{
$redis = getRedis();
//使用lua腳本保證原子性
$script = 'if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end';
return $redis->eval($script, [$key, $random], 1);
}
function decrGoodsStockNums()
{
$redis = getRedis();
//獲取商品庫存數(shù)
$ret = $redis->get('goods_stock_nums');
if ($ret === false) {
return false;
}
if ($ret <= 0) {
return false;
}
$random = mt_rand();
//先獲取鎖
if (lock('goods_stock_nums_lock', $random)) {
//修改庫存數(shù)
$redis->decr('goods_stock_nums');
//釋放鎖
unlock('goods_stock_nums_lock', $random);
return true;
} else {
usleep(100);
decrGoodsStockNums();
}
}
decrGoodsStockNums();上面這段引用別人的代碼
但是這種鎖的機制還是不能保證事務(wù)的安全
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

