挑戰Kafka!Redis5.0重量級特性Stream嘗鮮

來源:互聯網
上載者:User

導讀:Redis5.0最新重點推出了Stream的支援,給眾多架構師在訊息佇列方面帶來了新的選擇,特別是Redis粉絲們絕對是一個福音。那麼Redis的Stream有哪些特別的功能?跟kafka有哪些異同?怎麼更好的使用它呢?本文作者老錢對此調研頗多,小編讀後覺得受益很大,大家也不妨詳細瞭解下。

作者簡介:錢文品(老錢),互連網分布式高並發技術十年老兵,目前任掌閱科技資深後端工程師。熟練使用 Java、Python、Golang 等多種電腦語言,開發過遊戲,製作過網站,寫過訊息推送系統和MySQL 中介軟體,實現過開源的 ORM 架構、Web 架構、RPC 架構等

Redis5.0最近被作者突然放出來了,增加了很多新的特色功能。而Redis5.0最大的新特性就是多出了一個資料結構Stream,它是一個新的強大的支援多播的可持久化的訊息佇列,作者坦言Redis Stream狠狠地借鑒了Kafka的設計。

Redis Stream的結構如所示,它有一個訊息鏈表,將所有加入的訊息都串起來,每個訊息都有一個唯一的ID和對應的內容。訊息是持久化的,Redis重啟後,內容還在。

每個Stream都有唯一的名稱,它就是Redis的key,在我們首次使用xadd指令追加訊息時自動建立。

每個Stream都可以掛多個消費組,每個消費組會有個遊標last_delivered_id在Stream數組之上往前移動,表示當前消費組已經消費到哪條訊息了。每個消費組都有一個Stream內唯一的名稱,消費組不會自動建立,它需要單獨的指令xgroup create進行建立,需要指定從Stream的某個訊息ID開始消費,這個ID用來初始化last_delivered_id變數。

每個消費組(Consumer Group)的狀態都是獨立的,相互不受影響。也就是說同一份Stream內部的訊息會被每個消費組都消費到。

同一個消費組(Consumer Group)可以掛接多個消費者(Consumer),這些消費者之間是競爭關係,任意一個消費者讀取了訊息都會使遊標last_delivered_id往前移動。每個消費者者有一個組內唯一名稱。

消費者(Consumer)內部會有個狀態變數pending_ids,它記錄了當前已經被用戶端讀取的訊息,但是還沒有ack。如果用戶端沒有ack,這個變數裡面的訊息ID會越來越多,一旦某個訊息被ack,它就開始減少。這個pending_ids變數在Redis官方被稱之為PEL,也就是Pending Entries List,這是一個很核心的資料結構,它用來確保用戶端至少消費了訊息一次,而不會在網路傳輸的中途丟失了沒處理。

訊息ID

訊息ID的形式是timestampInMillis-sequence,例如1527846880572-5,它表示當前的訊息在毫米時間戳記1527846880572時產生,並且是該毫秒內產生的第5條訊息。訊息ID可以由伺服器自動產生,也可以由用戶端自己指定,但是形式必須是整數-整數,而且必須是後面加入的訊息的ID要大於前面的訊息ID。

訊息內容

訊息內容就是索引值對,形如hash結構的索引值對,這沒什麼特別之處。

增刪改查

xadd 追加訊息

xdel 刪除訊息,這裡的刪除僅僅是設定了標誌位,不影響訊息總長度

xrange 擷取訊息列表,會自動過濾已經刪除的訊息

xlen 訊息長度

del 刪除Stream

# *號表示伺服器自動產生ID,後面順序跟著一堆key/value

127.0.0.1:6379>xadd codehole * name laoqian age30#  名字叫laoqian,年齡30歲

1527849609889-0# 產生的訊息ID

127.0.0.1:6379>xadd codehole * name xiaoyu age29

1527849629172-0

127.0.0.1:6379>xadd codehole * name xiaoqian age1

1527849637634-0

127.0.0.1:6379>xlen codehole

(integer)3

127.0.0.1:6379>xrange codehole - +# -表示最小值, +表示最大值

127.0.0.1:6379>xrange codehole - +

1)1)1527849609889-0

2)1)"name"

2)"laoqian"

3)"age"

4)"30"

2)1)1527849629172-0

2)1)"name"

2)"xiaoyu"

3)"age"

4)"29"

3)1)1527849637634-0

2)1)"name"

2)"xiaoqian"

3)"age"

4)"1"

127.0.0.1:6379>xrange codehole1527849629172-0+# 指定最小訊息ID的列表

1)1)1527849629172-0

2)1)"name"

2)"xiaoyu"

3)"age"

4)"29"

2)1)1527849637634-0

2)1)"name"

2)"xiaoqian"

3)"age"

4)"1"

127.0.0.1:6379>xrange codehole -1527849629172-0# 指定最大訊息ID的列表

1)1)1527849609889-0

2)1)"name"

2)"laoqian"

3)"age"

4)"30"

2)1)1527849629172-0

2)1)"name"

2)"xiaoyu"

3)"age"

4)"29"

127.0.0.1:6379>xdel codehole1527849609889-0

(integer)1

127.0.0.1:6379>xlen codehole# 長度不受影響

(integer)3

127.0.0.1:6379>xrange codehole - +# 被刪除的訊息沒了

1)1)1527849629172-0

2)1)"name"

2)"xiaoyu"

3)"age"

4)"29"

2)1)1527849637634-0

2)1)"name"

2)"xiaoqian"

3)"age"

4)"1"

127.0.0.1:6379>del codehole# 刪除整個Stream

(integer)1

獨立消費

我們可以在不定義消費組的情況下進行Stream訊息的獨立消費,當Stream沒有新訊息時,甚至可以阻塞等待。Redis設計了一個單獨的消費指令xread,可以將Stream當成普通的訊息佇列(list)來使用。使用xread時,我們可以完全忽略消費組(Consumer Group)的存在,就好比Stream就是一個普通的列表(list)。

# 從Stream頭部讀取兩條訊息

127.0.0.1:6379>xread count2streams codehole0-0

1)1)"codehole"

2)1)1)1527851486781-0

2)1)"name"

2)"laoqian"

3)"age"

4)"30"

2)1)1527851493405-0

2)1)"name"

2)"yurui"

3)"age"

4)"29"

# 從Stream尾部讀取一條訊息,毫無疑問,這裡不會返回任何訊息

127.0.0.1:6379>xread count1streams codehole $

(nil)

# 從尾部阻塞等待新訊息到來,下面的指令會堵住,直到新訊息到來

127.0.0.1:6379>xread block0count1streams codehole $

# 我們從新開啟一個視窗,在這個視窗往Stream裡塞訊息

127.0.0.1:6379>xadd codehole * name youming age60

1527852774092-0

# 再切換到前面的視窗,我們可以看到阻塞解除了,返回了新的訊息內容

# 而且還顯示了一個等待時間,這裡我們等待了93s

127.0.0.1:6379>xread block0count1streams codehole $

1)1)"codehole"

2)1)1)1527852774092-0

2)1)"name"

2)"youming"

3)"age"

4)"60"

(93.11s)

用戶端如果想要使用xread進行順序消費,一定要記住當前消費到哪裡了,也就是返回的訊息ID。下次繼續調用xread時,將上次返回的最後一個訊息ID作為參數傳遞進去,就可以繼續消費後續的訊息。

block 0表示永遠阻塞,直到訊息到來,block 1000表示阻塞1s,如果1s內沒有任何訊息到來,就返回nil

127.0.0.1:6379>xread block1000count1streams codehole $

(nil)

(1.07s)


建立消費組

Stream通過xgroup create指令建立消費組(Consumer Group),需要傳遞起始訊息ID參數用來初始化last_delivered_id變數。

127.0.0.1:6379>xgroup create codehole cg10-0#  表示從頭開始消費

OK

# $表示從尾部開始消費,只接受新訊息,當前Stream訊息會全部忽略

127.0.0.1:6379>xgroup create codehole cg2 $

OK

127.0.0.1:6379>xinfo codehole# 擷取Stream資訊

1) length

2) (integer)3# 共3個訊息

3) radix-tree-keys

4) (integer)1

5) radix-tree-nodes

6) (integer)2

7) groups

8) (integer)2# 兩個消費組

9) first-entry# 第一個訊息

10)1)1527851486781-0

2)1)"name"

2)"laoqian"

3)"age"

4)"30"

11) last-entry# 最後一個訊息

12)1)1527851498956-0

2)1)"name"

2)"xiaoqian"

3)"age"

4)"1"

127.0.0.1:6379>xinfo groups codehole# 擷取Stream的消費組資訊

1)1) name

2)"cg1"

3) consumers

4) (integer)0# 該消費組還沒有消費者

5) pending

6) (integer)0# 該消費組沒有正在處理的訊息

2)1) name

2)"cg2"

3) consumers# 該消費組還沒有消費者

4) (integer)0

5) pending

6) (integer)0# 該消費組沒有正在處理的訊息

消費

Stream提供了xreadgroup指令可以進行消費組的組內消費,需要提供消費組名稱、消費者名稱和起始訊息ID。它同xread一樣,也可以阻塞等待新訊息。讀到新訊息後,對應的訊息ID就會進入消費者的PEL(正在處理的訊息)結構裡,用戶端處理完畢後使用xack指令通知伺服器,本條訊息已經處理完畢,該訊息ID就會從PEL中移除。

# >號表示從當前消費組的last_delivered_id後面開始讀

# 每當消費者讀取一條訊息,last_delivered_id變數就會前進

127.0.0.1:6379>xreadgroup GROUP cg1 c1 count1streams codehole >

1)1)"codehole"

2)1)1)1527851486781-0

2)1)"name"

2)"laoqian"

3)"age"

4)"30"

127.0.0.1:6379>xreadgroup GROUP cg1 c1 count1streams codehole >

1)1)"codehole"

2)1)1)1527851493405-0

2)1)"name"

2)"yurui"

3)"age"

4)"29"

127.0.0.1:6379>xreadgroup GROUP cg1 c1 count2streams codehole >

1)1)"codehole"

2)1)1)1527851498956-0

2)1)"name"

2)"xiaoqian"

3)"age"

4)"1"

2)1)1527852774092-0

2)1)"name"

2)"youming"

3)"age"

4)"60"

# 再繼續讀取,就沒有新訊息了

127.0.0.1:6379>xreadgroup GROUP cg1 c1 count1streams codehole >

(nil)

# 那就阻塞等待吧

127.0.0.1:6379>xreadgroup GROUP cg1 c1 block0count1streams codehole >

# 開啟另一個視窗,往裡塞訊息

127.0.0.1:6379>xadd codehole * name lanying age61

1527854062442-0

# 回到前一個視窗,發現阻塞解除,收到新訊息了

127.0.0.1:6379>xreadgroup GROUP cg1 c1 block0count1streams codehole >

1)1)"codehole"

2)1)1)1527854062442-0

2)1)"name"

2)"lanying"

3)"age"

4)"61"

(36.54s)

127.0.0.1:6379>xinfo groups codehole# 觀察消費組資訊

1)1) name

2)"cg1"

3) consumers

4) (integer)1# 一個消費者

5) pending

6) (integer)5# 共5條正在處理的資訊還有沒有ack

2)1) name

2)"cg2"

3) consumers

4) (integer)0# 消費組cg2沒有任何變化,因為前面我們一直在操縱cg1

5) pending

6) (integer)0

# 如果同一個消費組有多個消費者,我們可以通過xinfo consumers指令觀察每個消費者的狀態

127.0.0.1:6379>xinfo consumers codehole cg1# 目前還有1個消費者

1)1) name

2)"c1"

3) pending

4) (integer)5# 共5條待處理訊息

5) idle

6) (integer)418715# 空閑了多長時間ms沒有讀取訊息了

# 接下來我們ack一條訊息

127.0.0.1:6379>xack codehole cg11527851486781-0

(integer)1

127.0.0.1:6379>xinfo consumers codehole cg1

1)1) name

2)"c1"

3) pending

4) (integer)4# 變成了5條

5) idle

6) (integer)668504

# 下面ack所有訊息

127.0.0.1:6379>xack codehole cg11527851493405-01527851498956-01527852774092-01527854062442-0

(integer)4

127.0.0.1:6379>xinfo consumers codehole cg1

1)1) name

2)"c1"

3) pending

4) (integer)0# pel空了

5) idle

6) (integer)745505

Stream訊息太多怎麼辦

讀者很容易想到,要是訊息積累太多,Stream的鏈表豈不是很長,內容會不會爆掉就是個問題了。xdel指令又不會刪除訊息,它只是給訊息做了個標誌位。

Redis自然考慮到了這一點,所以它提供了一個定長Stream功能。在xadd的指令提供一個定長長度maxlen,就可以將老的訊息幹掉,確保最多不超過指定長度。

127.0.0.1:6379>xlencodehole

(integer) 5

127.0.0.1:6379>xaddcodeholemaxlen3 *namexiaoruiage1

1527855160273-0

127.0.0.1:6379>xlencodehole

(integer) 3

我們看到Stream的長度被砍掉了。

訊息如果忘記ACK會怎樣

Stream在每個消費者結構中儲存了正在處理中的訊息ID列表PEL,如果消費者收到了訊息處理完了但是沒有回複ack,就會導致PEL列表不斷增長,如果有很多消費組的話,那麼這個PEL佔用的記憶體就會放大。

PEL如何避免訊息丟失

在用戶端消費者讀取Stream訊息時,Redis伺服器將訊息回複給用戶端的過程中,用戶端突然斷開了串連,訊息就丟失了。但是PEL裡已經儲存了發出去的訊息ID。待用戶端重新連上之後,可以再次收到PEL中的訊息ID列表。不過此時xreadgroup的起始訊息ID不能為參數>,而必須是任意有效訊息ID,一般將參數設為0-0,表示讀取所有的PEL訊息以及自last_delivered_id之後的新訊息。

結論

Stream的消費模型借鑒了kafka的消費分組的概念,它彌補了Redis Pub/Sub不能持久化訊息的缺陷。但是它又不同於kafka,kafka的訊息可以分partition,而Stream不行。如果非要分parition的話,得在用戶端做,提供不同的Stream名稱,對訊息進行hash模數來選擇往哪個Stream裡塞。如果讀者稍微研究過Redis作者的另一個開源項目Disque的話,這極可能是作者意識到Disque項目的活躍程度不夠,所以將Disque的內容移植到了Redis裡面。這隻是本人的猜測,未必是作者的初衷。如果讀者有什麼不同的想法,可以在評論區一起參與討論。

相關閱讀:

用最少的機器支撐萬億級訪問,微博6年Redis最佳化曆程

首發丨360開源的類Redis儲存系統:Pika

Redis實戰:如何構建類微博的億級社交平台

Codis作者黃東旭細說分布式Redis架構設計和踩過的那些坑

Redis架構之防雪崩設計:網站不宕機背後的兵法

同程旅遊緩衝系統設計:如何打造Redis時代的完美體系(含PPT)

出自:https://mp.weixin.qq.com/s/UUhP_I2wCqUeZV2SaUJm5A

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.