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 先說一下樂觀鎖
樂觀鎖,顧名思義,樂觀的認為數(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)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。