本文由ITPub根據封宇在【第十屆中國系統架構師大會(SACC2018)】現場演講內容整理而成。php
瓜子業務重線下,用戶網上看車、預定到店、成交等許多環節都發生在線下。瓜子IM智能客服系統的目的是要把這些線下的活動搬到線上,對線下行爲進行追溯,積累相關數據。系統鏈接用戶、客服、電銷、銷售、AI機器人、業務後臺等多個角色及應用,覆蓋網上諮詢、瀏覽、預定看車、到店體驗、後服、投訴等衆多環節,各個角色間經過可直接操做的卡片傳遞業務。html
例如,用戶有買車意向時,電銷或AI機器人會及時給用戶推送預定看車的卡片,用戶只需選擇時間便可完成預定操做。整個系統邏輯複雜,及時性、可靠性要求高,涉及IM消息、業務卡片、各類實時統計。這次演講,從數據架構層面講解系統遇到的挑戰及解決辦法。web
補充說明:本文對應的演講PPT詳見《瓜子IM智能客服系統的數據架構設計(PPT) [附件下載]》。算法
學習交流:數據庫
- 即時通信/推送技術開發交流5羣:215477170 [推薦]windows
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》後端
(本文同步發佈於:www.52im.net/thread-2807…)緩存
封宇:瓜子二手車高級技術專家,中國計算機學會專業會員。2017年2月入職瓜子二手車,主要負責瓜子即時消息解決方案及相關係統研發工做。在瓜子期間,主持自研消息系統用於支持瓜子內效工具呱呱,知足瓜子兩萬多員工移動辦公需求;做爲項目經理,負責瓜子服務在線化項目,該項目對瓜子二手車交易模式及流程帶來深遠影響。安全
在入職瓜子二手車以前,封宇曾供職於58同城、58到家、華北計算技術研究所,參與到家消息系統、58爬蟲系統以及多個國家級軍工科研項目的架構及研發工做。在消息系統、後端架構、存儲架構等方面有豐富經驗。服務器
封宇分享的其它IM技術資料:
《一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)》
《即時通信安全篇(七):用JWT技術解決IM系統Socket長鏈接的身份認證痛點》
今天分享的題目是「瓜子IM智能客服數據架構設計」,這個系統和旺旺比較像。
簡單說一下分享的幾大部份內容:
第一部分:項目背景,項目背景須要稍微講一下,有助於你們理解;
第二部分:是系統架構,爲何要簡單講一下系統架構呢?由於不講業務的架構都是耍流氓,因此說咱們講存儲,都要知道系統是怎麼回事;
第三部分:重點講一下存儲,在這塊咱們講分享一下咱們的實踐經驗,以及演進過程,更多的是採到的一些坑,咱們怎麼解決的。
比較熟悉的都知道瓜子二手車沒有中間商賺差價,其實瓜子在中間要作不少事情。
首先二手車很難,它不是一個標品,咱們要去收車,收車都是在社會上去收,經過網站收,你要驗車。咱們要把它放到咱們的網站上,有些車要收到咱們的場地,有些車多是在用戶家裏樓下停着。若是一個用戶來買車,在網上點了以後,你可能下單要去看這個車,咱們的銷售要去跟到線下去陪你看車選車試駕,還有一系列的複檢等等,有不少的事情。
如今瓜子這塊咱們作的還不夠好,爲何不夠好?咱們站在瓜子內部的角度來看這個事情,有不少的行爲發生在線下,咱們很難防就會帶來不少問題。好比飛單,有些去跟別的企業串,這個車就沒有在平臺上賣,這些問題都很難解決。
第二個就是銷售到底跟用戶在作什麼,他沒準罵那些用戶或者什麼的,回頭企業發現的投訴很難查。那咱們這個項目的一個重要的問題,就要解決一個線上化,就是把這些線下的行爲搬到線上,咱們利用通信,在IM過程中傳遞一些業務,這樣把整個的一些業務線上化固化下來。
第三個是電商化,我比較喜歡用京東,我以爲他的物流和客服都很好,瓜子也是但願作成電商,還有一個就降本增效快一點,若是如今你用瓜子,你會發現你到網上去瀏覽,就會有電銷人員給你打電話,這個是很煩的,對用戶的體驗很很差,再一個對瓜子來講成本也很高,咱們也養了不少的電銷,因此說咱們就啓動了這樣一個項目來解決這些問題。
剛纔提到了很複雜,整個系統串聯了不少角色,有用戶、銷售、電銷、評估師,還有AI和機器人。作系統要善於抽象,咱們先有一個基於通信的即時通信系統。第二咱們是要把通信系統集成到業務,或者叫把這些業務搬到通信系統上面來,核心就是這樣子。
這是截了幾個圖,咱們看第一個圖,咱們上邊就有一個車,下邊有個預定,用戶事實上是能夠直接在聊天界面裏面去點預定了。這個就是我說的跟通常的客服不同的,它能夠作一些叫作導購或者叫營銷也好,直接經過這樣一個途徑,由於瓜子的獲客成本很高,每一個訪問瓜子的用戶咱們都但願及時跟他溝通,這個圖有助於你們理解。
整個系統不是一個單一的系統,它結合了一些業務,咱們能夠把它拆分紅這麼幾個層次。最上面是一個端,那端首先就是瓜子的APP,固然還有毛豆的,如今瓜子的業務有好幾個,除此以外有些員工用的,好比說電銷的、客服的、售後的、金融的、咱們可能都是一些APP或者是一些桌面系統,這是咱們的客戶端。
第二是一個路由層,咱們要打通這些業務。要讓這些業務在這個系統裏邊及時的傳遞,所謂傳遞剛纔前面有個圖案,好比說你想約車了,那客服就給你或者電銷就給你發一個約車的卡片,你就能夠直接選時間約,這是傳遞業務。再往下邊是一些業務層,那就是原來瓜子有不少有什麼業務就涉及什麼業務,最底下是一個存儲。此次後邊主要講的就是站在存儲層的角度來看整個系統,重點會講存儲層怎麼對路由層進行支持。
存儲這塊會講大概幾個點,包括:
1)數據庫的拆分;
2)消息怎麼存,消息裏邊也會特別提一下羣的模型,大規模的羣是比較麻煩的;
3)還有一些存儲邏輯以及業務怎麼在上邊run起來;
4)最後是統計分析,實時計算這樣一些設備。
這個圖是如今的一個數據庫的圖,咱們看着這些就是數據庫已經分得很好了,好比通信的數據庫,有調度的有卡片有分析。
我在這裏介紹一下「調度」這個新出現的名詞,它是幹嗎的?就是你在這個系統裏邊點開一個車也好,點開的人也好,聊天時用戶看到的是一個瓜子的客服,後邊瓜子內部其實是一個瓜子的客戶團隊很是大的團隊來支持你,因此說到底你跟哪一個客服聊天,是有一些策略的。
咱們感受這樣一個拆分其實是瓜熟蒂落的,可是事實上根本就不是。我舉個例子,2015年大概是京東內部有一個分享,劉強東分享流露出來了,說有一個二手車企業一年仍是一個月,我忘了,賣了兩輛車出去,估值就到了2億美金。簡直不敢相信。
我分享這個例子並非說話有什麼不對,固然我相信他也不是說的瓜子,由於瓜子A輪它不止這個數,我想說最初企業是很小的,業務量是很小的,咱們根本就不多是這樣一個數據庫的結構,就跟沈老師說的同樣,其實它就是一個庫也沒有什麼IM,沒有什麼調度,這些卡片可能就是一個賣車的一個數據庫。
我是去年的2月份進入瓜子的,快兩年了,那時候瓜子的業務量很是小,具體我也不知道,就是一個數據庫,一個數據庫實際上是很是好的。由於不少人來了以後就說要拆庫,一個數據庫的好處是寫業務很快,十幾我的快速的就把系統就搭起來了。
咱們事實上庫拆成這麼幾個也經歷了一個過程,最初是作IM只有一個IM的庫,後來有了調度加了一個庫,再後來有什麼卡片,有分析逐步得往外擴。
這個庫它實際上是有必定的成本,若是你拆分得很差,你會去作不少接口,好比說你像關聯查一下,發現不是我團隊的庫也要作各類各樣接口,產生了分佈式的事物的一致性的問題,都產生了。因此說數據庫的拆分,尤爲垂直拆分,其實是隨着你不一樣的階段,你選擇不一樣的拆分方式,之後隨着系統的擴大瓜子業務擴大這個系統它會拆的更多數據庫,但拆的更多,對你的運維監控這些團隊的挑戰都會帶來一些成本,也會帶來一些挑戰。咱們如今把它拆成了這樣一個庫,各司其職。
(原圖來自《現代IM系統中聊天消息的同步和存儲方案探討》)
下面就重點說一下消息,這塊咱們怎麼存?
咱們看一下左邊這個圖,左邊這個圖是通常來講很容易理解的消息,怎麼存的方式?
之前桌面系統常常這麼幹:好比說A要給B發一個消息,他怎麼發?他就說A用戶端,A這個端我發一個消息,若是B在線,我就把消息直接發給他,他給我一個確認,這個過程就存儲好就結束了。
其實我服務當中不須要存這個消息,若是是A發給一個C這個C不在線怎麼辦?
咱們也有策略:A把這個消息發給了C,C若是沒有確認說我收到這個消息,我就把這個下邊的第二步,我就把它存到一個離線的數據庫裏邊等着你C何時上線,你就把這個消息拉回去,這個過程就完結了,這個消息我就給送到了,因此說這個時候的存儲很是簡單,我就一個離線庫,存一下某我的的消息就行了。
可是這種模式實際上是有不少問題的,真正使用的時候,不少產品如今是移動端的手機端,網絡首先是不穩定,長期處於一個C的狀態,若是你都去監控它的狀態,送沒送到,再存儲性能會不好。
(原圖來自《現代IM系統中聊天消息的同步和存儲方案探討》)
第二個如今的端有不少:有桌面的、有手機的、有APP端、還有PAD端,有好幾個端。若是都用這種模式,須要爲每一個端都這裏判斷去看傳輸數據其實也是很困難的。
咱們就變了一個方式:第1步來了消息,咱們就把消息存到存儲庫,你只要發消息我就先給你存下來,第2步,同時我還存到一個同步庫裏邊。
這兩個庫要稍微解釋一下,存儲和同步庫分別來作什麼?
存儲庫比較好理解,你何時都能從庫裏邊還原你的消息,把它讀回去,好比說你換了手機,你均可以把消息拉回來。
這個同步庫是什麼意思?同步庫就是說你沒有換手機,也沒有從新裝系統,就是你可能有一段時間離線,離線起來以後,就說我好比假設一個消息序列,1到100發給你了,就A發給C,1到100了。結果可是C從第70號消息的時候,他就離線了,他就不在線。這樣子,C這個端上線後,第四步把70號消息傳給這個服務端,說我有70消息同步庫就知道,把70到100的消息發給C。這樣子消息就是能夠送達這個端,這兩個概念稍微是會有一點模糊,可是沒有關係,後邊我會接着展開來說。
也就說一個消息,咱們會存一個消息同步庫和一個存儲庫,這個其實是一個消息同步庫,它是一個模型,我下一張PPT應該會講用什麼東西來存它。
若是咱們把它理解成一個郵件,你很好理解。咱們的郵件,有一個收件箱,同步庫就像一個收件箱,無論是誰發給你的郵件,羣發地也好,單發的也好,反正我都給你放一份,在收件箱裏面放一份,A把這個郵件放進去了消息,你從另外一個要取出來,你無論用這個手機也好用你的蘋果系統筆記本或者windows本也好,也都要去收這個郵件,收的過程就是什麼?好比說剛纔說第一蘋果系統筆記本,B1說我以前收了前兩封B遊標,它本地有一個消息最大的,說我在2號消息,我收到了,那他把2號消息傳給服務端,服務端就說好,後邊2到20號消息均可以收走了,這樣子能夠保證這個消息不重不漏地送給客戶端去。
B2說我以前這個端其實收了十封了,我就從11分開始收,B3說就收過一封,我就從第二封郵件開始收就行了,這樣子就解決了,消息就送過去了。這裏有幾個問題,咱們同步的過程就是理論上是能夠了,可是有幾個問題,第一個就是擴散寫擴散讀,在這塊跟存儲很相關,擴散寫和擴散讀有不少討論。
舉個例子:是什麼地方產生的?好比我若是是一個單聊,咱們兩我的聊天沒有問題,我確定把消息都寫給你了。可是事實上不少時候咱們是在一個羣裏邊聊天,給咱們銷售,咱們的評估師或者機器人,他們都在裏邊聊,用戶也在裏邊聊,這個消息我是爲每人寫一份,仍是我爲整個會話也就是這個羣,我只寫只存一份,大家都來讀。一個會話的消息,或者說你本身手裏的收件箱,我先說結論,咱們是在消息的同步庫裏邊採用的擴散寫,後邊還有一個存儲庫,存儲庫裏邊咱們是採用的擴散讀的方式,在同步庫裏邊,咱們每人都寫了一份,這樣子讀的時候很方便。而在存儲的時候,因爲咱們的存儲速度慢一些,咱們是隻寫了一份數據,這一個會話只有一條消息。
第二個點是事實上在同步過程當中,咱們遇到了一些問題,有不少的策略須要咱們考慮。就是一個消息TimeLine,有時候不一樣的端有不一樣的同步策略,好比說有些場景下,咱們要求它的每個端都收到這一條消息,那就是咱們的通知,在公司發的優惠券什麼的,每一個端都要送達。有些場景人他是但願說你的手機收了,那你打開桌面,咱們就再也不給你送這個消息了,按照剛纔這一個同步模型,有一些困難的每一個端可能都會去搜這個消息,因此說咱們就準備了三個這樣的存儲的號。
那好比說這個圖上的B1B2B3,當前消息的咱們另外存在一個最大消息的號。第三個號你全部的裏邊搜的消息最靠前的一塊就說比較像B2這樣經過這三個位置的組合,咱們能夠肯定你收取消息的位置或者一個策略,這是這個模型,咱們存儲採用什麼?咱們採用了Redis cluster,咱們用了SortedSet結構。
我專門把它提出來了,由於咱們其實在這還踩過一個坑,我要存這個消息了,怎麼都得知道這個結構的效率,咱們查了一下SortedSet效率還能夠,就說它是一個ln這樣子一個效率,因此說在同步庫,若是咱們每一個用戶只存一部分消息,它的性能是很是高的。這個結構本質上是個跳錶,跳錶結構其實很複雜,我想在這會上講清楚很難,最後放了兩個圖,就是一本書。
咱們知道這些結構,好比像二叉樹或者一些紅黑樹,檢索都有比較好的索引策略,跳錶也相似。它比較相似什麼,就像咱們好比說一本書上有一千頁,我想翻到856頁怎麼翻?其實咱們有一種方法去前面去找索引去定位什麼東西,還有我大概翻到800頁,逐步修正。跳錶結構本質上比較像翻書,我以爲是翻到一個大的頁,先翻800頁,翻到850,在逐漸翻到860頁。
下面分享一下這塊咱們遇到了一個什麼問題!咱們SortedSet存儲,存消息,而咱們存消息爲了全局一致性,用了一個思路。這個算法咱們消息是一個長整型,就是上班卡,我先講的是不要緊,先講下面精度丟失的問題,咱們的消息是一個長整型。這個場景下總共是64位,因此說snowflake這個算法,它的前面第一位不用,它其實表示正整數它是有意義的。用41表示一個時間區間,這裏面產生一些ID表明了大概有六七十年或者三四十年,反正是確定是夠用了。
中間十位是一個工做機編號,他能夠支持1024個臺機器,咱們現階段用不了這麼多機器,最後的12位是一個毫秒內的一個序號,因此說構成了咱們消息的ID所以消息是很長的一串,算下來得18位的整數。
放到SortedSet裏邊以後,咱們後來就發現一些問題,發現這個時間靠的近的消息,咱們區分不出來它的前後順序。就這深挖下去,發現SortedSet它十個字其實是個double類型的,下邊這個圖是double類型的描述,它的精度只有52位,上邊長整型它是有63位的精度,這裏邊就有11位的差距。因此說在那個毫時間很接近的消息,它的精度丟失,咱們檢索拉取的時候,這些順序就出了問題。
所以咱們採用了一個策略,也能夠借鑑一下,根據咱們的當時的負載量以及機器數,這個最終保證了咱們幾乎遇不到這種精度丟失的問題,就把精度主動的轉換下降了。這個case上說明就是咱們選擇存儲的時候,數據類型很重要,你得根據你的業務類型看一下。
咱們還在這同步的時候遇到一些問題,就是這個問題更多出如今咱們內部的一個工具,咱們有不少的人數比較大的羣,由於咱們的消息像一個收件箱,他的大小是有限制的,有些大羣它瘋狂的刷消息,那這樣子這個羣裏邊可能就有成百上千上萬的消息,由於咱們收件箱大小有限制,咱們就會把以前更早的消息淘汰掉,致使一些單聊比較重要的消息就丟失了,這個是咱們的遇到的問題,後邊的PPT會有解決方案。
第二個問題就是還有一些web端,咱們web端,其實本地的緩存是很難用的,這個就是咱們用戶一打開以後,它有多少未讀數,只能先經過咱們把消息拉回去算一下,新拉到多少消息,才能計算出它有多少未讀數。這個實際上對咱們也是一個挑戰,很不友好。
接下來咱們講一下存儲這塊,咱們存儲消息要落庫了,咱們怎麼存消息?
咱們剛纔提到了,是按照每一個會話你看到的每一個人跟你聊天的一個維度來存儲。咱們也是採用了分庫的策略,分庫比較簡單。
這舉個例子:事實上這個庫不止這麼多個,咱們把一個他的消息繪畫的ID除以四,取它的模來肯定它到底放到哪一個庫裏邊,剛纔提到了咱們不少ID是用snowflake算法來生成的,咱們有個方法來防止它的生存不均,看一下。這是一個咱們防止它的ID分佈不均的一個方案。咱們看到最後有一個12位的序列號,若是你不加任何干預,他每次都從零開始,事實上當你併發比較小的時候,你會發現它後邊都是零,就最後幾位都是你這樣子,若是都是零,你用是你用固定的取模的算法,他就絕對是不平均了。
更多有關消息ID的算法和策略,能夠詳細讀讀如下文章:
好比說你的數據庫不夠了,你到時要擴容的時候你就發現很困難,你不知道之前的數據,你只能把之前的數據所有翻出來,簡直是災難,因此說咱們須要人爲的干預,咱們就是用取模的方式,把分庫進行特殊的處理,加上一些分庫的行爲,在這個裏邊咱們用了一個ID生成的時間,給他一個最後八位的一個遮罩,他在128個數據庫的時候,它分佈會很平均。
說一下怎麼擴容:剛纔以前提到的最開始業務量不多,可是前幾天瓜子一天已經賣出1萬輛的車了,因此說這個量如今咱們是逐步的會在起來,當成交1萬輛,用戶量是很是大的。咱們怎麼擴容,這就是擴容的基本方法。咱們最初有db0123這樣幾個庫,咱們看一下左邊有就是這個圖的左邊,一個msg:chatid=100和msg:chatid=104,之前chatid除以四的時候,100和104這兩個數據,這兩個數據它都會並重db0這個庫。
咱們分庫的時候怎麼作?第一步咱們把db0123這樣的庫同時進項,我就要主從同步,反正在搞db4567,db0和db4數據同樣的db1和db5數據也同樣,相對應的同樣。咱們把分庫策略改爲除以八求餘,以後的結果就出現什麼?
咱們就發現按照新的分庫策略,chatid104還在db0裏面,chatid100它因爲除了以後他就到db4了,這樣子咱們就至關因而從四個庫直接就變成了八個庫,以後的過程就是分庫規則上線以後,咱們再由DBA把不屬於庫的其餘數據給刪掉,這個庫的擴容就搞定了,因此說業務能夠接着跑,可是這樣子是否是就解決問題了?
其實沒有簡單,遠遠沒有,由於你像咱們的數據庫前面的數據庫是MySQL,爲了安全,他有一重兩重可能還有擴庫,簡直這個數據庫愈來愈多,它運維和DBA他就不幹了,說你這庫愈來愈多,我怎麼維護,這個受不了了。
因此說還有一個就是這種關係型數據庫,它能夠支持像事務有好多事務關聯性查詢這些很是豐富的邏輯,但事實上咱們一個消息都最多的一個數據量的應用,它用不上這麼複雜,它是很簡單的,我要麼根據消息ID查某一條消息,要麼根據一個消息要檢索這個範圍之間的消息,真的用不上這麼複雜的一些邏輯,因此說存儲用關係型數據庫並非說特別適合,那咱們就去研究了。
咱們首先一查發現有一個時序型的數據庫是一個OpenTSDB,他的應用場景跟這個業務很類似。OpenTSDB它內部是用Hbase來實現的,咱們以爲Hbase就很好。咱們爲何選擇Hbase?由於有團隊維護,很是現實。選用了Hbase以後,這個接下來若是用過Hbase的同窗就知道,除了咱們要分片,這些作好了以後,最關鍵的是要設計Rowkey設計是須要結合業務,並且須要設計得很是精妙,咱們的Rowkey結果就是一個會話chatid ID,Chatid能夠分散region,msgid 時間有序用於範圍檢索。
而若是咱們用這個你去諮詢,你會常常的一個場景,我打開了看到的最新的消息,我往下劃一劃纔是加載更老的消息,這個結構正好一來了以後,檢索你最新的消息,你往下滑的時候,咱們就接着去查後邊的消息,這樣子很是快,而若是當地什麼都沒有,你從新新裝一個很是方便,你直接來Hbase裏邊查詢最新的Rowkey你就找到你最新的消息了,這個就解決了。
還有一點就是region,就像分庫同樣,Hbase作的比較好,它能夠本身幫你維護這個分片,可是咱們不建議這麼搞本身維護分片,當你像這種消息的數據它存儲量是很小的,它很小會致使默認給你一個region,可是這樣一個讀寫瓶頸就來了,因此說咱們須要提早規劃咱們分庫的region.
下面是一些羣,羣有一些特殊的地方,在咱們的二手車APP上,這種大規模的羣比較小,可是我想分享的是咱們在內部通信裏邊羣遇到的一些問題也帶上來,就一塊兒把它跟你們交流一下。
第一個就是剛纔提到的減小存儲量。這個是下面的存儲庫,好比有不少羣,可能有2000多人有,若是我發一條消息就存2000份,那簡直是災難,因此說咱們只能存一份,所以咱們看這個圖就是左邊的藍色以後,咱們只存了一份,標明瞭這個消息ID,標明瞭這是哪一個會話或者是哪一個羣的。
由於存了一份,第二個問題就帶來了:若是今天加羣的人,昨天加羣的人其實看到的消息應該是不同的。
正常業務是這樣,有時候你還能夠看到最近多少條的邏輯怎麼實現,就是咱們在再給它擴展一個數據庫表,這個表是關係型數據庫裏的,記錄上羣的號碼,記錄上這我的的ID,記錄上他加羣的時間,加羣的時間咱們能夠經過一個函數把它運算。因此說msg ID的策略很重要,咱們通過加羣時間,因爲它是一個時間的函數,咱們能夠跟這個加羣的時間進行一個映射關係,這樣子我經過加羣時間可以大概定位到他從哪條消息能夠檢索,若是你須要去作策略,也能夠說上面看多少條,下邊看多少條均可以作。第三個就是有一個會話排序的問題,這種對話的場景裏邊,咱們能夠看到會有不少的會話,因此說這是一個策略的選擇。
第一種作法:你能夠爲每一個人建一個會話,他每有一條消息,你就把他的最後時間更新一下,這個過程就能知足會話的排序,但事實上咱們能不能這麼作?我以爲咱們不能這麼作,由於有些時候消息不少,並且有些時候用戶很大,咱們發一條消息。有一千我的要去更新他的狀態,無論你用多少都是扛不住的,因此會話的策略,咱們也是在緩存轉存會話的最後一條消息量。當用戶要來拉取他的會話列表,或者更新他的會話列表的時候,由服務器端給他預算好了以後返回給他,咱們用的時候正常狀況下與客戶端它本地是能夠收到消息,若是你在線他是本身知道調整這個數據的。拉取會話的行爲,當它發生離線了再次打開,這個時候須要更新一下,若是這個頻率比較低這樣一個取捨,咱們的存儲模型也就出來了,因此說其餘不少業務都是在發生的時候咱們就跟蹤她的狀態,而這個會話排序咱們是在好比說讀取的時候咱們才能夠創建這個過程。
後邊的已讀未讀,這個點再也不細講,沒有什麼特徵。咱們知道緩存裏爲每條消息都建了一個存儲結構,說這條消息哪些人已讀哪些未讀,在比較短的時間把它淘汰。消息撤回這塊提一下,以前有個小同窗這麼幹,這個消息怎麼撤回?在關係型數據庫裏邊,這個消息要撤回,我在表裏邊把這條消息標記上,這條消息是撤回來的,這個作法有沒有問題?一點都沒有問題。
以後他又來了個需求,說我就想看一下這些沒有撤回的消息拿出來怎麼辦?這個同窗也是剛畢業沒多久,就調整,就想到了建索引,他就把索引建好了,就能夠這麼去拉取數據,結果跑一段時間數據庫報警了。這不行,怎麼回事?由於撤回的消息跟正常沒撤回的消息比例是失衡的很是小一間隔索引,因此毫無心義,並且還消耗了寫消息的性能,所以咱們撤回消息後來兩種作法,第一是把它從這個消息庫裏邊刪掉,挪到一個撤回的消息表裏邊,這是顯而易見的。還有一種作法就是咱們也打標記,可是不作索引,我也不支持你過濾接受,而我是無差異的拉出來以後在存儲的邏輯層那邊把它過濾掉,這樣子作。
下邊有提到了,咱們講存儲結構不光是一個簡單的一個數據庫這樣一個簡單的概念,它其實在db到業務之間還會有一些叫作約定也好,規範也好,或者下降複雜度也好,由於你直接讓業務去處理它是很差的,因此咱們有存儲的邏輯,這樣邏輯層作一些基本的邏輯。
這裏跟你們分享一下:瓜子它APP的地位還不夠高,我第一次用的時候一點它要登錄,所以咱們要作一些匿名的策略,咱們但願匿名的狀態下你已經能創建溝通了,若是你以爲能夠咱們再接着聊,賣車也好,買車也好,因此說匿名就對咱們這個業務帶來一個挑戰,匿名的時候,咱們可能給他分了一個ID,他聊着聊着以爲能夠了,它就登陸了,登陸了以後,他實名的時候,他實名有多是新建立的一個,也可能他以前就登過,可是因爲忘了,或者是時間久了過時了,這個時候他在這一次的業務過程中,他就兩個ID,若是一直讓它成爲兩個ID其實對後邊的電銷人員是很鬱悶的,說咱們開始跟我聊了一下,過會變了我的實際上是一我的,前面的業務也中斷了,因此說咱們對這個消息層面咱們就進行了一個Merge,這個咱們並無說你實名,咱們就把你的數據給搬家,按照這個實名的就是匿名有一個時間序列,實名是否是也有一個,咱們並無這麼搞,咱們仍是兩個,而是在存儲中間的一個層次進行拉取的過程,在須要Merge的時候,咱們在存儲邏輯上給他Merge。
可是匿名到實名遠沒有簡單,只是一個延伸,事實上你這個消息裏面的匿名很好作,可是你的業務匿名到實名很難,還有咱們常常遇到這個問題,機器人給他發了一個東西,匿名狀態,後來他登錄了,他一打開,拉回去了以後,這個消息還在他那裏,他變成實名了。他進行操做,這個時候業務的匿名到實名實際上是更難的,若是有作這樣想法的,提早想好,更多的是業務層面的理論都是。
這個是消息的最後一部分了,實際上還會遇到一些問題,事實上消息同步是很是複雜的一個事情,咱們後來越作越以爲它複雜。這個有些人會出現一個什麼狀況,好比說我用A手機收了幾條消息,我在B手機上又收了幾條消息,過一段時間我在A手機上又來收幾條消息。
你看剛纔那種模型就會致使中間出現不少斷層,中間出現了不少,就像Client右邊這個圖,就345的消息他並無收到,可是服務端實際上是全部消息都有的。這時候咱們作了一些策略,咱們爲每一個消息嚴格的編號,msg index:1,2,3,每一個消息嚴格的編號。若是是這樣子,客戶端知道了以後,他就知道我原來少了345這三個號對不對?我就能夠到服務端去說,我缺345這幾個消息,你給我解索出來。
有沒有這麼容易?客戶端可能以爲這個是很容易的,可是到了服務端事情不是這麼回事,345是你本身編的一個號,而咱們的消息以前說了snowflake這樣一個惟一的編號,那你拿着345並不能找到你究竟是哪一個消息ID,因此說我是否是服務端要用哪一個消息創建這麼一個索引,仍是應該是一個編號到msg ID索引?
能夠作,可是存儲量工做量很是的大,那咱們怎麼幹?
咱們在邏輯層裏邊作了一些事情,服務端每次返回客戶端的消息,咱們把這個消息把它作成一個鏈表的結構,當你來拉取,由於是反向消息好可能是吧?拉去2號消息的時候,我就說,這個我告訴你,你的下一條消息是msg7,你拉取,也能夠一段一段拉取不要緊。你拉到msg6的時候,我告訴你說你的消息msg5,我客戶端說原來少這個消息5,這樣子客戶端能夠經過這個消息ID到服務端來檢索消息,因爲是消息ID無論咱們是OK也好,或者咱們的關係型數據庫是基於索引也好,都很容易作,現成的,也不須要再維護其餘的索引關係。因此說這也是一個策略的點。這種斷層咱們就解決了。
可是還有問題,有時候消息很是多,若是你一次都把這些就是咱們剛纔說的同步庫的消息收過去,過程實際上是很慢的,尤爲在深度用戶的時候這個方案很差。
有一種作法:你把這個消息壓縮一下送過去,簡直傳遞。可是其實仍是很差,客戶端要渲染,要計算數量很慢,這就是剛纔提到的擴散讀和擴散寫的問題,最先有一個,因此說後來更好的辦法是說同步庫裏邊也並非去同步的具體的消息,你能夠去作這個用戶有哪些變動的會話,這麼一個會話,它有多少未接收的數據,記錄好這個數字有多少未讀。這樣客戶端能夠把這些數據拉到本地,你看到了有多少未讀的會話以後,你點進去的時候,你再照這個存儲庫裏邊反向的經過這個來拉取你的消息,再加上咱們剛纔說的中間空蕩的一個補齊的策略,一個列表補齊的策略,這樣的體驗很是好。
因此說咱們就解決了咱們這個項目,咱們就解決了消息的問題。後邊咱們看一下消息解決了尚未完,咱們要推廣這個項目,咱們要落地,須要作業務,由於只是傳一個消息沒有意義,對觀衆來講咱們就在作業務了,咱們承載業務的就是叫咱們的業務卡片,最初那個圖裏邊咱們看到的那些傳過去能夠直接操做的這個東西,應該咱們還申請了專利,當時去查了一下沒人這麼搞的,可是因爲沒人這麼搞,其實咱們在實施過程當中遇到一些問題,下面咱們看一下這個圖。
這個圖右邊有一個卡片的代理,右圖有個綠色的卡片代理是咱們對這個業務設置的一個特殊的東西,咱們在推廣的時候遇到不少問題,咱們這些業務原有的業務部門,他們都作得有接口是現成的,因爲你把它搬到了IM交互裏面有幾種方法:第一大家所有給我改一下,這個是很困難,有些業務說他不肯意,咱們就設計了這麼一個代理的產出的卡片的全部響應試點,先打到咱們的一個代理模塊,代理模塊再去適配你原有的業務邏輯,這樣子代理模塊,知道用戶操做行爲究竟是什麼樣,成沒成功,成功了或者沒成功,他再經過調度,經過他們的通道通知相關業務的各方結果,這是一種策略。
同時它要高可靠,好比說咱們預定看車就至關於下班了,就至關於這個是很重要的業務。你這個不行,鏈條太長了,風險過高,寧肯咱們加點東西均可以,那就是左邊這個邏輯,這業務服務願意說我本身改一下,可控性我得本身把控,不能說由於通信有問題,個人業務就不跑了。那就是他改一下,來觸發調度的一些邏輯。這個是咱們在整個推廣的過程中最重要的一個策略,實際上也是探索出來的,由於不光是一個技術問題,仍是個組織結構問題。
簡單提一下調度問題,由於不是重點,你怎麼知道究竟是哪一個客戶來服務你?咱們提出了一個場景的概念,就是你每次從各類入口進到對話界面的時候,這些入口咱們是有狀態的。A入口B入口,好比你約車仍是砍價什麼之類的,咱們它先把這個場景到調度去註冊一下,說我從這兒進來,同時調度會有一些大數據來支撐,原來你是誰誰,你就從這個場景進來,咱們認爲你多是要幹什麼事情。這樣給他返回場景裏,他再次跟通道間發生關係的時候,帶上這個場景,咱們這個調度就知道把你推給具體的誰,是銷售也好,機器人也好,是咱們具體的解決投訴的客服或者解決什麼電銷的客服了。
接下來再提一下分析統計,像瓜子的規模已經比較大了,咱們如今有超過一千個研發團隊,可是在大數據這塊投入也比較多,可是事實上如今公司都是數據驅動化,這個團隊的力量依然是頗有限的,它支持各類各樣的業務線,很是吃力的仍是很忙,因此咱們在分析統計有兩塊,第一塊是T+1的分析是離線的,還有一塊是實時的一個分析。
咱們這個項目於對實時統計的要求很高,好比及時回覆率這些各類各樣的統計要求實時的監控報警,怎麼作呢?這是咱們整個系統另外一個角度的一個架構,和咱們一些跟消息相關的或跟一些業務調度相關的,咱們都走了一個kafka,就是通道的以及送給客服的一些銷售評估師等等,他們業務線的這些邏輯,咱們經過kafka傳遞一些比較多的數據。咱們在想能不能用借用kafka來簡單的實現技術,事實上是能夠,這概念是一個流式數據庫,咱們最初的結構就是圖左邊這一塊,整個系統中間走的消息都經過了一個kafka.
咱們能夠保證業務在上面跑,其次kafka它緩存的這段數據,咱們是能夠對他進行流式計算,咱們總體的架構是上圖這樣。
我簡單重複一下咱們的過程:
第一:咱們這個系統經過數據層面展現了一個通信,就是即時通信的這樣一個系統,大概是怎麼作的,數據庫怎麼存的;
第二:是把咱們通信的能力應用到業務系統,咱們解決了技術上或者組織上遇到了一些什麼困難;
第三:是咱們找一個比較簡單的方法,處理咱們的一些離線計算,固然他作T+1也是能夠的。
謝謝你們。
《一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)》
《IM開發基礎知識補課(二):如何設計大量圖片文件的服務端存儲架構?》
《IM開發基礎知識補課(三):快速理解服務端數據庫讀寫分離原理及實踐建議》
《IM開發基礎知識補課(四):正確理解HTTP短鏈接中的Cookie、Session和Token》
《WhatsApp技術實踐分享:32人工程團隊創造的技術神話》
《王者榮耀2億用戶量的背後:產品定位、技術架構、網絡方案等》
《IM系統的MQ消息中間件選型:Kafka仍是RabbitMQ?》
《騰訊資深架構師乾貨總結:一文讀懂大型分佈式系統設計的方方面面》
《子彈短信光鮮的背後:網易雲信首席架構師分享億級IM平臺的技術實踐》
《知乎技術分享:從單機到2000萬QPS併發的Redis高性能緩存實踐之路》
《IM開發基礎知識補課(五):通俗易懂,正確理解並用好MQ消息隊列》
《微信技術分享:微信的海量IM聊天消息序列號生成實踐(算法原理篇)》
《微信技術分享:微信的海量IM聊天消息序列號生成實踐(容災方案篇)》
《新手入門:零基礎理解大型分佈式架構的演進歷史、技術原理、最佳實踐》
《一套高可用、易伸縮、高併發的IM羣聊、單聊架構方案設計實踐》
《阿里技術分享:阿里自研金融級數據庫OceanBase的艱辛成長之路》
《社交軟件紅包技術解密(一):全面解密QQ紅包技術方案——架構、技術實現等》
《社交軟件紅包技術解密(二):解密微信搖一搖紅包從0到1的技術演進》
《社交軟件紅包技術解密(三):微信搖一搖紅包雨背後的技術細節》
《社交軟件紅包技術解密(四):微信紅包系統是如何應對高併發的》
《社交軟件紅包技術解密(五):微信紅包系統是如何實現高可用性的》
《社交軟件紅包技術解密(六):微信紅包系統的存儲層架構演進實踐》
《社交軟件紅包技術解密(七):支付寶紅包的海量高併發技術實踐》
《社交軟件紅包技術解密(九):談談手Q紅包的功能邏輯、容災、運維、架構等》
《即時通信新手入門:一文讀懂什麼是Nginx?它可否實現IM的負載均衡?》
《即時通信新手入門:快速理解RPC技術——基本概念、原理和用途》
《多維度對比5款主流分佈式MQ消息隊列,媽媽不再擔憂個人技術選型了》
《從游擊隊到正規軍(一):馬蜂窩旅遊網的IM系統架構演進之路》
《從游擊隊到正規軍(二):馬蜂窩旅遊網的IM客戶端架構演進和實踐總結》
《IM開發基礎知識補課(六):數據庫用NoSQL仍是SQL?讀這篇就夠了!》
《瓜子IM智能客服系統的數據架構設計(整理自現場演講,有配套PPT)》
>> 更多同類文章 ……
(本文同步發佈於:www.52im.net/thread-2807…)