日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Redis消息隊(duì)列發(fā)展歷程

作者 | 丕天

我們提供的服務(wù)有:網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、合浦ssl等。為千余家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的合浦網(wǎng)站制作公司

Redis是目前最受歡迎的kv類數(shù)據(jù)庫,當(dāng)然它的功能越來越多,早已不限定在kv場景,消息隊(duì)列就是Redis中一個重要的功能。

Redis從2010年發(fā)布1.0版本就具備一個消息隊(duì)列的雛形,隨著10多年的迭代,其消息隊(duì)列的功能也越來越完善,作為一個全內(nèi)存的消息隊(duì)列,適合應(yīng)用與要求高吞吐、低延時的場景。

我們來盤一下Redis消息隊(duì)列功能的發(fā)展歷程,歷史版本有哪些不足,后續(xù)版本是如何來解決這些問題的。

一、Redis 1.0 list

從廣義上來講消息隊(duì)列就是一個隊(duì)列的數(shù)據(jù)結(jié)構(gòu),生產(chǎn)者從隊(duì)列一端放入消息,消費(fèi)者從另一端讀取消息,消息保證先入先出的順序,一個本地的list數(shù)據(jù)結(jié)構(gòu)就是一個進(jìn)程維度的消息隊(duì)列,它可以讓模塊A寫入消息,模塊B消費(fèi)消息,做到模塊A/B的解耦與異步化。但想要做到應(yīng)用級別的解耦和異步還需要一個消息隊(duì)列的服務(wù)。

1.list的特性

Redis 1.0發(fā)布時就具備了list數(shù)據(jù)結(jié)構(gòu),應(yīng)用A可以通過lpush寫入消息,應(yīng)用B通過rpop從隊(duì)列中讀取消息,每個消息只會被讀取一次,而且是按照lpush寫入的順序讀到。同時Redis的接口是并發(fā)安全的,可以同時有多個生產(chǎn)者向一個list中生產(chǎn)消息,多個消費(fèi)者從list中讀取消息。

這里還有個問題,消費(fèi)者要如何知道list中有消息了,需要不斷輪詢?nèi)ゲ樵儐?。輪詢無法保證消息被及時的處理,會增加延時,而且當(dāng)list為空時,大部分輪詢的請求都是無效請求,這種方式大量浪費(fèi)了系統(tǒng)資源。好在Redis有brpop接口,該接口有一個參數(shù)是超時時間,如果list為空,那么Redis服務(wù)端不會立刻返回結(jié)果,它會等待list中有新數(shù)據(jù)后在返回或是等待最多一個超時時間后返回空。通過brpop接口實(shí)現(xiàn)了長輪詢,該效果等同于服務(wù)端推送,消費(fèi)者能立刻感知到新的消息,而且通過設(shè)置合理的超時時間,使系統(tǒng)資源的消耗降到很低。

#基于list完成消息的生產(chǎn)和消費(fèi)

#生產(chǎn)者生產(chǎn)消息msg1
lpush listA msg1
(integer) 1

#消費(fèi)者讀取到消息msg1
rpop listA
"msg1"

#消費(fèi)者阻塞式讀取listA,如果有數(shù)據(jù)立刻返回,否則最多等待10秒
brpop listA 10
1) "listA"
2) "msg1"

使用rpop或brpop這樣接口消費(fèi)消息會先從隊(duì)列中刪除消息,然后再由應(yīng)用消費(fèi),如果應(yīng)用應(yīng)用在處理消息前異常宕機(jī)了,消息就丟失了。但如果使用lindex這樣的只讀命令先讀取消息處理完畢后在刪除,又需要額外的機(jī)制來保證一條消息不會被其他消費(fèi)者重復(fù)讀到。好在list有rpoplpush或brpoplpush這樣的接口,可以原子性的從一個list中移除一個消息并加入另一個list。

應(yīng)用程序可以通過2個list組和來完成消息的消費(fèi)和確認(rèn)功能,使用rpoplpush從list A中消費(fèi)消息并移入list B,等消息處理完畢后在從list B中刪除消息,如果在處理消息過程中應(yīng)用異常宕機(jī),恢復(fù)后應(yīng)用可以重新從list B中讀取未處理的消息并處理。這種方式為消息的消費(fèi)增加了ack機(jī)制。

#基于2個list完成消息消費(fèi)和確認(rèn)

#從listA中讀取消息并寫入listB
rpoplpush listA listB
"msg1"

#業(yè)務(wù)邏輯處理msg1完畢后,從listB中刪除msg1,完成消息的確認(rèn)
lrem listB 1 msg1
(integer) 1

2.list的不足之處

通過Redis 1.0就引入的list結(jié)構(gòu)我們就能實(shí)現(xiàn)一個分布式的消息隊(duì)列,滿足一些簡單的業(yè)務(wù)需求。但list結(jié)構(gòu)作為消息隊(duì)列服務(wù)有一個很致命的問題,它沒有廣播功能,一個消息只能被消費(fèi)一次。而在大型系統(tǒng)中,通常一個消息會被下游多個應(yīng)用同時訂閱和消費(fèi),例如當(dāng)用戶完成一個訂單的支付操作時,需要通知商家發(fā)貨,要更新物流狀態(tài),可能還會提高用戶的積分和等級,這些都是不同的下游子系統(tǒng),他們?nèi)繒嗛喼Ц锻瓿傻牟僮?,而list一個消息只能被消費(fèi)一次在這樣復(fù)雜的大型系統(tǒng)面前就捉襟見肘了。

可能你會說那弄多個list,生產(chǎn)者向每個list中都投遞消息,每個消費(fèi)者處理自己的list不就行了嗎。這樣第一是性能不會太好,因?yàn)橥粋€消息需要被重復(fù)的投遞,第二是這樣的設(shè)計違反了生產(chǎn)者和消費(fèi)者解耦的原則,這個設(shè)計下生產(chǎn)者需要知道下游有哪些消費(fèi)者,如果業(yè)務(wù)發(fā)生變化,需要額外增加一個消費(fèi)者,生產(chǎn)者的代碼也需要修改。

3.總結(jié)

優(yōu)勢

模型簡單,和使用本地list基本相同,適配容易

通過brpop做到消息處理的實(shí)時性

通過rpoplpush來聯(lián)動2個list,可以做到消息先消費(fèi)后確認(rèn),避免消費(fèi)者應(yīng)用異常情況下消息丟失

不足

消息只能被消費(fèi)一次,缺乏廣播機(jī)制

二、Redis 2.0 pubsub

list作為消息隊(duì)列應(yīng)用場景受到限制很重要的原因在于沒有廣播,所以Redis 2.0中引入了一個新的數(shù)據(jù)結(jié)構(gòu)pubsub。pubsub雖然不能算作是list的替代品,但它確實(shí)能解決一些list不能解決的問題。

1.pubsub特性

pubsub引入一個概念叫channel,生產(chǎn)者通過publish接口投遞消息時會指定channel,消費(fèi)者通過subscribe接口訂閱它關(guān)心的channel,調(diào)用subscribe后這條連接會進(jìn)入一個特殊的狀態(tài),通常不能在發(fā)送其他請求,當(dāng)有消息投遞到這個channel時Redis服務(wù)端會立刻通過該連接將消息推送到消費(fèi)者。這里一個channel可以被多個應(yīng)用訂閱,消息會同時投遞到每個訂閱者,做到了消息的廣播。

另一方面,消費(fèi)者可以會訂閱一批channel,例如一個用戶訂閱了浙江的新聞的推送,但浙江新聞還會進(jìn)行細(xì)分,例如“浙江杭州xx”、“浙江溫州xx”,這里訂閱者不需要獲取浙江的所有子類在挨個訂閱,只需要調(diào)用psubscribe“浙江*”就能訂閱所有以浙江開頭的新聞推送了,這里psubscribe傳入一個通配符表達(dá)的channel,Redis服務(wù)端按照規(guī)則推送所有匹配channel的消息給對應(yīng)的客戶端。

#基于pubsub完成channel的匹配和消息的廣播

#消費(fèi)者1訂閱channel1
subscribe channel1
1) "subscribe"
2) "channel1"
3) (integer) 1
#收到消息推送
1) "message"
2) "channel1"
3) "msg1"

#消費(fèi)者2訂閱channel*
psubscribe channel*
1) "psubscribe"
2) "channel*"
3) (integer) 1
#收到消息推送
1) "pmessage"
2) "channel*"
3) "channel1"
4) "msg1"
1) "pmessage"
2) "channel*"
3) "channel2"
4) "msg2"

#生產(chǎn)者發(fā)布消息msg1和msg2
publish channel1 msg1
(integer) 2
publish channel2 msg2
(integer) 1

在Redfis 2.8時加入了keyspace notifications功能,此時pubsub除了通知用戶自定義消息,也可以通知系統(tǒng)內(nèi)部消息。keyspace notifications引入了2個特殊的channel分別是__keyevent@__:和__keyspace@__:,通過訂閱__keyevent客戶端可以收到某個具體命令調(diào)用的回調(diào)通知,通過訂閱__keyspace客戶端可以收到目標(biāo)key的增刪改操作以及過期事件。使用這個功能還需要開啟配置notify-keyspace-events。

#通過keyspace notifications功能獲取系統(tǒng)事件

#寫入請求
set testkey v EX 1

#訂閱key級別的事件
psubscribe __keyspace@0__:testkey
1) "psubscribe"
2) "__keyspace@0__:testkey"
3) (integer) 1
#收到通知
1) "pmessage"
2) "__keyspace@0__:testkey"
3) "__keyspace@0__:testkey"
4) "set"
1) "pmessage"
2) "__keyspace@0__:testkey"
3) "__keyspace@0__:testkey"
4) "expire"
1) "pmessage"
2) "__keyspace@0__:testkey"
3) "__keyspace@0__:testkey"
4) "expired"

#訂閱所有的命令事件
psubscribe __keyevent@0__:*
1) "psubscribe"
2) "__keyevent@0__:*"
3) (integer) 1
#收到通知
1) "pmessage"
2) "__keyevent@0__:*"
3) "__keyevent@0__:set"
4) "testkey"
1) "pmessage"
2) "__keyevent@0__:*"
3) "__keyevent@0__:expire"
4) "testkey"
1) "pmessage"
2) "__keyevent@0__:*"
3) "__keyevent@0__:expired"
4) "testkey"


2.pubsub的不足之處

pubsub既能單播又能廣播,還支持channel的簡單正則匹配,功能上已經(jīng)能滿足大部分業(yè)務(wù)的需求,而且這個接口發(fā)布的時間很早,在2011年Redis 2.0發(fā)布時就已經(jīng)具備,用戶基礎(chǔ)很廣泛,所以現(xiàn)在很多業(yè)務(wù)都有用到這個功能。但你要深入了解pubsub的原理后,是肯定不敢把它作為一個一致性要求較高,數(shù)據(jù)量較大系統(tǒng)的消息服務(wù)的。

首先,pubsub的消息數(shù)據(jù)是瞬時的,它在Redis服務(wù)端不做保存,publish發(fā)送到Redis的消息會立刻推送到所有當(dāng)時subscribe連接的客戶端,如果當(dāng)時客戶端因?yàn)榫W(wǎng)絡(luò)問題斷連,那么就會錯過這條消息,當(dāng)客戶端重連后,它沒法重新獲取之前那條消息,甚至無法判斷是否有消息丟失。

其次,pubsub中消費(fèi)者獲取消息是一個推送模型,這意味著Redis會按消息生產(chǎn)的速度給所有的消費(fèi)者推送消息,不管消費(fèi)者處理能力如何,如果消費(fèi)者應(yīng)用處理能力不足,消息就會在Redis的client buf中堆積,當(dāng)堆積數(shù)據(jù)超過一個閾值后會斷開這條連接,這意味著這些消息全部丟失了,在也找不回來了。如果同時有多個消費(fèi)者的client buf堆積數(shù)據(jù)但又還沒達(dá)到斷開連接的閾值,那么Redis服務(wù)端的內(nèi)存會膨脹,進(jìn)程可能因?yàn)閛om而被殺掉,這導(dǎo)致了整個服務(wù)中斷。

3.總結(jié)

優(yōu)勢

  • 消息具備廣播能力
  • psubscribe能按字符串通配符匹配,給予了業(yè)務(wù)邏輯的靈活性
  • 能訂閱特定key或特定命令的系統(tǒng)消息

不足

  • Redis異常、客戶端斷連都會導(dǎo)致消息丟失
  • 消息缺乏堆積能力,不能削峰填谷。推送的方式缺乏背壓機(jī)制,沒有考慮消費(fèi)者處理能力,推送的消息超過消費(fèi)者處理能力后可能導(dǎo)致消息丟失或服務(wù)異常

三、Redis 5.0 stream

消息丟失、消息服務(wù)不穩(wěn)定的問題嚴(yán)重限制了pubsub的應(yīng)用場景,所以Redis需要重新設(shè)計一套機(jī)制,來解決這些問題,這就有了后來的stream結(jié)構(gòu)。

1.stream特性

一個穩(wěn)定的消息服務(wù)需要具備幾個要點(diǎn),要保證消息不會丟失,至少被消費(fèi)一次,要具備削峰填谷的能力,來匹配生產(chǎn)者和消費(fèi)者吞吐的差異。在2018年Redis 5.0加入了stream結(jié)構(gòu),這次考慮了list、pubsub在應(yīng)用場景下的缺陷,對標(biāo)kafka的模型重新設(shè)計全內(nèi)存消息隊(duì)列結(jié)構(gòu),從這時開始Redis消息隊(duì)列功能算是能和主流消息隊(duì)列產(chǎn)品pk一把了。

stream的改進(jìn)分為多個方面

成本:

  • 存儲message數(shù)據(jù)使用了listpack結(jié)構(gòu),這是一個緊湊型的數(shù)據(jù)結(jié)構(gòu),不同于list的雙向鏈表每個節(jié)點(diǎn)都要額外占用2個指針的存儲空間,這使得小msg情況下stream的空間利用率更高。

功能:

  • stream引入了消費(fèi)者組的概念,一個消費(fèi)者組內(nèi)可以有多個消費(fèi)者,同一個組內(nèi)的消費(fèi)者共享一個消息位點(diǎn)(last_delivered_id),這使得消費(fèi)者能夠水平的擴(kuò)容,可以在一個組內(nèi)加入多個消費(fèi)者來線性的提升吞吐,對于一個消費(fèi)者組,每條msg只會被其中一個消費(fèi)者獲取和處理,這是pubsub的廣播模型不具備的。
  • 不同消費(fèi)者組之前是相互隔離的,他們各自維護(hù)自己的位點(diǎn),這使得一條msg能被多個不同的消費(fèi)者組重復(fù)消費(fèi),做到了消息廣播的能力。
  • stream中消費(fèi)者采用拉取的方式,并能設(shè)置timeout在沒有消息時阻塞,通過這種長輪詢機(jī)制保證了消息的實(shí)時性,而且消費(fèi)速率是和消費(fèi)者自身吞吐相匹配。

消息不丟失:

  • stream的數(shù)據(jù)會存儲在aof和rdb文件中,這使Redis重啟后能夠恢復(fù)stream的數(shù)據(jù)。而pubsub的數(shù)據(jù)是瞬時的,Redis重啟意味著消息全部丟失。
  • stream中每個消費(fèi)者組會存儲一個last_delivered_id來標(biāo)識已經(jīng)讀取到的位點(diǎn),客戶端連接斷開后重連還是能從該位點(diǎn)繼續(xù)讀取,消息不會丟失。
  • stream引入了ack機(jī)制保證消息至少被處理一次。考慮一種場景,如果消費(fèi)者應(yīng)用已經(jīng)讀取了消息,但還沒來得及處理應(yīng)用就宕機(jī)了,對于這種已經(jīng)讀取但沒有ack的消息,stream會標(biāo)示這條消息的狀態(tài)為pending,等客戶端重連后通過xpending命令可以重新讀取到pengind狀態(tài)的消息,繼續(xù)處理。如果這個應(yīng)用永久宕機(jī)了,那么該消費(fèi)者組內(nèi)的其他消費(fèi)者應(yīng)用也能讀取到這條消息,并通過xclaim命令將它歸屬到自己下面繼續(xù)處理。
#基于stream完成消息的生產(chǎn)和消費(fèi),并確保異常狀態(tài)下消息至少被消費(fèi)一次

#創(chuàng)建mystream,并且創(chuàng)建一個consumergroup為mygroup
XGROUP CREATE mystream mygroup $ MKSTREAM
OK

#寫入一條消息,由redis自動生成消息id,消息的內(nèi)容是一個kv數(shù)組,這里包含field1 value1 field2 value2
XADD mystream * field1 value1 field2 value2
"1645517760385-0"

#消費(fèi)者組mygroup中的消費(fèi)者consumer1從mystream讀取一條消息,>表示讀取一條該消費(fèi)者組從未讀取過的消息
XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >
1) 1) "mystream"
2) 1) 1) "1645517760385-0"
2) 1) "field1"
2) "value1"
3) "field2"
4) "value2"

#消費(fèi)完成后ack確認(rèn)消息
xack mystream mygroup 1645517760385-0
(integer) 1

#如果消費(fèi)者應(yīng)用在ack前異常宕機(jī),恢復(fù)后重新獲取未處理的消息id。
XPENDING mystream mygroup - + 10
1) 1) "1645517760385-0"
2) "consumer1"
3) (integer) 305356
4) (integer) 1

#如果consumer1永遠(yuǎn)宕機(jī),其他消費(fèi)者可以把pending狀態(tài)的消息移動到自己名下后繼續(xù)消費(fèi)
#將消息id 1645517760385-0移動到consumer2下
XCLAIM mystream mygroup consumer2 0 1645517760385-0
1) 1) "1645517760385-0"
2) 1) "field1"
2) "value1"
3) "field2"
4) "value2"

Redis stream保證了消息至少被處理一次,但如果想做到每條消息僅被處理一次還需要應(yīng)用邏輯的介入。

消息被重復(fù)處理要么是生產(chǎn)者重復(fù)投遞,要么是消費(fèi)者重復(fù)消費(fèi)。

  • 對于生產(chǎn)者重復(fù)投遞問題,Redis stream為每個消息都設(shè)置了一個唯一遞增的id,通過參數(shù)可以讓Redis自動生成id或者應(yīng)用自己指定id,應(yīng)用可以根據(jù)業(yè)務(wù)邏輯為每個msg生成id,當(dāng)xadd超時后應(yīng)用并不能確定消息是否投遞成功,可以通過xread查詢該id的消息是否存在,存在就說明已經(jīng)投遞成功,不存在則重新投遞,而且stream限制了id必須遞增,這意味了已經(jīng)存在的消息重復(fù)投遞會被拒絕。這套機(jī)制保證了每個消息可以僅被投遞一次。
  • 對于消費(fèi)者重復(fù)消費(fèi)的問題,考慮一個場景,消費(fèi)者讀取消息后業(yè)務(wù)處理完畢,但還沒來得及ack就發(fā)生了異常,應(yīng)用恢復(fù)后對于這條沒有ack的消息進(jìn)行了重復(fù)消費(fèi)。這個問題因?yàn)閍ck和消費(fèi)消息的業(yè)務(wù)邏輯發(fā)生在2個系統(tǒng),沒法做到事務(wù)性,需要業(yè)務(wù)來改造,保證消息處理的冪等性。

2.stream的不足

stream的模型做到了消息的高效分發(fā),而且保證了消息至少被處理一次,通過應(yīng)用邏輯的改造能做到消息僅被處理一次,它的能力對標(biāo)kafka,但吞吐高于kafka,在高吞吐場景下成本比kafka低,那它又有哪些不足了。

首先消息隊(duì)列很重要的一個功能就是削峰填谷,來匹配生產(chǎn)者和消費(fèi)者吞吐的差異,生產(chǎn)者和消費(fèi)者吞吐差異越大,持續(xù)時間越長,就意味著steam中需要堆積更多的消息,而Redis作為一個全內(nèi)存的產(chǎn)品,數(shù)據(jù)堆積的成本比磁盤高。

其次stream通過ack機(jī)制保證了消息至少被消費(fèi)一次,但這有個前提就是存儲在Redis中的消息本身不會丟失。Redis數(shù)據(jù)的持久化依賴aof和rdb文件,aof落盤方式有幾種,通過配置appendfsync決定,通常我們不會配置為always來讓每條命令執(zhí)行完后都做一次fsync,線上配置一般為everysec,每秒做一次fsync,而rdb是全量備份時生成,這意味了宕機(jī)恢復(fù)可能會丟掉最近一秒的數(shù)據(jù)。另一方面線上生產(chǎn)環(huán)境的Redis都是高可用架構(gòu),當(dāng)主節(jié)點(diǎn)宕機(jī)后通常不會走恢復(fù)邏輯,而是直接切換到備節(jié)點(diǎn)繼續(xù)提供服務(wù),而Redis的同步方式是異步同步,這意味著主節(jié)點(diǎn)上新寫入的數(shù)據(jù)可能還沒同步到備節(jié)點(diǎn),在切換后這部分?jǐn)?shù)據(jù)就丟失了。所以在故障恢復(fù)中Redis中的數(shù)據(jù)可能會丟失一部分,在這樣的背景下無論stream的接口設(shè)計的多么完善,都不能保證消息至少被消費(fèi)一次。

3.總結(jié)

優(yōu)勢

  • 在成本、功能上做了很多改進(jìn),支持了緊湊的存儲小消息、具備廣播能力、消費(fèi)者能水平擴(kuò)容、具備背壓機(jī)制
  • 通過ack機(jī)制保證了Redis服務(wù)端正常情況下消息至少被處理一次的能力

不足

  • 內(nèi)存型消息隊(duì)列,數(shù)據(jù)堆積成本高
  • Redis本身rpo>0,故障恢復(fù)可能會丟數(shù)據(jù),所以stream在Redis發(fā)生故障恢復(fù)后也不能保證消息至少被消費(fèi)一次。

四、Tair持久內(nèi)存版 stream

Redis stream的不足也是內(nèi)存型數(shù)據(jù)庫特性帶來的,它擁有高吞吐、低延時,但大容量下成本會比較高,而應(yīng)用的場景也不完全是絕對的大容量低吞吐或小容量高吞吐,有時應(yīng)用的場景會介于二者之間,需要平衡容量和吞吐的關(guān)系,所以需要一個產(chǎn)品它的存儲成本低于Redis stream,但它的性能又高于磁盤型消息隊(duì)列。

另一方面Redis stream在Redis故障場景下不能保證消息的不丟失,這導(dǎo)致業(yè)務(wù)需要自己實(shí)現(xiàn)一些復(fù)雜的機(jī)制來回補(bǔ)這段數(shù)據(jù),同時也限制了它應(yīng)用在一些對一致性要求較高的場景。為了讓業(yè)務(wù)邏輯更簡單,stream應(yīng)用范圍更廣,需要保證故障場景下的消息持久化。

兼顧成本、性能、持久化,這就有了Tair持久內(nèi)存版。

1.Tair持久內(nèi)存版特性

更大空間,更低成本

Tair持久內(nèi)存版引入了Intel傲騰持久內(nèi)存(下面稱作AEP),它的性能略低于內(nèi)存,但相同容量下成本低于內(nèi)存。Tair持久內(nèi)存版將主要數(shù)據(jù)存儲在AEP上,使得相同容量下,成本更低,這使同樣單價下stream能堆積更多的消息。

兼容社區(qū)版

Tair持久內(nèi)存版兼容原生Redis絕大部分的數(shù)據(jù)結(jié)構(gòu)和接口,對于stream相關(guān)接口做到了100%兼容,如果你之前使用了社區(qū)版stream,那么不需要修改任何代碼,只需要換一個連接地址就能切換到持久內(nèi)存版。并且通過工具完成社區(qū)版和持久內(nèi)存版數(shù)據(jù)的雙向遷移。

數(shù)據(jù)的實(shí)時持久化

Tair持久內(nèi)存版并不是簡單將Redis中的數(shù)據(jù)換了一個介質(zhì)存儲,因?yàn)檫@樣僅能通過AEP降低成本,但沒用到AEP斷電數(shù)據(jù)不丟失的特性,對持久化能力沒有任何提升。

開源Redis通過在磁盤上記錄AppendOnlyLog來持久化數(shù)據(jù),AppendOnlyLog記錄了所有的寫操作,相當(dāng)于redolog,在宕機(jī)恢復(fù)時通過回放這些log恢復(fù)數(shù)據(jù)。但受限于磁盤介質(zhì)的高延時和Redis內(nèi)存數(shù)據(jù)庫使用場景下對低延時的要求,并不能在每次寫操作后fsync持久化log,最新寫入的數(shù)據(jù)可能并沒有持久化到磁盤,這也是數(shù)據(jù)可能丟失的根因。

Tair持久內(nèi)存版的數(shù)據(jù)恢復(fù)沒有使用AppendOnlyLog來完成, 而是將將redis數(shù)據(jù)結(jié)構(gòu)存儲在AEP上,這樣宕機(jī)后這些數(shù)據(jù)結(jié)構(gòu)并不會丟失,并且對這些數(shù)據(jù)結(jié)構(gòu)增加了一些額外的描述信息,宕機(jī)后在recovery時能夠讀到這些額外的描述信息,讓這些redis數(shù)據(jù)結(jié)構(gòu)重新被識別和索引,將狀態(tài)恢復(fù)到宕機(jī)前的樣子。Tair通過將redis數(shù)據(jù)結(jié)構(gòu)和描述信息實(shí)時寫入AEP,保證了寫入數(shù)據(jù)的實(shí)時持久化。

HA數(shù)據(jù)不丟失

Tair持久內(nèi)存版保證了數(shù)據(jù)的持久化,但生產(chǎn)環(huán)境中都是高可用架構(gòu),多數(shù)情況下當(dāng)主節(jié)點(diǎn)異常宕機(jī)后并不會等主節(jié)點(diǎn)重啟恢復(fù),而是切換到備節(jié)點(diǎn)繼續(xù)提供服務(wù),然后給新的主節(jié)點(diǎn)添加一個新的備節(jié)點(diǎn)。所以在故障發(fā)生時如果有數(shù)據(jù)還沒從主節(jié)點(diǎn)同步到備節(jié)點(diǎn),這部分?jǐn)?shù)據(jù)就會丟失。

Redis采用的異步同步,當(dāng)客戶端寫入數(shù)據(jù)并返回成功時對Redis的修改可能還沒同步到備節(jié)點(diǎn),如果此時主節(jié)點(diǎn)宕機(jī)數(shù)據(jù)就會丟失。為了避免在HA過程中數(shù)據(jù)丟失,Tair持久內(nèi)存版引入了半同步機(jī)制,確保寫入請求返回成功前相關(guān)的修改已經(jīng)同步到備節(jié)點(diǎn)。

可以發(fā)現(xiàn)開啟半同步功能后寫入請求的RT會變高,多出主備同步的耗時,這部分耗時大概在幾十微秒。但通過一些異步化的技術(shù),雖然寫請求的RT會變高,但對實(shí)例的最大寫吞吐影響很小。

當(dāng)開啟半同步后生成者通過xadd投遞消息,如果返回成功,消息一定同步到備節(jié)點(diǎn),此時發(fā)生HA,消費(fèi)者也能在備節(jié)點(diǎn)上讀到這條消息。如果xadd請求超時,此時消息可能同步到備節(jié)點(diǎn)也可能沒有,生產(chǎn)者沒法確定,此時通過再次投遞消息,可以保證該消息至少被消費(fèi)一次。如果要嚴(yán)格保證消息僅被消費(fèi)一次,那么生產(chǎn)者可以通過xread接口查詢消息是否存在,對于不存在的場景重新投遞。

2.總結(jié)

優(yōu)勢

  • 引入了AEP作為存儲介質(zhì),目前Tair持久內(nèi)存版價格是社區(qū)版的70%。
  • 保證了數(shù)據(jù)的實(shí)時持久化,并且通過半同步技術(shù)保證了HA不丟數(shù)據(jù),大多數(shù)情況下做到消息不丟失(備庫故障或主備網(wǎng)絡(luò)異常時會降級為異步同步,優(yōu)先保障可用性),消息至少被消費(fèi)一次或僅被消費(fèi)一次。

五、未來

消息隊(duì)列主要是為了解決3類問題,應(yīng)用模塊的解耦、消息的異步化、削峰填谷。目前主流的消息隊(duì)列都能滿足這些需求,所以在實(shí)際選型時還會考慮一些特殊的功能是否滿足,產(chǎn)品的性能如何,具體業(yè)務(wù)場景下的成本怎么樣,開發(fā)的復(fù)雜度等。

Redis的消息隊(duì)列功能并不是最全面的,它不希望做成一個大而全的產(chǎn)品,而是做一個小而美的產(chǎn)品,服務(wù)好一部分用戶在某些場景下的需求。目前用戶選型Redis作為消息隊(duì)列服務(wù)的原因,主要有Redis在相同成本下吞吐更高、Redis的延時更低、應(yīng)用需要一個消息服務(wù)但又不想額外引入一堆依賴等。

未來Tair持久內(nèi)存版會針對這些述求,把這些優(yōu)勢繼續(xù)放大。

吞吐

通過優(yōu)化持久內(nèi)存版的持久化流程,讓吞吐接近內(nèi)存版甚至超過內(nèi)存版吞吐。

延時

通過rdma在多副本間同步數(shù)據(jù),降低半同步下寫入數(shù)據(jù)的延時。


文章名稱:Redis消息隊(duì)列發(fā)展歷程
網(wǎng)站鏈接:http://www.5511xx.com/article/cdgihoj.html