好友動態的實現原理
今天準備跟大家聊的一個功能就是:好友動態功能。聽起來好抽象,有木有 ~~ 那說直白一點:微信朋友圈。這下聽懂了嘛?
我們現在好多軟件都有一個類似朋友圈的功能:微博、微信、 QQ …… 只要涉及到好友、粉絲這樣的 app 或者是網站,一定有這樣的一個功能。那這個功能是怎么樣來實現的呢?
其實,這個技術已經非常的老了…… 估計快 7-8 年了。那個時候有一個網站還非常流行,他叫:開心網。大家都在偷菜、搶車位,然后你的好友動態里就會出現“ xxx 又搶了車位”這樣的信息。那會兒老王在貼吧,產品經理要求做一個叫做 i 貼吧的產品(不知道有沒有人知道或者能記得),其中一個類似這樣“ xxx 又發了一個貼子”的功能。同時代的,微博、微信等等紛紛擁有這些功能。這看起來就是一個爛大街的功能…… 后來到了百詞斬,要求做一個好友動態的功能,看起來就完全是同一個功能的翻版。
那這個爛大街的功能是怎么樣實現的呢?等老王慢慢道來。
1 、基礎邏輯
如果你有 N 個好友,當他們其中有 K 個人發出了新的信息,你就可以看到按時間倒序的排列的動態。
有些時候,還會根據業務的要求,將某些動態信息進行合并,比如:某幾個人都回復了同一個貼子,就可以合并為,“ xx1, xx2, xx3 都回復了 yyy 發的貼子《老王真帥》”。這樣就使得動態消息看起來不是那么冗長繁雜。
好了,基本邏輯描述了這么多,想必大家都已經心領神會。幾個必要的點:
A 、好友間的關注關系:單向關注一般稱為粉絲;雙向關注一般稱為好友。為描述簡單,統一稱之為好友的關注關系;
B 、好友產生動態:好友做了需要記錄和展示的動作;
C 、動態的聚合:多個好友產生的動態需要合并后展示。
2 、實現的原理
那具體怎么樣實現呢?有幾種方法,我們一一來介紹。
A 、 推 送到信箱。
這個是最容易想到的實現方法。假設我有一個信箱,如果我的好友,每次發新的動態的時候,都將他們的動態放一份到我的信箱里。這樣,我每次去信箱里看的時候,就能從上到下看完所有的好友動態。
上圖演示的就是有新動態的好友將新的動態推送到我的信箱。因為整個過程是信息產生者主動發送的過程,所以這種方式我們就叫做 推 (push) ,這是一種主動的行為。
優點:
那這種方式最明顯的優點就是:
a 、邏輯簡單。這個可以用非常簡單的偽代碼來表示。
foreach (Follower f in followerList)
{
f.mailbox.add(myMsg);
}
b 、新動態信息顯示及時。在數據量不大的情況下,基本上可以做到實時或者是準實時。這邊兒動態一發,那邊就可以顯示出來。
不過這個方法的問題也比較明顯。比如,有一個明星,他有上千萬的粉絲,如果他發一個動態…… 就只能呵呵了。我們簡單算一下,如果每個動態推送給好友的時間假定為 1 個毫秒,那么 1000 萬條動態順序推送的話,就需要 1ms * 1000w = 1ws 約等于 3 個小時。
也就是說,我們的系統什么事情都不做,在保證效率足夠高的情況下,將這條動態推送給這個明星的所有關注者,需要 3 個小時。也就是說,最后一個粉絲要動態發生后 3 個小時才能看到。這個時效明顯就失去了動態這個產品的意義了。
那對于這個算法,我們有什么改進的方法嘛?
當然有(不然老王怎么往下講呢,對不對)!既然串行(一條條的)推很慢,那我們就讓推這個動作并行起來。因為推這個動作消耗主要在網絡上,而對于推送的機器來講, cpu 消耗是很低的,所以我們可以通過增加線程的方式,來提高推送的并行度。比如,開 10 個線程來推,這樣我們所花的時間是不是就接近于原來的 1/10 了呢?
不過,如果有很多明星都同時發動態,我們的線程估計就要爆掉了。那又有解決方案沒有呢?那肯定也是有的。既然這個地方會卡在同步的發送上,我們是否可以用異步的方式來推送呢?還記得分布式系統嘛?還記得我們的 Message Queue 嘛?我們就可以用這個系統來解決我們的問題。
當某一個朋友發送了一個新的動態,我們就將這個動態放到一個入口的發送器里( Poster ),這個發送器立刻將這個動態轉發到傳送的 Message Queue ( trans-mq )。 trans-mq 是多層級聯,根據實際用戶量來計算。最后一層的 mq 是用來做實際更新的( update-mq )。每一個負責一組用戶的更新,將相關的動態塞到需要管理的那組用戶的信箱里。
比方一個 update-mq 管理 100 萬用戶,那么我們如果有 1 億用戶,就只需要 100 個這樣的 update-mq ,可能就只需要一個 trans-mq 。當某一個明星有發送新動態的時候,他的 1000 萬粉絲散落在這 100 個 update-mq 管理的群組中,每個群組平均就只有 10w 個粉絲。如果寫入每個 mailbox 還是 1 個毫秒,那么 10w 個粉絲就只需要 100 秒(大概 2 分鐘)。這樣,我們就把一個需要幾小時處理完成的工作,變成了需要 1-2 分鐘需要做的工作。為什么呢?因為他們并行起來工作了!
那是不是就完結了呢?并不是!如果發送的信息違反 xx 規定需要刪除怎么辦呢?有兩種方式:
a 、同樣的流程還需要走一遍:將刪除操作推送到各個信箱;
b 、全局增加一個刪除位,記錄所有刪除過動態:用戶請求的時候,將動態 id 去這個刪除系統里面查,看看是否有刪除過。如果有,則過濾掉已經刪除的動態。
好了,以上就是推送到信箱這種方式的工作原理和優化,怎么樣,看懂了嘛?
B 、用時再 拉 取
這第二種方式呢,實際就是一種懶人模式( lazy mode )。他究竟有多懶呢?真的,很懶。如果你的好友發了動態,你永遠不登錄,這個動態就永遠不會推送給你。只有你登錄到動態頁面,系統才會懶洋洋的把你的好友的動態抽取一遍。
上圖大體描述了這個動態獲取的過程:
a 、用戶進入到動態頁面(比如:朋友圈);
b 、獲取好友列表;
c 、把好友的動態按時間倒排并展示。
大家注意到沒有,整個過程中,用戶已經沒有了那個 mailbox ,取而代之的是動態的去拉取好友的信息。相對于之前聊的主動推送而言,這個就是一個被動的拉取過程。
由于是這樣的一個工作模式,所以他有他的優點:當我關注了多個大 V 的時候,這些大 V 發動態不再主動推送給我,所以就避免了上千萬次的主動推送過程。轉而是用戶瀏覽的時候再去獲取。這使得我們的系統不會因為大 V 發動態而變得忙碌。
不過,如果我們的登錄用戶很多 && 他們都關注了很多其他用戶,我們的系統就會有很大壓力了:因為,他們要動態獲取被關注者的信息并且做聚合。那我們有哪些辦法做優化么?來看老王的才藝展示:
a 、限制關注的數量:大家可以看到,好多的動態系統一般都限制用戶添加好友或者關注的數量。其中有一個原因就有可能是采用了這樣拉取動態的手段。限制用戶關注量以后,我們系統在合并的時候,就可以減少合并的數量,從而提升效率;
b 、為每個用戶建立動態的緩存:我們可以給每個用戶建立一個動態的 cache ,如果關注他的人取拉取他的動態,就不用每次都從磁盤(或者數據庫)拉取,而直接從緩存中獲取。從而提升拉取的速度;
c 、為聚合后的動態建立緩存:還是緩存方案,不過這次針對的不是產生動態的人,而是瀏覽動態的人。一旦拉取過一次以后,我們就將聚合過后的數據做一個緩存,下次再刷新的時候,就直接可以從緩存中獲取(特別適合那種手不停的下拉刷新朋友圈的人 ~ )
d 、建立時間更新表:這個是個什么東東?如果我關注了 500 個人,如果平均每個人有 10 個動態,那一次給我 5000 個動態,對于我來講,我會瘋掉…… 所以,其實從產品上講,沒有必要將所有被關注者的動態都獲取來展示,對吧。比如,我們可以獲取最近有更新的 20 個或者 50 個被關注者,將他們的動態拉取做展示,對于絕大多數情況就非常適用。那種長期不更新的人,也沒必要看,對吧 ~ 所以,我們可以為每個被關注者建立一個最后更新時間表,然后獲取動態前,先看看哪些最近有更新,然后只拉取這些有更新的用戶的動態做合并排序就可以了。
好了,動態用時方去拉,這種方法就差不多說到這兒。看懂了么?
在具體設計這個系統的時候,最好是根據產品業務形態、用戶的數據規模等等綜合來判斷和考慮。兩種方式各有優缺點,實現的成本也因規模不同。
a 、如果規模小,用最簡單的推就可以了;
b 、如果規模中等,用簡單優化過后的拉也可以了;
c 、如果是規模比較大,可以適度考慮優化過后的推或者拉,甚至推拉結合。
沒有一個完美的架構,只有更適合業務的方案。所以,一切都取決于你的選擇
來自:http://mp.weixin.qq.com/s?__biz=MzA3MDExNzcyNA==&mid=2650392282&idx=1&sn=93fd63aea3ee3a3ccfc6cae43699fa65&scene=0