對于一個可靠的IM系統(tǒng)戴陡,需要保證消息的百分之百到達(dá)對端塞绿。即使是在極端情況下丟失一條消息也是不能容忍的。一個極其極其低概率的事件恤批,若是放大到分布式系統(tǒng)中异吻,那這個概率事件就成了必然事件。在開發(fā)測試中如果發(fā)現(xiàn)一次偶然的消息丟失問題而忽略不查喜庞,那上線之后就必然會發(fā)生消息丟失诀浪。所以作為技術(shù),一定不能放過任何一個極端情況下發(fā)生的問題延都。
在服務(wù)器給客戶端發(fā)送消息的過程中雷猪,有兩種方式,1.主動推送消息給客戶端晰房;2.客戶端來拉消息求摇。這兩種方式都可以達(dá)到目的,下面就來分析一下兩者的區(qū)別殊者。
A ? -> ?SERVER ?-> B
上述描述一種簡單模型与境,A給B發(fā)送一條消息,首先會到達(dá)服務(wù)器猖吴,然后服務(wù)器將消息轉(zhuǎn)發(fā)給客戶端B摔刁。這是推的方式,服務(wù)器是直接將消息推給客戶端的海蔽。那在復(fù)雜的網(wǎng)絡(luò)環(huán)境中共屈,如何保證消息能夠到達(dá)B端?
這個例子有兩處需要做保證准潭,第一是如何保證A發(fā)出去的消息成功到達(dá)服務(wù)器趁俊。第二處是服務(wù)器推給B的消息如何知道已經(jīng)成功送達(dá)。
本文主要分析第二條刑然。
在正常情況下,服務(wù)器直接把消息下發(fā)給B端就完事了暇务,這也是大家最希望看到的結(jié)果泼掠。如果僅僅這樣處理,那系統(tǒng)會常常因為這個環(huán)節(jié)丟消息垦细,而且非常嚴(yán)重择镇。我們需要考慮以下幾種情況。第一括改,對方不在線怎么辦腻豌。第二,在移動網(wǎng)絡(luò)下,信號經(jīng)常會不穩(wěn)定吝梅,比如乘坐地鐵過隧道虱疏,信號會中斷,會導(dǎo)致消息沒有成功到達(dá)對端苏携。如何保證消息可靠抵達(dá)做瞪?
1.當(dāng)知道對端不在線的情況下,將消息存在服務(wù)器右冻,等待客戶端下次登陸來拉取装蓬。
2.對于沒推成功的情況,服務(wù)器增加重推的機(jī)制纱扭,客戶端收到消息后給服務(wù)器回復(fù)確認(rèn)牍帚,服務(wù)器取消后續(xù)推送。
新增的邏輯引入新的復(fù)雜度乳蛾,需要解決暗赶。
1.要確保成功將消息存儲在服務(wù)器,如果存儲失敗屡久,算是丟失消息忆首。這樣就要對存儲失敗的情況做檢測。一種是明確知道存失敗了被环,另一種是后端服務(wù)超時糙及,不知道有沒有存成功。存儲失敗可以重試筛欢,存儲超時也可以簡單認(rèn)為是存儲失敗浸锨,再重試。只要保證多次存儲同一消息是冪等操作就可以版姑,防止存了兩條柱搜。
2.對于重推,服務(wù)器要實現(xiàn)重推邏輯剥险,把推送操作加到定時器里面聪蘸,同時緩存這條消息。超時未收到客戶端的確認(rèn)就再推一次表制。由于網(wǎng)絡(luò)原因或者客戶端卡住健爬,會導(dǎo)致推送的消息到達(dá)了客戶端,但是客戶端的確認(rèn)一直沒有到達(dá)服務(wù)器么介,導(dǎo)致服務(wù)器推送了多次消息娜遵,所以客戶端需要對消息做重復(fù)消息的過濾。其次是多次推送后壤短,客戶端一直沒有回復(fù)確認(rèn)设拟,這個可能是網(wǎng)絡(luò)原因慨仿,客戶端真沒收到,也可能客戶端收到了纳胧,客戶端的確認(rèn)還在路上镰吆,但是已經(jīng)到了服務(wù)器重推次數(shù),服務(wù)器決定要不要將消息存儲到服務(wù)器躲雅?鑒于客戶端實現(xiàn)了消息過濾機(jī)制鼎姊,此處可以簡單地存儲消息到服務(wù)器。這就走1的邏輯相赁。后續(xù)客戶端再上線時拉服務(wù)端存儲的消息相寇,并做重復(fù)過濾。能保證消息不丟失钮科。
3.既然會拉取之前存儲在服務(wù)器的消息唤衫,那拉取完成之后需要將服務(wù)器存儲的消息刪除,這一步客戶端在確認(rèn)收到消息后再發(fā)刪除請求即可绵脯。否則每次都會拉一遍佳励,耗費(fèi)流量,而且消息多了會導(dǎo)致登陸后的收消息流程越來越卡蛆挫,由于有過濾機(jī)制赃承,不會出現(xiàn)重復(fù)消息顯示。
上述是推的方式實現(xiàn)消息可靠送達(dá)的復(fù)雜度悴侵。之間還有些邏輯沒包含進(jìn)來瞧剖,比如push】擅猓客戶端沒收到消息應(yīng)該改推push抓于。那這樣一來推push的情況就有很多。公司之前的老系統(tǒng)是采用推的方式浇借,我們在這一塊踩過很多坑捉撮,服務(wù)端的實現(xiàn)邏輯也相當(dāng)復(fù)雜,各種判斷妇垢,包括存消息到服務(wù)器巾遭,重推消息給客戶端,推push給客戶端闯估,考慮多設(shè)備問題恢总,根據(jù)客戶端的確認(rèn)做重推取消等等。個人看法是:相信我睬愤,如果這樣做,后果很嚴(yán)重纹安,你會因頻繁的消息丟失問題沉浸在復(fù)雜的代碼邏輯中無法自拔尤辱,甚至砂豌,開始懷疑人生。
接下來分析下拉消息的實現(xiàn)方式光督。
A ?-> SERVER -> ?B
B <--> SERVER
如果說推的方式是一步到位阳距,那拉消息的實現(xiàn)方式分為兩步。第一结借,A將消息發(fā)送到服務(wù)器筐摘,服務(wù)器存儲這條消息,并發(fā)送一個通知給客戶端B船老,告訴他有消息來了咖熟,快來拉取。第二柳畔,客戶端來拉取這條消息馍管,收到后刪除服務(wù)器的這條消息。同樣的問題薪韩,有兩點(diǎn)需要注意确沸。第一,網(wǎng)絡(luò)原因?qū)е峦ㄖ獩]到對端俘陷,第二罗捎,對方不在線怎么辦?如何保證消息可靠抵達(dá)拉盾?
拉的方式下桨菜,可以先將消息存儲到服務(wù)器,再來給發(fā)送者和接收者推通知盾剩。如果消息存儲失敗雷激,就可以簡單回復(fù)消息發(fā)送失敗給發(fā)送者,讓發(fā)送者手動重發(fā)告私。對于后續(xù)流程屎暇,如下:
1.如果對方不在線,就不推送通知驻粟,直接結(jié)束消息發(fā)送流程根悼,等待后續(xù)對方上線拉消息。這里就不用考慮存儲消息失敗的情況了蜀撑,因為存儲步驟在之前已保證ok挤巡。
2.對方在線,如果通知推送失敗怎么處理酷麦?對于沒推成功的情況矿卑,不再重推,等待下次上線拉消息沃饶!沒錯母廷,就是這么暴力和任性轻黑。
如此可能出現(xiàn)的問題是消息亂序走趋∫破龋客戶端可以根據(jù)消息在服務(wù)端的生成時間排序,可以解決這個問題嗡贺。就是會出現(xiàn)消息突然跳躍順序业舍。
在實現(xiàn)中抖拦,客戶端拉消息應(yīng)該是按照msgid范圍拉一批消息,而且在服務(wù)端的實現(xiàn)中舷暮,msgid要保證遞增态罪,無重復(fù)〗烹梗客戶端重新聯(lián)網(wǎng)后應(yīng)先保證處理拉服務(wù)器消息的流程先走完向臀,再處理新的消息通知。防止新的消息打亂上次的拉取邏輯诸狭,中間出現(xiàn)丟消息的情況券膀。
另外一個問題是服務(wù)器連續(xù)推過來n條消息通知,客戶端是不是應(yīng)答這n條消息通知驯遇,去拉n次芹彬?因為客戶端一次會拉一批消息,或許處理第一條的消息通知就已經(jīng)把后續(xù)的新消息都拉下來了叉庐,后面的拉取就成了重復(fù)動作舒帮,會導(dǎo)致消息拉重復(fù)了。這種問題也好解決陡叠,從服務(wù)器拉取回來后玩郊,判斷最大msgid是否比收到的通知中msgid要大,如果是枉阵,就忽略小的通知译红,不拉。如果拉到的最大msgid要比通知里的msgid小兴溜,就應(yīng)該繼續(xù)拉取侦厚。當(dāng)然,客戶端對消息的重復(fù)過濾邏輯還是要有的拙徽。
這樣的做法是在網(wǎng)絡(luò)交互上刨沦,多了一步通知和拉取,耗費(fèi)一些客戶端的流量膘怕。服務(wù)端的實現(xiàn)邏輯復(fù)雜度大大降低想诅,客戶端需要多處理一些邏輯。
這兩種方案,我個人傾向于拉的方式侧蘸。然后其中有幾個技術(shù)實現(xiàn)細(xì)節(jié)后續(xù)再寫裁眯。比如如何保證在分布式環(huán)境下msgid連續(xù)遞增不重復(fù),如何保證客戶端對消息的排序讳癌,消息同步的具體方案,服務(wù)端對消息的存儲等存皂。
總體而言晌坤,推的方案,服務(wù)端需要處理復(fù)雜的邏輯旦袋,客戶端需要處理的相對較少骤菠。拉的方案,服務(wù)端需要處理的邏輯比較簡單疤孕,客戶端需要配合做一些保證商乎。對于消息可靠的保證方面,個人傾向于拉的方案祭阀,更靠譜鹉戚。