這幾天都在學習Redis的相關知識,發現了一個問題,Redis雖然是單線程的,但是他有一個特點:IO多路複用,這樣的特點使2個請求同時對同一key進行操作時,會出現2個請求同時拿到該key的值,進行了重複的操作,在秒殺中的體現爲超賣;
具體代碼爲:
public function redis1(){
$redis = new \Redis();
$redis->connect('127.0.0.1',6379);
for ($i=1;$i<=500;$i++) {
$num = $redis->get('val');
$redis->set('val', $num+1);
usleep(10000);
}
}
使用usleep是因爲redis速度太快,不睡眠的話,體現不出來2個請求同時進行。。。
我發現了一個很奇怪的現象,如果我分別請求兩次相同的接口,程序是同步進行的,第一個請求進行中的時候,第二個請求會阻塞。當兩個接口都執行完畢,查看val的值是1000。(大概因爲是在同一個瀏覽器執行同一個接口)
但我又寫了一個一模一樣的接口,redis2
public function redis2(){
$redis = new \Redis();
$redis->connect('127.0.0.1',6379);
for ($i=1;$i<=500;$i++) {
$num = $redis->get('val');
$redis->set('val', $num+1);
usleep(10000);
}
}
我分別請求接口redis1和redis2,結果這兩個請求是異步的(不同瀏覽器請求同一個接口也可以),所以也導致了最後val的值是800多,結果是小於1000的,這說明了兩個請求在搶佔資源,而且可能會同時搶佔到同一個資源,然後進行操作。這就是爲什麼redis還是需要鎖機制的原因了。。。
要使用redis的鎖,我們需要用到的Redis方法是setnx,該方法的用法是setnx(key,value),嘗試設值,如果key已經存在值,則返回0,不存在則設值,並返回1。這個方法就很符合鎖的特點,代碼如下:
public function redisTran1(){
$redis = new \Redis();
$redis->connect('127.0.0.1',6379);
for ($i=1;$i<=500;$i++) {
while (true){
if(!$redis->setnx('lock',1))//拿鎖的權限,沒拿到則continue
continue;
$num = $redis->get('val');
$redis->set('val', $num+1);
$redis->del('lock');//解鎖
break;
}
usleep(10000);
}
}
我們也是和上面一樣,寫一個一模一樣的接口,redisTran2,然後分別請求接口redisTran1和redisTran2,最後的val的結果是1000,說明鎖機制成功生效了。
2020-2-22 注:
對於redis的原子性,我有了更加深入的理解,redis的原子性,說的是redis的api具有原子性,像第一次測試,val的值小於1000,這樣的結果是不具備原子性的;事實上,redis的工作流程如下:
上面圖片中的箭頭都代表了redis的api,且它們是上一個執行完,下一個才繼續執行,api是具有原子性的。
如果想要業務也具有原子性,直接用一步到位的方法,如incr。
此外,根據我的 調查,redis的事務是不具備原子性的,即前一條語句執行失敗,後面的還是會繼續執行。