自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

大家所推崇的Redis分布式鎖真的就萬無一失嗎?

存儲 存儲軟件 分布式 Redis
在單實例JVM中,常見的處理并發(fā)問題的方法有很多,比如synchronized關(guān)鍵字進(jìn)行訪問控制、volatile關(guān)鍵字、ReentrantLock等常用方法。但是在分布式環(huán)境中,上述方法卻不能在跨JVM場景中用于處理并發(fā)問題,當(dāng)業(yè)務(wù)場景需要對分布式環(huán)境中的并發(fā)問題進(jìn)行處理時,需要使用分布式鎖來實現(xiàn)。

 在單實例JVM中,常見的處理并發(fā)問題的方法有很多,比如synchronized關(guān)鍵字進(jìn)行訪問控制、volatile關(guān)鍵字、ReentrantLock等常用方法。但是在分布式環(huán)境中,上述方法卻不能在跨JVM場景中用于處理并發(fā)問題,當(dāng)業(yè)務(wù)場景需要對分布式環(huán)境中的并發(fā)問題進(jìn)行處理時,需要使用分布式鎖來實現(xiàn)。

[[268881]]

分布式鎖,是指在分布式的部署環(huán)境下,通過鎖機制來讓多客戶端互斥的對共享資源進(jìn)行訪問。

目前比較常見的分布式鎖實現(xiàn)方案有以下幾種:

  • 基于數(shù)據(jù)庫,如MySQL
  • 基于緩存,如Redis
  • 基于Zookeeper、etcd等。

這里介紹一下如何使用緩存(Redis)實現(xiàn)分布式鎖。

使用Redis實現(xiàn)分布式鎖最簡單的方案是使用命令SETNX。SETNX(SET if Not eXist)的使用方式為:SETNX key value,只在鍵key不存在的情況下,將鍵key的值設(shè)置為value,若鍵key存在,則SETNX不做任何動作。SETNX在設(shè)置成功時返回,設(shè)置失敗時返回0。當(dāng)要獲取鎖時,直接使用SETNX獲取鎖,當(dāng)要釋放鎖時,使用DEL命令刪除掉對應(yīng)的鍵key即可。

上面這種方案有一個致命問題,就是某個線程在獲取鎖之后由于某些異常因素(比如宕機)而不能正常的執(zhí)行解鎖操作,那么這個鎖就永遠(yuǎn)釋放不掉了。為此,我們可以為這個鎖加上一個超時時間。***時間我們會聯(lián)想到Redis的EXPIRE命令(EXPIRE key seconds)。但是這里我們不能使用EXPIRE來實現(xiàn)分布式鎖,因為它與SETNX一起是兩個操作,在這兩個操作之間可能會發(fā)生異常,從而還是達(dá)不到預(yù)期的結(jié)果,示例如下:

  1. // STEP 1 
  2. SETNX key value 
  3. // 若在這里(STEP1和STEP2之間)程序突然崩潰,則無法設(shè)置過期時間,將有可能無法釋放鎖 
  4. // STEP 2 
  5. EXPIRE key expireTime 

對此,正確的姿勢應(yīng)該是使用“SET key value [EX seconds] [PX milliseconds] [NX|XX]”這個命令。

從 Redis 2.6.12 版本開始, SET 命令的行為可以通過一系列參數(shù)來修改:

  • EX seconds : 將鍵的過期時間設(shè)置為 seconds 秒。 執(zhí)行 SET key value EX seconds 的效果等同于執(zhí)行 SETEX key seconds value 。
  • PX milliseconds : 將鍵的過期時間設(shè)置為 milliseconds 毫秒。 執(zhí)行 SET key value PX milliseconds 的效果等同于執(zhí)行 PSETEX key milliseconds value 。
  • NX : 只在鍵不存在時, 才對鍵進(jìn)行設(shè)置操作。 執(zhí)行 SET key value NX 的效果等同于執(zhí)行 SETNX key value 。
  • XX : 只在鍵已經(jīng)存在時, 才對鍵進(jìn)行設(shè)置操作。

舉例,我們需要創(chuàng)建一個分布式鎖,并且設(shè)置過期時間為10s,那么可以執(zhí)行以下命令:

  1. SET lockKey lockValue EX 10 NX 
  2. 或者 
  3. SET lockKey lockValue PX 10000 NX 

注意EX和PX不能同時使用,否則會報錯:ERR syntax error。

解鎖的時候還是使用DEL命令來解鎖。

修改之后的方案看上去很***,但實際上還是會有問題。試想一下,某線程A獲取了鎖并且設(shè)置了過期時間為10s,然后在執(zhí)行業(yè)務(wù)邏輯的時候耗費了15s,此時線程A獲取的鎖早已被Redis的過期機制自動釋放了。在線程A獲取鎖并經(jīng)過10s之后,改鎖可能已經(jīng)被其它線程獲取到了。當(dāng)線程A執(zhí)行完業(yè)務(wù)邏輯準(zhǔn)備解鎖(DEL key)的時候,有可能刪除掉的是其它線程已經(jīng)獲取到的鎖。

所以***的方式是在解鎖時判斷鎖是否是自己的。我們可以在設(shè)置key的時候?qū)alue設(shè)置為一個唯一值uniqueValue(可以是隨機值、UUID、或者機器號+線程號的組合、簽名等)。當(dāng)解鎖時,也就是刪除key的時候先判斷一下key對應(yīng)的value是否等于先前設(shè)置的值,如果相等才能刪除key,偽代碼示例如下:

  1. if uniqueKey == GET(key) { 
  2.     DEL key 

這里我們一眼就可以看出問題來:GET和DEL是兩個分開的操作,在GET執(zhí)行之后且在DEL執(zhí)行之前的間隙是可能會發(fā)生異常的。如果我們只要保證解鎖的代碼是原子性的就能解決問題了。這里我們引入了一種新的方式,就是Lua腳本,示例如下:

  1. if redis.call("get",KEYS[1]) == ARGV[1] then 
  2.  return redis.call("del",KEYS[1]) 
  3. else 
  4.  return 0 
  5. end 

其中ARGV[1]表示設(shè)置key時指定的唯一值。

由于Lua腳本的原子性,在Redis執(zhí)行該腳本的過程中,其他客戶端的命令都需要等待該Lua腳本執(zhí)行完才能執(zhí)行。

下面我們使用Jedis來演示一下獲取鎖和解鎖的實現(xiàn),具體如下:

  1. public boolean lock(String lockKey, String uniqueValue, int seconds){ 
  2.  SetParams params = new SetParams(); 
  3.  params.nx().ex(seconds); 
  4.  String result = jedis.set(lockKey, uniqueValue, params); 
  5.  if ("OK".equals(result)) { 
  6.  return true
  7.  } 
  8.  return false
  9. public boolean unlock(String lockKey, String uniqueValue){ 
  10.  String script = "if redis.call('get', KEYS[1]) == ARGV[1] " + 
  11.  "then return redis.call('del', KEYS[1]) else return 0 end"
  12.  Object result = jedis.eval(script,  
  13.  Collections.singletonList(lockKey),  
  14.  Collections.singletonList(uniqueValue)); 
  15.  if (result.equals(1)) { 
  16.  return true
  17.  } 
  18.  return false

如此就萬無一失了嗎?顯然不是!

表面來看,這個方法似乎很管用,但是這里存在一個問題:在我們的系統(tǒng)架構(gòu)里存在一個單點故障,如果Redis的master節(jié)點宕機了怎么辦呢?有人可能會說:加一個slave節(jié)點!在master宕機時用slave就行了!

但是其實這個方案明顯是不可行的,因為Redis的復(fù)制是異步的。舉例來說:

  1. 線程A在master節(jié)點拿到了鎖。
  2. master節(jié)點在把A創(chuàng)建的key寫入slave之前宕機了。
  3. slave變成了master節(jié)點。
  4. 線程B也得到了和A還持有的相同的鎖。(因為原來的slave里面還沒有A持有鎖的信息)

當(dāng)然,在某些場景下這個方案沒有什么問題,比如業(yè)務(wù)模型允許同時持有鎖的情況,那么使用這種方案也未嘗不可。

舉例說明,某個服務(wù)有2個服務(wù)實例:A和B,初始情況下A獲取了鎖然后對資源進(jìn)行操作(可以假設(shè)這個操作很耗費資源),B沒有獲取到鎖而不執(zhí)行任何操作,此時B可以看做是A的熱備。當(dāng)A出現(xiàn)異常時,B可以“轉(zhuǎn)正”。當(dāng)鎖出現(xiàn)異常時,比如Redis master宕機,那么B可能會同時持有鎖并且對資源進(jìn)行操作,如果操作的結(jié)果是冪等的(或者其它情況),那么也可以使用這種方案。這里引入分布式鎖可以讓服務(wù)在正常情況下避免重復(fù)計算而造成資源的浪費。

為了應(yīng)對這種情況,antriez提出了Redlock算法。Redlock算法的主要思想是:假設(shè)我們有N個Redis master節(jié)點,這些節(jié)點都是完全獨立的,我們可以運用前面的方案來對前面單個的Redis master節(jié)點來獲取鎖和解鎖,如果我們總體上能在合理的范圍內(nèi)或者N/2+1個鎖,那么我們就可以認(rèn)為成功獲得了鎖,反之則沒有獲取鎖(可類比Quorum模型)。雖然Redlock的原理很好理解,但是其內(nèi)部的實現(xiàn)細(xì)節(jié)很是復(fù)雜,要考慮很多因素

Redlock算法也并非是“銀彈”,他除了條件有點苛刻外,其算法本身也被質(zhì)疑。關(guān)于Redis分布式鎖的安全性問題,在分布式系統(tǒng)專家Martin Kleppmann和Redis的作者antirez之間就發(fā)生過一場爭論。

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2021-01-11 18:36:06

Redis分布式鎖數(shù)據(jù)庫

2013-02-01 10:48:52

2013-01-06 10:57:03

2017-10-26 14:15:32

測試工具設(shè)計開發(fā)

2013-08-12 10:37:14

云服務(wù)云計算

2021-06-22 06:13:05

私有云混合多云網(wǎng)絡(luò)架構(gòu)

2011-07-19 21:20:01

2020-10-11 20:41:14

消息隊列數(shù)據(jù)技術(shù)

2016-11-28 15:54:17

聯(lián)想網(wǎng)盤

2020-02-07 10:14:07

程序員設(shè)計人生第一份工作

2022-02-07 19:28:02

LoRa廣域網(wǎng)5G

2019-06-19 15:40:06

分布式鎖RedisJava

2021-07-26 11:09:46

Redis分布式技術(shù)

2021-08-31 15:57:32

勒索軟件零信任網(wǎng)絡(luò)罪犯

2020-07-30 09:35:09

Redis分布式鎖數(shù)據(jù)庫

2022-06-16 08:01:24

redis分布式鎖

2023-08-21 19:10:34

Redis分布式

2022-01-06 10:58:07

Redis數(shù)據(jù)分布式鎖

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2013-11-21 07:33:34

點贊
收藏

51CTO技術(shù)棧公眾號