jinfeng_wang

          G-G-S,D-D-U!

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            400 Posts :: 0 Stories :: 296 Comments :: 0 Trackbacks
          http://huoding.com/2015/09/14/463 



          在 Redis 里,所謂 SETNX,是「SET if Not eXists」的縮寫,也就是只有不存在的時候才設置,可以利用它來實現鎖的效果,不過很多人沒有意識到 SETNX 有陷阱!

          比如說:某個查詢數據庫的接口,因為調用量比較大,所以加了緩存,并設定緩存過期后刷新,問題是當并發量比較大的時候,如果沒有鎖機制,那么緩存過期的瞬間,大量并發請求會穿透緩存直接查詢數據庫,造成雪崩效應,如果有鎖機制,那么就可以控制只有一個請求去更新緩存,其它的請求視情況要么等待,要么使用過期的緩存。

          下面以目前 PHP 社區里最流行的 PHPRedis 擴展為例,實現一段演示代碼:

          <?php  $ok = $redis->setNX($key, $value);  if ($ok) {     $cache->update();     $redis->del($key); }  ?>

          緩存過期時,通過 SetNX  獲取鎖,如果成功了,那么更新緩存,然后刪除鎖。看上去邏輯非常簡單,可惜有問題:如果請求執行因為某些原因意外退出了,導致創建了鎖但是沒有刪除鎖,那么這個鎖將一直存在,以至于以后緩存再也得不到更新。于是乎我們需要給鎖加一個過期時間以防不測:

          <?php  $redis->multi(); $redis->setNX($key, $value); $redis->expire($key, $ttl); $redis->exec();  ?>

          因為 SetNX 不具備設置過期時間的功能,所以我們需要借助 Expire 來設置,同時我們需要把兩者用 Multi/Exec 包裹起來以確保請求的原子性,以免 SetNX 成功了 Expire 卻失敗了。 可惜還有問題:當多個請求到達時,雖然只有一個請求的 SetNX 可以成功,但是任何一個請求的 Expire 卻都可以成功,如此就意味著即便獲取不到鎖,也可以刷新過期時間,如果請求比較密集的話,那么過期時間會一直被刷新,導致鎖一直有效。于是乎我們需要在保證原子性的同時,有條件的執行 Expire,接著便有了如下 Lua 代碼:

          local key   = KEYS[1] local value = KEYS[2] local ttl   = KEYS[3]  local ok = redis.call('setnx', key, value)   if ok == 1 then   redis.call('expire', key, ttl) end   return ok

          沒想到實現一個看起來很簡單的功能還要用到 Lua 腳本,著實有些麻煩。其實 Redis 已經考慮到了大家的疾苦,從 2.6.12 起,SET 涵蓋了 SETEX 的功能,并且 SET 本身已經包含了設置過期時間的功能,也就是說,我們前面需要的功能只用 SET 就可以實現。

          <?php  $ok = $redis->set($key, $value, array('nx', 'ex' => $ttl));  if ($ok) {     $cache->update();     $redis->del($key); }  ?>

          如上代碼是完美的嗎?答案是還差一點!設想一下,如果一個請求更新緩存的時間比較長,甚至比鎖的有效期還要長,導致在緩存更新過程中,鎖就失效了,此時另一個請求會獲取鎖,但前一個請求在緩存更新完畢的時候,如果不加以判斷直接刪除鎖,就會出現誤刪除其它請求創建的鎖的情況,所以我們在創建鎖的時候需要引入一個隨機值:

          <?php  $ok = $redis->set($key, $random, array('nx', 'ex' => $ttl));  if ($ok) {     $cache->update();      if ($redis->get($key) == $random) {         $redis->del($key);     } }  ?>

          如此基本實現了單機鎖,假如要實現分布鎖,請參考:Distributed locks with Redis,不過分布式鎖需要注意的地方更多:How to do distributed lockingIs Redlock safe

          posted on 2016-12-14 21:30 jinfeng_wang 閱讀(172) 評論(0)  編輯  收藏 所屬分類: 2016-REDIS
          主站蜘蛛池模板: 睢宁县| 奉化市| 宁乡县| 沐川县| 通化市| 岫岩| 赣榆县| 慈溪市| 红安县| 临沂市| 邵阳市| 吉安县| 磐安县| 天长市| 邢台市| 云安县| 新建县| 南郑县| 蕲春县| 玛多县| 东阳市| 澄迈县| 昌黎县| 和平县| 凤庆县| 博客| 大安市| 桂东县| 香格里拉县| 泰宁县| 民县| 辰溪县| 霞浦县| 临安市| 大埔县| 东安县| 绩溪县| 东海县| 石家庄市| 英吉沙县| 师宗县|