基於Dynomite的分散式延遲佇列

colincheng發表於2018-12-17

最近看了Dyno-queues分散式延遲佇列的原始碼,發現了一些不錯的技巧,而本文是對Dyno-queues架構精華的總結。
本文是根據 https://medium.com/netflix-techblog/distributed-delay-queues-based-on-dynomite-6b31eca37fbc 翻譯而來,如果有不準之處請大家多包含。

在Netflix的平臺上執行著許多的業務流程,這些流程的任務是通過非同步編排進行驅動,現在我們要實現一個分散式延遲佇列,這個延遲佇列具有如下特點:

  • 分散式
  • 不用外部的鎖機制
  • 高併發
  • 至少一次語義交付
  • 不遵循嚴格的FIFO
  • 延遲佇列(訊息在將來某個時間之前不會從佇列中取出)
  • 優先順序

一、使用Dynomite和Redis構建佇列

Dynomite是一種通用的實現,可以與許多不同的key-value儲存引擎一起使用。目前它提供了對Redis序列化協議(RESP)和Memcached寫協議的支援。我們選擇Dynomite,是因為其具有效能,多資料中心複製和高可用性的特點。此外,Dynomite提供分片和可插拔的資料儲存引擎,允許我們在資料需求增加垂直和水平擴充套件。

1、為什麼選擇Redis?

我們選擇Redis作為構建佇列的儲存引擎:

  • Redis架構通過提供構建佇列所需的資料結構很好地支援了佇列設計,同時Redis的效能也非常優秀,具備低延遲的特性
  • Dynomite在Redis之上提供了高可用性、對等複製以及一致性等特性,用於構建分散式叢集佇列。

一個佇列被儲存為Redis的有序集合(ZADD和ZRANGE等操作),Redis使用分數對有序集合中的成員進行排序,當往佇列中儲存資料時,根據優先順序和超時時間計算分數。

2、使用Redis實現資料的push和pop

對於每個佇列,維護三組Redis資料結構:

  • 包含佇列元素和分數的有序集合
  • 包含訊息內容的Hash集合,其中key為訊息ID。
  • 包含客戶端已經消費但尚未確認的訊息有序集合,Un-ack集合。

PUSH

  • 根據訊息超時(延遲佇列)和優先順序計算得分
  • 新增到佇列的有序集合
  • 將Message物件到Hash集合中,key是messageId。

POP

  • 計算當前時間為最大分數。
  • 獲取分數在0和最大分數之間的訊息。
  • 將messageID新增到unack集合中,並從佇列的有序集中刪除這個messageID。
  • 如果上一步成功,則根據messageID從Redis集合中檢索訊息。

ACK

  • 從unack集合中刪除messageID。
  • 從Message有效集合中刪除messageID。
    客戶端未進行確認的訊息,會被再度推回到佇列中(這是一個定時任務負責檢測)。

3、可用分割槽和機架意識

我們的佇列是在Dynomite的JAVA客戶端Dyno之上建立的,Dyno為持久連線提供連線池,並且可以配置為拓撲感知,此外,Dyno為應用程式提供特定的本地機架(在AWS中,機架是一個區域,例如 us-east-1a、us-east-1b等),us-east-1a的客戶端將連線到相同區域的Dynomite/Redis節點,除非該節點不可用,在這種情況下該客戶端將進行故障轉移。這個屬性被用於通過區域劃分佇列。

分片
佇列根據可用區域進行分片,將資料推送到佇列時,通過輪訓機制確定分片,這種機制可以確保所有分片的資料是平衡的,每個分片都代表Redis中的有序集合,有序集中的key是queueName和AVAILABILITY _ZONE的組合。

避免全域性鎖
image.png

  • 每個節點(上圖中的N1…Nn)與可用性區域具有關聯性,並且與該區域中的redis伺服器進行通訊。
  • Dynomite / Redis節點一次只能提供一個請求,Dynomite可以允許數千個併發連線,但是請求是由Redis中的單個執行緒處理,這確保了當發出兩個併發呼叫從佇列輪詢元素時,是由Redis伺服器順序執行,從而避免任何本地或分散式鎖。
  • 在發生故障轉移的情況下,確保沒有兩個客戶端連線從佇列中獲取相同的訊息。

處理Un-ACK的訊息
後臺程式監視UNACK集合中的訊息,這些訊息在給定時間內未被客戶端確認(每個佇列可配置)。這些訊息將移回到佇列中。

Dyno-queues分散式延遲佇列的github地址是:
https://github.com/Netflix/dyno-queues


相關文章