設(shè)計微信的聊天系統(tǒng)

微信 chat design

Intro

一開始是被邀回答這個問題, 如果好設(shè)計微信, 需要學(xué)哪些技術(shù)? 我覺得時間比空口羅列技術(shù)關(guān)鍵詞要稍微有用一點, 于是花了1:45小時寫了這篇設(shè)計. 從一個小的突破口, 從最基礎(chǔ)的需求出發(fā)來設(shè)計一下微信聊天的功能.

開一開腦洞的同時, 沒想到還讓我琢磨出了幾種微信現(xiàn)有的問題/限制: 無法云端備份聊天記錄, 微信群不能超過500人等等. 我認為是最初設(shè)計系統(tǒng)的時候有一些無法scale的缺陷, 那么導(dǎo)致了現(xiàn)在要花很大的人力和金錢去重改, 所以還沒有被當做第一要務(wù)吧!

讓我們開始, 一個大前提:

Client side message可以簡單地通過P2P來實現(xiàn), 比如使用socket.io. 但是我們這里考慮的是造一個微信, 就要將可能考慮到的全流程都涉及到. 這里假設(shè)我們的message不是通過client-client P2P實現(xiàn)的, 而是通過客戶端-> server -> 客戶端實現(xiàn)的, 那么就可以用sendMessage這個例子來介紹一下系統(tǒng)設(shè)計.

Scoping

  • 需要做微信的什么功能? [我們假設(shè)就是要實現(xiàn)chat這個功能]
  • 這個功能中間有多少個小的模塊需要考慮? [發(fā)送信息, 存儲信息, 讀取信息]
  • 這些小功能如果用最簡單的方式, 大概需要哪些技術(shù)模塊實現(xiàn)? [User Interface, Web Server, Backend Service, Data Storage, Notification model]
  • 假設(shè)有多少用戶會用? [假設(shè)每天有1K用戶, 1 million 用戶, 1 billion 用戶時不同的情況]
  • Deployment: 這個產(chǎn)品如何到用戶手里? [根據(jù)個人經(jīng)驗, 假如說是web端好了]
  • Next step: determine database based on call pattern, scaling, caching ...

Workflow

design的第一步, 都是要以最簡單明了的方式, 把需要的功能實現(xiàn)了: 先考慮,就2個人需要chat, 看是能怎么做?

根據(jù)上面的回答的那些問題, 把每一個環(huán)節(jié)寫下來.

想象一下, 你是userA, 你的女朋友是userB. 不要問為什么你是userA而女朋友是userB, 按照管理, 程序員絕大比例是單身男 , 這里讓你有一次女朋友吧!

Workflow1

你發(fā)送信息

=> Request傳到了WebServer

=> Request 傳到BackendService

=> 信息存儲在Database, 同時發(fā)送notification

=> 女朋友 的手機端不斷地在poll notification, 并且收到notification

=> 取決于這個notification里面是否包括chat的內(nèi)容, 女朋友可能再向 WebServer,Service, Database request信息的具體內(nèi)容

如果女朋友心情好, 選擇回復(fù), 那么重復(fù)以上動作

Workflow2

女朋友心血來潮, 看你手機記錄, 在app里面向上找chat history, 滑動一頁

=> 這個request傳到 WebServer

=> 找到相應(yīng)的Backend Service

=> 根據(jù)時間或者其他什么分頁方式, 從Database讀取上一頁的chat history

=> Backend Service

=> Web Server

=> 獲取的信息傳送回到女朋友這里, 看到你半夜找朋友吃雞的記錄

如果女朋友心情不好, 那么你就呵呵了.

Design Details

User Interface

UI固然非常重要, 但是在設(shè)計初期, 不必要全身心掉入UI的設(shè)計和選擇中, 基本上需要考慮的一些點, 記下來就可以. 比如:

選擇Angular做前端的controller, view.

選擇Bootstrap來潤色UI element

用Angular本身的testing framework來做testing

差不多到此為止, 下面去關(guān)注跟重要的部分.

注意: 在client端, 可能本地會運行一個小的server, 不斷地poll notifications:

這里可以用到一個AWS SQS的技術(shù), 不斷地對某一個queue讀取, 看有沒有發(fā)給自己的notification.

Web Server

我們要做一個chat的工具, 所以可以預(yù)料到:

同一個server上因為大量的user會經(jīng)過大量的I/O

server上面最重要的不過是把信息來回傳遞, 并不需要做很多業(yè)務(wù)信息的處理

基于這兩點, 我們可以暫且選用nodeJS: node的長處在于非澈⒈快的I/O 可以快速handle非常多的request.

另外的好處: node和前端都是些javascript, 在做起來的時候不用switch context太多

這一步可以稍微涉及一下API:

put sendMessage(userA, userB, message): send message from A to B

get getMessage(userA, timestamp, pageNum): based on timestamp and page num, read historical messages

deleteMessage(messageId): remove a message from database

Backend Service

Service的選擇也可以有很多, 但為了方便理解, 我們這里也選用nodejs.

需要一個backend service有security的因素. 在這一步, 你的service真的在和database交流, 而這時候會用到很多access credential, 而這些最好都是在墻內(nèi)的(不和真正的外界user接觸, 也不會expose給外界).

上面提到的web server會把每個request都傳到service來, 這中間會通過一道道防火墻和security check, 確保安全.

在service里面, 我們會有API的mapping, 比如:

put sendMessage(userA, userB, message): send message from A to B.

  • 把數(shù)據(jù)存儲到database
  • send SNS/SQS notification, 然后user會被notify

get getMessage(userA, timestamp, pageNum): based on timestamp and page num, read historical messages

  • 從database里面根據(jù)request的信息, 讀取之前存儲的message

deleteMessage(messageId): remove a message from database

  • 從database里面根據(jù)messageId, 刪除信息

Data Storage

我們選怎么樣的data storage呢? 有傳統(tǒng)的Sql database, 也有流行的non-sql database.

這里其實兩種都可以. 我們姑且將這個table命名為 MessageTable 我們在database里面很可能是用message id來存儲單個信息entry:

  • messageId: string
  • message: string
  • sender: string
  • receiver: string
  • timestamp: date

寫入database好像比較簡單.

那么我們要支持哪些種讀取呢? 比如:

女朋友讀取你和她之間前10分鐘的數(shù)據(jù): 需要 index on sender, receiver, timestamp

根據(jù)messageId 刪除entire message entry: 因為messageId是primaryKey, 直接用它刪就好了.

其他一些微信里面可能有的功能:

找到所有提到'吵架'的message: index on message

MessageTable 主要需要的一些功能就是以上, 但每個API的使用頻率可能不同, 排列一下:

寫入: 對應(yīng)sendMessage, 相對是最多的

讀取: 對應(yīng)getMessage, 比write應(yīng)該少點, 你的女朋友不會一直不斷地翻記錄, 手會累, 多數(shù)還是發(fā)信息.

刪除(其實也是write): 對應(yīng)deleteMessage, 相對少一點

搜索: 可能是index on message, 相對少一點

根據(jù)不同的call pattern, 我們在設(shè)計service的時候, 可能就會有輕重緩急的不同來分布這些API traffic. 比如: writeAPI被用的最多最多, 那么我們可能給這個service多一些box.

Notification Model

上面提到了我們可以用AWS SNS/SQS的方式來實現(xiàn)notification.

這里可以解釋幾點:

  • notification model的最終原理, 其實都是有個server在一個端口不斷地polling(), 也就是說我們的客戶端在不斷地問郵局: 有我的信件嘛, 有我的信件嘛, 永不停止.
  • 并不一定要用AWS的服務(wù), 其他的也可以實現(xiàn), 這里說SQS方便解釋.

注意: 為什么要用queue呢?

  • 后到的message, 后處理; 先到的message, 應(yīng)該先到用戶那里
  • 得到了notification了以后, 需要把這個message從queue里面刪除掉, 也是queue的原理

Deployment

這里不只是說你的APP怎么到用戶那里呢: app store, 或者網(wǎng)頁access; 這里更多是說, 如果的有更新, 那么怎么到用戶那里?

我們會用到一個pipeline的概念: 每一個stage都應(yīng)該有不同的testing, 打個比方, 吃飯要吃: 涼菜, 熱菜, 湯.

涼菜: 在test environment里面, 這里鏈接的都是test domain的web server, service, 和內(nèi)部的測試用戶.

熱菜: 這里是跟production environment 一樣了, 所有的dependency也都是在production, 然后你去測試你的APP.

湯: 這是最后的階段,也是public accessible 的那個stage:在這個環(huán)境里面的APP, 用戶就可以用到了.

你需要借助一些已有的host/deployment工具來推送和測試你的代碼.

比較簡單常用的一個服務(wù)器網(wǎng)站叫做Heroku, 是SalesForce下的一個服務(wù); 當然AWS也有一些列的host/server服務(wù), 也可以使用.

More and More

到這一步, 好像全部做完了嘛! 你和女朋友終于可以在你寫的微信上面聊天了!

問題1

你開心地邀請了你的朋友一起加入, 那么問題來了:

雖然你是一個程序員, 但是你的女朋友是交際花, 突然一夜之間來了1000個朋友加入了你的微信服務(wù)器, 你開始感受到延遲; 第二天晚上, 突然有了1 million個用戶加入, 你的服務(wù)器瞬間爆炸, 宕機了. 你該怎么辦?

第一個手段無非是: 再買幾個個box 來handle requests, 同時擴大你的database read/write capacity.

這樣scaling好像能夠減輕一點壓力, 但是很快又不行了, 當?shù)诙€million, 第三個million朋友來的時候, 你發(fā)現(xiàn)這些人又不給錢, 所以你買不起服務(wù)器了, 女朋友要難過傷心了!!!

這時候怎么辦?

前面提到了, 每種不同的操作, 有自己的重要新, 比如sendMessage()就非常非常多用和重要, 而read historical message就沒所謂; 而同時, notifiction也是非常非常核心.

回到design的初期, 我們可以選擇分流, 開兩個service:

WriteMessageService: 往上面買100個服務(wù)器

ReadMessageService: 只買50個服務(wù)器, 夠用就好了

過去你可能總共需要200個服務(wù)器, 因為所有的traffic混在一起, 加大了每個服務(wù)器的平均負荷. 而現(xiàn)在減少成了總共150個服務(wù)器, 省下了資金, 也可以繼續(xù)維持你的微信運營, 女朋友又對你笑了, 很高興很幸福啊!

問題2

不久之后, 你突然發(fā)現(xiàn), 你當初只用了1個database instance, 但是現(xiàn)在你有了10 million, 一個database的讀/寫完全沒有辦法支持, 也就是說, 很多read/write message都在跟database交互的時候出錯沒有了. 一半以上的用戶感受到了大幅度延遲和發(fā)送失誤, 產(chǎn)生不滿, 你的女朋友的手機也無法發(fā)送了, 感到非常氣憤. 這時候怎么辦?

再加上5個database instance吧, 讓來往的traffic去不同的database讀寫好不好?

這里有兩種情況可以考慮:

  • 將5個database變成各自的replication. 這樣讀起來可能方便了: 用load balancer 把request分配去不同的database讀; 但是這里有個問題: 你寫的時候怎么辦!? 每次要同時寫到5個地方, 速度不一定一樣, 而且復(fù)制也可能在network里失敗斷掉, 那么用戶每次讀寫就不consistent. 對于我們這個注重讀寫的APP, 這樣的分布不行; 如果是寫的快慢和consistency不重要, 但是讀的需求很大, 才可能用這個模式.
  • 另一個方法: 將5個database分成5分, 每一個database承載一部分的用戶, 而且永遠承載這些用戶. 這里可以用用戶的名字做個hash, 最后hash的結(jié)果來判斷存去哪個database. 當然啦, 每次在選擇database的時候, 可能要多一個判斷, 根據(jù)用戶的id, 去不同的database存取.

問題3

這里還引出了又一個問題: 我們的message是不是應(yīng)該跟著用戶走? 也就是說, 我們需要把所有跟某個用戶相關(guān)的message, 全部復(fù)制一遍. 那么實際上微信這么做么?

過去在用QQ的時候, 有個漫游設(shè)置, 現(xiàn)在分析開來, 也就是根據(jù)某個用戶個人的需求, 將他所有的message 漫游, 根據(jù)他的messageID 跟著人, 存到同一個database里面.

而微信貌似沒有做這樣的操作: 所有的message好像都是在local, 如果換手機, 并且不轉(zhuǎn)移message, 那么message就全部丟失了.

我可以理解微信不做漫游message: 因為那么多億人, 沒一個人, 就存一個他的version of chat history, 這樣可能太過費勁了. 當然, 并不是說解決不了, 但可能并沒有巨大的需求, 所以沒有去實現(xiàn), 可以理解.

雖然微信可能沒有在云端做這個getMessage()的服務(wù), 而是在本地讀手機, 但并不是說我們上面的設(shè)計都白費了. 我確定, 微信可能會是暫時存儲一定量的信息, 比如:

'最近/尚未簽收'的信息: 換手機, 上一條微信在第一個手機上還沒有打開看的, 在第二個手機上依然受到了新信息.

又或者說, 你1000個朋友同時每個人給你發(fā)了100條短信; 假設(shè)你的手機是10年前的諾基亞, 只有32MB的容量, 那么100k個短信會讓你手機爆掉吧;如果沒有, 那么這些信息可能存在某個臨時數(shù)據(jù)庫, 而不在你的手機上.

(wait, 難道微信不存在數(shù)據(jù)庫而是直接強行塞到你手機里? .... 好危險哈哈哈...不可能的啦)

你的手機應(yīng)該就是不斷地去poll()message, 然后給你發(fā)個message count, 而message本身, 還要重新去read. 這里有幾個可能的步驟:

  • queue里面的message嚴格要求簽收, 如果不簽收, 不會刪除
  • 在一定時間里面 (1 week) 在云端存取未讀信息, 比如說某個地方有個24 hours cache
    一旦過期, 這些信息就被自動刪除. 而在期限內(nèi)讀, 就可以順利拿到, 并且存一個local copy.

這樣想, 是不是我們看的一些視頻或者照片, 過了很久之后, 就打不開了, 說過期了呀? 我猜就是這個原因.

再重申一下, 為什么會需要過期:

  • 內(nèi)容太大, 并不是非常多人一直去read history
  • 根據(jù)我們粗略的設(shè)計, database分布的時候, 這些數(shù)據(jù)要跟著user存儲的地方, 被完全復(fù)制一遍, 不合理 (當然啦, 這個naive的設(shè)計導(dǎo)致了這個結(jié)果, 其實是有很多辦法拆分和優(yōu)化的, 可以有效率的實現(xiàn))

這里提到了Cache或者是臨時database.

Cache是自然而然的過期, 刪除.

如果支持TTL的database, 也是可以將數(shù)據(jù)自動過期刪除的. TTL: time to live

其他問題:

還有很多其他問題可以考慮:

  • calculate 具體的read/write, API volume來決定box的數(shù)量
  • 根據(jù)跟具體的requirement來細化database數(shù)據(jù)的分布和access pattern
  • 如何handle traffic monitoring, 采取什么樣的action 等等

結(jié)束語

做一個粗略design就是這么high. 寫完這些, 大概耗時1個小時45分鐘.

這個design能不能用呢? 我覺得實現(xiàn)你和女朋友的單方面溝通, 是綽綽有余的, 但是思考的過程中已經(jīng)發(fā)現(xiàn)了非常多的漏洞和可以用actual use case填補的地方. 真的要給1million個朋友用, 估計夠嗆: 我們巧妙地忽略了UX的設(shè)計, 和PM的斗爭, 無窮無盡的Testing等等等等. 先寫到這!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末长酗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雁歌,老刑警劉巖京腥,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抗楔,死亡現(xiàn)場離奇詭異痛倚,居然都是意外死亡失尖,警方通過查閱死者的電腦和手機善榛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門戴而,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砾肺,“玉大人齐佳,你說我怎么就攤上這事≌冢” “怎么了炼吴?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長疫衩。 經(jīng)常有香客問我硅蹦,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任童芹,我火速辦了婚禮涮瞻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘假褪。我一直安慰自己署咽,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布生音。 她就那樣靜靜地躺著宁否,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缀遍。 梳的紋絲不亂的頭發(fā)上慕匠,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音域醇,去河邊找鬼台谊。 笑死,一個胖子當著我的面吹牛譬挚,可吹牛的內(nèi)容都是我干的锅铅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼减宣,長吁一口氣:“原來是場噩夢啊……” “哼盐须!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚪腋,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姨蟋,沒想到半個月后屉凯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡眼溶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年悠砚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堂飞。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡灌旧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绰筛,到底是詐尸還是另有隱情枢泰,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布铝噩,位于F島的核電站衡蚂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜毛甲,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一年叮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧玻募,春花似錦只损、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至坑雅,卻和暖如春辈挂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背裹粤。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工终蒂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人遥诉。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓拇泣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親矮锈。 傳聞我的和親對象是個殘疾皇子霉翔,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)苞笨,斷路器债朵,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評論 25 707
  • 第二次服靶向藥的時間:3月12日,植樹節(jié)瀑凝。距今天已經(jīng)三周了序芦。升血小板的藥停了,上次住院抽血檢查的血小板計數(shù)大約是2...
    梔言片語閱讀 263評論 1 2
  • 柳搖因風(fēng)起粤咪,水濁源浪故谚中。 心若遇風(fēng)浪,清靜歸何處寥枝?
    鼓樓和魚pandada閱讀 149評論 0 0
  • 很多人說女人談戀愛的時候智商為零宪塔,不其然,恰恰相反戀愛中的女人智商情商雙飆囊拜。 很多看似很傻很天真的話那也是在心理想...
    賴七錢閱讀 238評論 1 1