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

使用 Redis 實(shí)現(xiàn)分布式系統(tǒng)輕量級協(xié)調(diào)技術(shù)

運(yùn)維 數(shù)據(jù)庫運(yùn)維 其他數(shù)據(jù)庫 分布式 Redis
Redis 經(jīng)常被人們認(rèn)為是一種 NoSQL 軟件,但其本質(zhì)上是一種分布式的數(shù)據(jù)結(jié)構(gòu)服務(wù)器軟件,提供了一個分布式的基于內(nèi)存的數(shù)據(jù)結(jié)構(gòu)存儲服務(wù)。

在分布式系統(tǒng)中,各個進(jìn)程(本文使用進(jìn)程來描述分布式系統(tǒng)中的運(yùn)行主體,它們可以在同一個物理節(jié)點(diǎn)上也可以在不同的物理節(jié)點(diǎn)上)相互之間通常是需要協(xié)調(diào)進(jìn)行運(yùn)作的,有時是不同進(jìn)程所處理的數(shù)據(jù)有依賴關(guān)系,必須按照一定的次序進(jìn)行處理,有時是在一些特定的時間需要某個進(jìn)程處理某些事務(wù)等等,人們通常會使用分布式鎖、選舉算法等技術(shù)來協(xié)調(diào)各個進(jìn)程之間的行為。因?yàn)榉植际较到y(tǒng)本身的復(fù)雜特性,以及對于容錯性的要求,這些技術(shù)通常是重量級的,比如 Paxos 算法,欺負(fù)選舉算法,ZooKeeper 等,側(cè)重于消息的通信而不是共享內(nèi)存,通常也是出了名的復(fù)雜和難以理解,當(dāng)在具體的實(shí)現(xiàn)和實(shí)施中遇到問題時都是一個挑戰(zhàn)。

Redis 經(jīng)常被人們認(rèn)為是一種 NoSQL 軟件,但其本質(zhì)上是一種分布式的數(shù)據(jù)結(jié)構(gòu)服務(wù)器軟件,提供了一個分布式的基于內(nèi)存的數(shù)據(jù)結(jié)構(gòu)存儲服務(wù)。在實(shí)現(xiàn)上,僅使用一個線程來處理具體的內(nèi)存數(shù)據(jù)結(jié)構(gòu),保證它的數(shù)據(jù)操作命令的原子特性;它同時還支持基于 Lua 的腳本,每個 Redis 實(shí)例使用同一個 Lua 解釋器來解釋運(yùn)行 Lua 腳本,從而 Lua 腳本也具備了原子特性,這種原子操作的特性使得基于共享內(nèi)存模式的分布式系統(tǒng)的協(xié)調(diào)方式成了可能,而且具備了很大的吸引力,和復(fù)雜的基于消息的機(jī)制不同,基于共享內(nèi)存的模式對于很多技術(shù)人員來說明顯容易理解的多,特別是那些已經(jīng)了解多線程或多進(jìn)程技術(shù)的人。在具體實(shí)踐中,也并不是所有的分布式系統(tǒng)都像分布式數(shù)據(jù)庫系統(tǒng)那樣需要嚴(yán)格的模型的,而所使用的技術(shù)也不一定全部需要有堅(jiān)實(shí)的理論基礎(chǔ)和數(shù)學(xué)證明,這就使得基于 Redis 來實(shí)現(xiàn)分布式系統(tǒng)的協(xié)調(diào)技術(shù)具備了一定的實(shí)用價值,實(shí)際上,人們也已經(jīng)進(jìn)行了不少嘗試。本文就其中的一些協(xié)調(diào)技術(shù)進(jìn)行介紹。

signal/wait 操作

在分布式系統(tǒng)中,有些進(jìn)程需要等待其它進(jìn)程的狀態(tài)的改變,或者通知其它進(jìn)程自己的狀態(tài)的改變,比如,進(jìn)程之間有操作上的依賴次序時,就有進(jìn)程需要等待,有進(jìn)程需要發(fā)射信號通知等待的進(jìn)程進(jìn)行后續(xù)的操作,這些工作可以通過 Redis 的 Pub/Sub 系列命令來完成,比如:

 

  1. import redis, time 
  2. rc = redis.Redis() 
  3.  
  4. def wait( wait_for ): 
  5.  ps = rc.pubsub() 
  6.  ps.subscribe( wait_for ) 
  7.  ps.get_message() 
  8.  wait_msg = None 
  9.  while True: 
  10.  msg = ps.get_message() 
  11.  if msg and msg['type'] == 'message'
  12.  wait_msg = msg  
  13.  break 
  14.  time.sleep(0.001
  15.  ps.close() 
  16.  return wait_msg 
  17.  
  18. def signal_broadcast( wait_in, data ): 
  19.  wait_count = rc.publish(wait_in, data) 
  20.  return wait_count 

用這個方法很容易進(jìn)行擴(kuò)展實(shí)現(xiàn)其它的等待策略,比如 try wait,wait 超時,wait 多個信號時是要等待全部信號還是任意一個信號到達(dá)即可返回等等。因?yàn)?Redis 本身支持基于模式匹配的消息訂閱(使用 psubscribe 命令),設(shè)置 wait 信號時也可以通過模式匹配的方式進(jìn)行。

和其它的數(shù)據(jù)操作不同,訂閱消息是即時易逝的,不在內(nèi)存中保存,不進(jìn)行持久化保存,如果客戶端到服務(wù)端的連接斷開的話也是不會重發(fā)的,但是在配置了 master/slave 節(jié)點(diǎn)的情況下,會把 publish 命令同步到 slave 節(jié)點(diǎn)上,這樣我們就可以同時在 master 以及 slave 節(jié)點(diǎn)的連接上訂閱某個頻道,從而可以同時接收到發(fā)布者發(fā)布的消息,即使 master 在使用過程中出故障,或者到 master 的連接出了故障,我們?nèi)匀荒軌驈? slave 節(jié)點(diǎn)獲得訂閱的消息,從而獲得更好的魯棒性。另外,因?yàn)閿?shù)據(jù)不用寫入磁盤,這種方法在性能上也是有優(yōu)勢的。

上面的方法中信號是廣播的,所有在 wait 的進(jìn)程都會收到信號,如果要將信號設(shè)置成單播,只允許其中一個收到信號,則可以通過約定頻道名稱模式的方式來實(shí)現(xiàn),比如:

頻道名稱 = 頻道名前綴 (channel) + 訂閱者全局*** ID(myid)

其中*** ID 可以是 UUID,也可以是一個隨機(jī)數(shù)字符串,確保全局***即可。在發(fā)送 signal 之前先使用“pubsub channels channel*”命令獲得所有的訂閱者訂閱的頻道,然后發(fā)送信號給其中一個隨機(jī)指定的頻道;等待的時候需要傳遞自己的*** ID,將頻道名前綴和*** ID 合并為一個頻道名稱,然后同前面例子一樣進(jìn)行 wait。示例如下:

 

  1. import random 
  2.  
  3. single_cast_script=""
  4.  local channels = redis.call('pubsub''channels', ARGV[1]..'*'); 
  5.  if #channels == 0 then 
  6.  return 0
  7.  end; 
  8.  local index= math.mod(math.floor(tonumber(ARGV[2])), #channels) + 1
  9.  return redis.call( 'publish', channels[index], ARGV[3]);  
  10.  ""
  11.  
  12. def wait_single( channel, myid): 
  13.  return wait( channel + myid ) 
  14.  
  15. def signal_single( channel, data): 
  16.  rand_num = int(random.random() * 65535
  17.  return rc.eval( single_cast_script, 0, channel, str(rand_num), str(data) )

#p#

分布式鎖 Distributed Locks

分布式鎖的實(shí)現(xiàn)是人們探索的比較多的一個方向,在 Redis 的官方網(wǎng)站上專門有一篇文檔介紹 基于 Redis 的分布式鎖 ,其中提出了 Redlock 算法,并列出了多種語言的實(shí)現(xiàn)案例,這里作一簡要介紹。

Redlock 算法著眼于滿足分布式鎖的三個要素:

  • 安全性:保證互斥,任何時間至多只有一個客戶端可以持有鎖
  • 免死鎖:即使當(dāng)前持有鎖的客戶端崩潰或者從集群中被分開了,其它客戶端最終總是能夠獲得鎖。
  • 容錯性:只要大部分的 Redis 節(jié)點(diǎn)在線,那么客戶端就能夠獲取和釋放鎖。

鎖的一個簡單直接的實(shí)現(xiàn)方法就是用 SET NX 命令設(shè)置一個設(shè)定了存活周期 TTL 的 Key 來獲取鎖,通過刪除 Key 來釋放鎖,通過存活周期來保證避免死鎖。不過這個方法存在單點(diǎn)故障風(fēng)險,如果部署了 master/slave 節(jié)點(diǎn),則在特定條件下可能會導(dǎo)致安全性方面的沖突,比如:

  1. 客戶端 A 從 master 節(jié)點(diǎn)獲得鎖
  2. master 節(jié)點(diǎn)在將 key 復(fù)制到 slave 節(jié)點(diǎn)之前崩潰了
  3. slave 節(jié)點(diǎn)提升為新的 master 節(jié)點(diǎn)
  4. 客戶端 B 從新的 master 節(jié)點(diǎn)獲得了鎖,而這個鎖實(shí)際上已經(jīng)由客戶端 A 所持有,導(dǎo)致了系統(tǒng)中有兩個客戶端在同一時間段內(nèi)持有同一個互斥鎖,破壞了互斥鎖的安全性。

在 Redlock 算法中,通過類似于下面這樣的命令進(jìn)行加鎖:

 

  1. SET resource_name my_random_value NX PX 30000 

這里的 my_random_value 為全局不同的隨機(jī)數(shù),每個客戶端需要自己產(chǎn)生這個隨機(jī)數(shù)并且記住它,后面解鎖的時候需要用到它。

解鎖則需要通過一個 Lua 腳本來執(zhí)行,不能簡單地直接刪除 Key,否則可能會把別人持有的鎖給釋放了:

 

  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] 的值就是前面加鎖的時候的 my_random_value 的值。

如果需要更好的容錯性,可以建立一個有 N(N 為奇數(shù))個相互獨(dú)立完備的 Redis 冗余節(jié)點(diǎn)的集群,這種情況下,一個客戶端獲得鎖和釋放鎖的算法如下:

  1. 先獲取當(dāng)前時間戳 timestamp_1,以毫秒為單位。
  2. 以相同的 Key 和隨機(jī)數(shù)值,依次從 N 個節(jié)點(diǎn)獲取鎖,每次獲取鎖都設(shè)置一個超時,超時時限要保證小于所有節(jié)點(diǎn)上該鎖的自動釋放時間,以免在某個節(jié)點(diǎn)上耗時過長,通常都設(shè)的比較短。
  3. 客戶端將當(dāng)前時間戳減去***步中的時間戳 timestamp_1,計(jì)算獲取鎖總消耗時間。只有當(dāng)客戶端獲得了半數(shù)以上節(jié)點(diǎn)的鎖,而且總耗時少于鎖存活時間,該客戶端才被認(rèn)為已經(jīng)成功獲得了鎖。
  4. 如果獲得了鎖,則其存活時間為開始預(yù)設(shè)鎖存活時間減去獲取鎖總耗時間。
  5. 如果客戶端不能獲得鎖,則應(yīng)該馬上在所有節(jié)點(diǎn)上解鎖。
  6. 如果要重試,則在隨機(jī)延時之后重新去獲取鎖。
  7. 獲得了鎖的客戶端要釋放鎖,簡單地在所有節(jié)點(diǎn)上解鎖即可。

Redlock 算法不需要保證 Redis 節(jié)點(diǎn)之間的時鐘是同步的(不論是物理時鐘還是邏輯時鐘),這點(diǎn)和傳統(tǒng)的一些基于同步時鐘的分布式鎖算法有所不同。Redlock 算法的具體的細(xì)節(jié)可以參閱 Redis 的官方文檔,以及文檔中列出的多種語言版本的實(shí)現(xiàn)。

選舉算法

在分布式系統(tǒng)中,經(jīng)常會有些事務(wù)是需要在某個時間段內(nèi)由一個進(jìn)程來完成,或者由一個進(jìn)程作為 leader 來協(xié)調(diào)其它的進(jìn)程,這個時候就需要用到選舉算法,傳統(tǒng)的選舉算法有欺負(fù)選舉算法(霸道選舉算法)、環(huán)選舉算法、Paxos 算法、Zab 算法 (ZooKeeper) 等,這些算法有些依賴于消息的可靠傳遞以及時鐘同步,有些過于復(fù)雜,難以實(shí)現(xiàn)和驗(yàn)證。新的 Raft 算法相比較其它算法來說已經(jīng)容易了很多,不過它仍然需要依賴心跳廣播和邏輯時鐘,leader 需要不斷地向 follower 廣播消息來維持從屬關(guān)系,節(jié)點(diǎn)擴(kuò)展時也需要其它算法配合。

選舉算法和分布式鎖有點(diǎn)類似,任意時刻最多只能有一個 leader 資源。當(dāng)然,我們也可以用前面描述的分布式鎖來實(shí)現(xiàn),設(shè)置一個 leader 資源,獲得這個資源鎖的為 leader,鎖的生命周期過了之后,再重新競爭這個資源鎖。這是一種競爭性的算法,這個方法會導(dǎo)致有比較多的空檔期內(nèi)沒有 leader 的情況,也不好實(shí)現(xiàn) leader 的連任,而 leader 的連任是有比較大的好處的,比如 leader 執(zhí)行任務(wù)可以比較準(zhǔn)時一些,查看日志以及排查問題的時候也方便很多,如果我們需要一個算法實(shí)現(xiàn) leader 可以連任,那么可以采用這樣的方法:

 

  1. import redis 
  2.  
  3. rc = redis.Redis() 
  4. local_selector = 0 
  5.  
  6. def master(): 
  7.  global local_selector 
  8.  master_selector = rc.incr('master_selector'
  9.  if master_selector == 1
  10.  # initial / restarted 
  11.  local_selector = master_selector 
  12.  else
  13.  if local_selector > 0
  14.  # I'm the master before 
  15.  if local_selector > master_selector: 
  16.  # lost, maybe the db is fail-overed. 
  17.  local_selector = 0 
  18.  else
  19.  # continue to be the master 
  20.  local_selector = master_selector 
  21.  if local_selector > 0
  22.  # I'm the current master 
  23.  rc.expire('master_selector'20
  24.  return local_selector > 0 

這個算法鼓勵連任,只有當(dāng)前的 leader 發(fā)生故障或者執(zhí)行某個任務(wù)所耗時間超過了任期、或者 Redis 節(jié)點(diǎn)發(fā)生故障恢復(fù)之后才需要重新選舉出新的 leader。在 master/slave 模式下,如果 master 節(jié)點(diǎn)發(fā)生故障,某個 slave 節(jié)點(diǎn)提升為新的 master 節(jié)點(diǎn),即使當(dāng)時 master_selector 值尚未能同步成功,也不會導(dǎo)致出現(xiàn)兩個 leader 的情況。如果某個 leader 一直連任,則 master_selector 的值會一直遞增下去,考慮到 master_selector 是一個 64 位的整型類型,在可預(yù)見的時間內(nèi)是不可能溢出的,加上每次進(jìn)行 leader 更換的時候 master_selector 會重置為從 1 開始,這種遞增的方式是可以接受的,但是碰到 Redis 客戶端(比如 Node.js)不支持 64 位整型類型的時候就需要針對這種情況作處理。如果當(dāng)前 leader 進(jìn)程處理時間超過了任期,則其它進(jìn)程可以重新生成新的 leader 進(jìn)程,老的 leader 進(jìn)程處理完畢事務(wù)后,如果新的 leader 的進(jìn)程經(jīng)歷的任期次數(shù)超過或等于老的 leader 進(jìn)程的任期次數(shù),則可能會出現(xiàn)兩個 leader 進(jìn)程,為了避免這種情況,每個 leader 進(jìn)程在處理完任期事務(wù)之后都應(yīng)該檢查一下自己的處理時間是否超過了任期,如果超過了任期,則應(yīng)當(dāng)先設(shè)置 local_selector 為 0 之后再調(diào)用 master 檢查自己是否是 leader 進(jìn)程。

#p#

消息隊(duì)列

消息隊(duì)列是分布式系統(tǒng)之間的通信基本設(shè)施,通過消息可以構(gòu)造復(fù)雜的進(jìn)程間的協(xié)調(diào)操作和互操作。Redis 也提供了構(gòu)造消息隊(duì)列的原語,比如 Pub/Sub 系列命令,就提供了基于訂閱/發(fā)布模式的消息收發(fā)方法,但是 Pub/Sub 消息并不在 Redis 內(nèi)保持,從而也就沒有進(jìn)行持久化,適用于所傳輸?shù)南⒓词箒G失了也沒有關(guān)系的場景。

如果要考慮到持久化,則可以考慮 list 系列操作命令,用 PUSH 系列命令(LPUSH, RPUSH 等)推送消息到某個 list,用 POP 系列命令(LPOP, RPOP,BLPOP,BRPOP 等)獲取某個 list 上的消息,通過不同的組合方式可以得到 FIFO,F(xiàn)ILO,比如:

 

  1. import redis 
  2. rc = redis.Redis() 
  3.  
  4. def fifo_push(q, data): 
  5.  rc.lpush(q, data) 
  6.  
  7. def fifo_pop(q): 
  8.  return rc.rpop(q) 
  9.  
  10. def filo_push(q, data): 
  11.  rc.lpush(q, data) 
  12.  
  13. def filo_pop(q): 
  14.  return rc.lpop(q) 

如果用 BLPOP,BRPOP 命令替代 LPOP, RPOP,則在 list 為空的時候還支持阻塞等待。不過,即使按照這種方式實(shí)現(xiàn)了持久化,如果在 POP 消息返回的時候網(wǎng)絡(luò)故障,則依然會發(fā)生消息丟失,針對這種需求 Redis 提供了 RPOPLPUSH 和 BRPOPLPUSH 命令來先將提取的消息保存在另外一個 list 中,客戶端可以先從這個 list 查看和處理消息數(shù)據(jù),處理完畢之后再從這個 list 中刪除消息數(shù)據(jù),從而確保了消息不會丟失,示例如下:

 

  1. def safe_fifo_push(q, data): 
  2.  rc.lpush(q, data) 
  3.  
  4. def safe_fifo_pop(q, cache): 
  5.  msg = rc.rpoplpush(q, cache) 
  6.  # check and do something on msg 
  7.  rc.lrem(cache, 1) # remove the msg in cache list. 
  8.  return msg 

如果使用 BRPOPLPUSH 命令替代 RPOPLPUSH 命令,則可以在 q 為空的時候阻塞等待。

結(jié)語

使用 Redis 作為分布式系統(tǒng)的共享內(nèi)存,以共享內(nèi)存模式為基礎(chǔ)來實(shí)現(xiàn)分布式系統(tǒng)協(xié)調(diào)技術(shù),雖然不像傳統(tǒng)的基于消息傳遞的技術(shù)那樣有著堅(jiān)實(shí)的理論證明的基礎(chǔ),但是它在一些要求不苛刻的情況下不失為一種簡單實(shí)用的輕量級解決方案,畢竟不是每個系統(tǒng)都需要嚴(yán)格的容錯性等要求,也不是每個系統(tǒng)都會頻繁地發(fā)生進(jìn)程異常,而且 Redis 本身已經(jīng)經(jīng)受了工業(yè)界的多年實(shí)踐和考驗(yàn)。另外,用 Redis 技術(shù)還有一些額外的好處,比如在開發(fā)過程中和生產(chǎn)環(huán)境中都可以直接觀察到鎖、隊(duì)列的內(nèi)容,實(shí)施的時候也不需要額外的特別配置過程等,它足夠簡單,在調(diào)試問題的時候邏輯清晰,進(jìn)行排查和臨時干預(yù)也比較方便。在可擴(kuò)展性方面也比較好,可以動態(tài)擴(kuò)展分布式系統(tǒng)的進(jìn)程數(shù)目,而不需要事先預(yù)定好進(jìn)程數(shù)目。

Redis 支持基于 Key 值 hash 的集群,在集群中應(yīng)用本文所述技術(shù)時建議另外部署專用 Redis 節(jié)點(diǎn)(或者冗余 Redis 節(jié)點(diǎn)集群)來使用,因?yàn)樵诨?Key 值 hash 的集群中,不同的 Key 值會根據(jù) hash 值被分布到不同的集群節(jié)點(diǎn)上,而且對于 Lua 腳本的支持也受到限制,難以保證一些操作的原子性,這一點(diǎn)是需要考慮到的。使用專用節(jié)點(diǎn)還有一個好處是專用節(jié)點(diǎn)的數(shù)據(jù)量會少很多,當(dāng)應(yīng)用了 master/slave 部署或者 AOF 模式的時候,因?yàn)閿?shù)據(jù)量少,master 和 slave 之間的同步會少很多,AOF 模式實(shí)時寫入磁盤的數(shù)據(jù)也少很多,這樣子也可以大大提高可用性。

本文示例所列 Python 代碼在 Python3.4 下運(yùn)行,Redis 客戶端采用 redis 2.10.3 ,Redis 服務(wù)端版本為 3.0.1 版。

原文鏈接:http://www.ibm.com/developerworks/cn/opensource/os-cn-redis-coordinate/index.html?ca=drs-&utm_source=tuicool

責(zé)任編輯:Ophira 來源: ibm developerWorks 中國
相關(guān)推薦

2021-06-01 07:57:42

Zookeeper分布式系統(tǒng)

2021-04-14 13:32:50

Redis輕量級分布式

2023-02-23 07:55:41

2024-04-29 08:42:23

2024-06-11 13:50:43

2024-10-07 10:07:31

2012-11-06 13:58:26

分布式云計(jì)算分布式協(xié)同

2024-04-01 05:10:00

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

2017-10-11 16:12:19

內(nèi)存

2023-10-26 18:10:43

分布式并行技術(shù)系統(tǒng)

2023-12-18 10:24:59

2015-08-19 15:45:33

2019-10-10 09:16:34

Zookeeper架構(gòu)分布式

2023-08-21 19:10:34

Redis分布式

2022-01-06 10:58:07

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

2014-05-08 14:38:26

tornadoredissession

2019-06-19 15:40:06

分布式鎖RedisJava

2019-12-26 08:59:20

Redis主從架構(gòu)

2023-03-13 00:08:26

2021-08-26 08:03:30

大數(shù)據(jù)Zookeeper選舉
點(diǎn)贊
收藏

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