您好,登錄后才能下訂單哦!
本文根據封宇在2018年10月18日【第十屆中國系統架構師大會(SACC2018)】現場演講內容整理而成。
講師介紹:
封宇,瓜子二手車高級技術專家,中國計算機學會專業會員。2017年2月入職瓜子二手車,主要負責瓜子即時消息解決方案及相關系統研發工作。在瓜子期間,主持自研消息系統用于支持瓜子內效工具呱呱,滿足瓜子兩萬多員工移動辦公需求;作為項目經理,負責瓜子服務在線化項目,該項目對瓜子二手車交易模式及流程帶來深遠影響。
在入職瓜子二手車之前,封宇曾供職于58同城、58到家、華北計算技術研究所,參與到家消息系統、58爬蟲系統以及多個國家級軍工科研項目的架構及研發工作。在消息系統、后端架構、存儲架構等方面有豐富經驗。
本文摘要:
瓜子業務重線下,用戶網上看車、預約到店、成交等許多環節都發生在線下。瓜子IM智能客服系統的目的是要把這些線下的活動搬到線上,對線下行為進行追溯,積累相關數據。系統連接用戶、客服、電銷、銷售、AI機器人、業務后臺等多個角色及應用,覆蓋網上咨詢、瀏覽、預約看車、到店體驗、后服、投訴等眾多環節,各個角色間通過可直接操作的卡片傳遞業務。
例如,用戶有買車意向時,電銷或AI機器人會及時給用戶推送預約看車的卡片,用戶只需選擇時間即可完成預約操作。整個系統邏輯復雜,及時性、可靠性要求高,涉及IM消息、業務卡片、各種實時統計。此次演講,從數據架構層面講解系統遇到的挑戰及解決辦法。
分享大綱:
一、項目背景
二、系統架構
三、存儲架構
演講正文:
今天分享的題目是“瓜子IM智能客服數據架構設計”,這個系統和旺旺比較像。簡單說一下分享的幾大部分內容:第一部分項目背景,項目背景需要稍微講一下,有助于大家理解;第二部分是系統架構,為什么要簡單講一下系統架構呢?因為不講業務的架構都是耍流氓,所以說我們講存儲,都要知道系統是怎么回事;第三部分重點講一下存儲,在這塊我們講分享一下我們的實踐經驗,以及演進過程,更多的是采到的一些坑,我們怎么解決的。
項目背景
比較熟悉的都知道瓜子二手車沒有中間商賺差價,其實瓜子在中間要做很多事情。首先二手車很難,它不是一個標品,我們要去收車,收車都是在社會上去收,通過網站收,你要驗車。我們要把它放到我們的網站上,有些車要收到我們的場地,有些車可能是在用戶家里樓下停著。如果一個用戶來買車,在網上點了之后,你可能下單要去看這個車,我們的銷售要去跟到線下去陪你看車選車試駕,還有一系列的復檢等等,有很多的事情。
現在瓜子這塊我們做的還不夠好,為什么不夠好?我們站在瓜子內部的角度來看這個事情,有很多的行為發生在線下,我們很難防就會帶來很多問題。比如飛單,有些去跟別的企業串,這個車就沒有在平臺上賣,這些問題都很難解決。第二個就是銷售到底跟用戶在做什么,他沒準罵那些用戶或者什么的,回頭企業發現的投訴很難查。那我們這個項目的一個重要的問題,就要解決一個線上化,就是把這些線下的行為搬到線上,我們利用通訊,在IM過程當中傳遞一些業務,這樣把整個的一些業務線上化固化下來。
第二個是電商化,我比較喜歡用京東,我覺得他的物流和客服都很好,瓜子也是希望做成電商,還有一個就降本增效快一點,如果現在你用瓜子,你會發現你到網上去瀏覽,就會有電銷人員給你打電話,這個是很煩的,對用戶的體驗很不好,再一個對瓜子來說成本也很高,我們也養了很多的電銷,所以說我們就啟動了這樣一個項目來解決這些問題。
剛才提到了很復雜,整個系統串聯了很多角色,有用戶、銷售、電銷、評估師,還有AI和機器人。做系統要善于抽象,我們先有一個基于通訊的即時通訊系統。第二我們是要把通訊系統集成到業務,或者叫把這些業務搬到通訊系統上面來,核心就是這樣子。
這是截了幾個圖,我們看第一個圖,我們上邊就有一個車,下邊有個預約,用戶事實上是可以直接在聊天界面里面去點預約了。這個就是我說的跟一般的客服不一樣的,它可以做一些叫做導購或者叫營銷也好,直接通過這樣一個途徑,因為瓜子的獲客成本很高,每個訪問瓜子的用戶我們都希望及時跟他溝通,這個圖有助于大家理解。
系統架構
整個系統不是一個單一的系統,它結合了一些業務,我們可以把它拆分成這么幾個層次。最上面是一個端,那端首先就是瓜子的APP,當然還有毛豆的,現在瓜子的業務有好幾個,除此之外有些員工用的,比如說電銷的、客服的、售后的、金融的、我們可能都是一些APP或者是一些桌面系統,這是我們的客戶端。
第二是一個路由層,我們要打通這些業務。要讓這些業務在這個系統里邊及時的傳遞,所謂傳遞剛才前面有個圖案,比如說你想約車了,那客服就給你或者電銷就給你發一個約車的卡片,你就可以直接選時間約,這是傳遞業務。再往下邊是一些業務層,那就是原來瓜子有很多有什么業務就涉及什么業務,最底下是一個存儲。這次后邊主要講的就是站在存儲層的角度來看整個系統,重點會講存儲層怎么對路由層進行支持。
存儲架構
存儲這塊會講大概幾個點,包括數據庫的拆分,消息怎么存,消息里邊也會特別提一下群的模型,大規模的群是比較麻煩的。還有一些存儲邏輯以及業務怎么在上邊run起來。最后是統計分析,實時計算這樣一些設備。
這個圖是現在的一個數據庫的圖,我們看著這些就是數據庫已經分得很好了,比如通訊的數據庫,有調度的有卡片有分析。我在這里介紹一下調度這個新出現的名詞,它是干嘛?就是你在這個系統里邊點開一個車也好,點開的人也好,聊天時用戶看到的是一個瓜子的客服,后邊瓜子內部實際上是一個瓜子的客戶團隊非常大的團隊來支持你,所以說到底你跟哪個客服聊天,是有一些策略的。我們感覺這樣一個拆分實際上是順理成章的,但是事實上根本就不是。我舉個例子,2015年大概是京東內部有一個分享,劉強東分享流露出來了,說有一個二手車企業一年還是一個月,我忘了,賣了兩輛車出去,估值就到了2億美金。簡直不敢相信。
我分享這個例子并不是說話有什么不對,當然我相信他也不是說的瓜子,因為瓜子A輪它不止這個數,我想說最初企業是很小的,業務量是很小的,我們根本就不可能是這樣一個數據庫的結構,就跟沈老師說的一樣,其實它就是一個庫也沒有什么IM,沒有什么調度,這些卡片可能就是一個賣車的一個數據庫。
我是去年的2月份進入瓜子的,快兩年了,那時候瓜子的業務量非常小,具體我也不知道,就是一個數據庫,一個數據庫其實是非常好的,因為很多人來了之后就說要拆庫,一個數據庫的好處是寫業務很快,十幾個人快速的就把系統就搭起來了。我們事實上庫拆成這么幾個也經歷了一個過程,最初是做IM只有一個IM的庫,后來有了調度加了一個庫,再后來有什么卡片,有分析逐步得往外擴。這個庫它其實是有一定的成本,如果你拆分得不好,你會去做很多接口,比如說你像關聯查一下,發現不是我團隊的庫也要做各種各樣接口,產生了分布式的事物的一致性的問題,都產生了。所以說數據庫的拆分,尤其垂直拆分,實際上是隨著你不同的階段,你選擇不同的拆分方式,以后隨著系統的擴大瓜子業務擴大這個系統它會拆的更多數據庫,但拆的更多,對你的運維監控這些團隊的挑戰都會帶來一些成本,也會帶來一些挑戰。我們現在把它拆成了這樣一個庫,各司其職。
下面就重點說一下消息,這塊我們怎么存?我們看一下左邊這個圖,左邊這個圖是一般來說很容易理解的消息,怎么存的方式?以前桌面系統經常這么干,比如說A要給B發一個消息,他怎么發?他就說A用戶端,A這個端我發一個消息,如果B在線,我就把消息直接發給他,他給我一個確認,這個過程就存儲好就結束了。其實我服務當中不需要存這個消息,如果是A發給一個C這個C不在線怎么辦?我們也有策略,A把這個消息發給了C,C如果沒有確認說我收到這個消息,我就把這個下邊的第二步,我就把它存到一個離線的數據庫里邊等著你C什么時候上線,你就把這個消息拉回去,這個過程就完結了,這個消息我就給送到了,所以說這個時候的存儲非常簡單,我就一個離線庫,存一下某個人的消息就好了。
但是這種模式其實是有很多問題的,真正使用的時候,很多產品現在是移動端的手機端,網絡首先是不穩定,長期處于一個C的狀態,如果你都去監控它的狀態,送沒送到,再存儲性能會很差。
第二個現在的端有很多,有桌面的,有手機的,有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生成的時間,給他一個最后八位的一個遮罩,他在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是你自己編的一個號,而我們的消息之前說了snow?ake這樣一個唯一的編號,那你拿著345并不能找到你到底是哪個消息ID,所以說我是不是服務端要用哪個消息建立這么一個索引,還是應該是一個編號到msg ID索引?可以做,但是存儲量工作量非常的大,那我們怎么干?我們在邏輯層里邊做了一些事情,服務端每次返回客戶端的消息,我們把這個消息把它做成一個鏈表的結構,當你來拉取,因為是反向消息好多是吧?拉去2號消息的時候,我就說,這個我告訴你,你的下一條消息是msg7,你拉取,也可以一段一段拉取沒關系。你拉到msg6的時候,我告訴你說你的消息msg5,我客戶端說原來少這個消息5,這樣子客戶端可以通過這個消息ID到服務端來檢索消息,由于是消息ID不管我們是OK也好,或者我們的關系型數據庫是基于索引也好,都很容易做,現成的,也不需要再維護其他的索引關系。所以說這也是一個策略的點。這種斷層我們就解決了。
但是還有問題,有時候消息非常多,如果你一次都把這些就是我們剛才說的同步庫的消息收過去,過程其實是很慢的,尤其在深度用戶的時候這個方案不好。有一種做法,你把這個消息壓縮一下送過去,簡直傳遞。但是其實還是不好,客戶端要渲染,要計算數量很慢,這就是剛才提到的擴散讀和擴散寫的問題,最早有一個,所以說后來更好的辦法是說同步庫里邊也并不是去同步的具體的消息,你可以去做這個用戶有哪些變更的會話,這么一個會話,它有多少未接收的數據,記錄好這個數字有多少未讀。這樣客戶端可以把這些數據拉到本地,你看到了有多少未讀的會話之后,你點進去的時候,你再照這個存儲庫里邊反向的通過這個來拉取你的消息,再加上我們剛才說的中間空蕩的一個補齊的策略,一個列表補齊的策略,這樣的體驗非常好。
所以說我們就解決了我們這個項目,我們就解決了消息的問題。后邊我們看一下消息解決了還沒有完,我們要推廣這個項目,我們要落地,需要做業務,因為只是傳一個消息沒有意義,對觀眾來說我們就在做業務了,我們承載業務的就是叫我們的業務卡片,最初那個圖里邊我們看到的那些傳過去可以直接操作的這個東西,應該我們還申請了專利,當時去查了一下沒人這么搞的,但是由于沒人這么搞,其實我們在實施過程中遇到一些問題,下面我們看一下這個圖。
這個圖右邊有一個卡片的代理,右圖有個綠色的卡片代理是我們對這個業務設置的一個特殊的東西,我們在推廣的時候遇到很多問題,我們這些業務原有的業務部門,他們都做得有接口是現成的,由于你把它搬到了IM交互里面有幾種方法:第一你們全部給我改一下,這個是很困難,有些業務說他不愿意,我們就設計了這么一個代理的產出的卡片的所有響應試點,先打到我們的一個代理模塊,代理模塊再去適配你原有的業務邏輯,這樣子代理模塊,知道用戶操作行為到底是什么樣,成沒成功,成功了或者沒成功,他再通過調度,通過他們的通道通知相關業務的各方結果,這是一種策略。
同時它要高可靠,比如說我們預約看車就相當于下班了,就相當于這個是很重要的業務。你這個不行,鏈條太長了,風險太高,寧可我們加點東西都可以,那就是左邊這個邏輯,這業務服務愿意說我自己改一下,可控性我得自己把控,不能說因為通訊有問題,我的業務就不跑了。那就是他改一下,來觸發調度的一些邏輯。這個是我們在整個推廣的過程當中最重要的一個策略,實際上也是探索出來的,因為不光是一個技術問題,還是個組織結構問題。
簡單提一下調度問題,因為不是重點,你怎么知道到底是哪個客戶來服務你?我們提出了一個場景的概念,就是你每次從各種入口進到對話界面的時候,這些入口我們是有狀態的。A入口B入口,比如你約車還是砍價什么之類的,我們它先把這個場景到調度去注冊一下,說我從這兒進來,同時調度會有一些大數據來支撐,原來你是誰誰,你就從這個場景進來,我們認為你可能是要干什么事情。這樣給他返回場景里,他再次跟通道間發生關系的時候,帶上這個場景,我們這個調度就知道把你推給具體的誰,是銷售也好,機器人也好,是我們具體的解決投訴的客服或者解決什么電銷的客服了。
接下來再提一下分析統計,像瓜子的規模已經比較大了,我們現在有超過一千個研發團隊,但是在大數據這塊投入也比較多,但是事實上現在公司都是數據驅動化,這個團隊的力量依然是很有限的,它支持各種各樣的業務線,非常吃力的還是很忙,所以我們在分析統計有兩塊,第一塊是T+1的分析是離線的,還有一塊是實時的一個分析。
我們這個項目于對實時統計的要求很高,比如及時回復率這些各種各樣的統計要求實時的監控報警,怎么做呢?這是我們整個系統另一個角度的一個架構,和我們一些跟消息相關的或跟一些業務調度相關的,我們都走了一個kafka,就是通道的以及送給客服的一些銷售評估師等等,他們業務線的這些邏輯,我們通過kafka傳遞一些比較多的數據。我們在想能不能用借用kafka來簡單的實現技術,事實上是可以,這概念是一個流式數據庫,我們最初的結構就是圖左邊這一塊,整個系統中間走的消息都通過了一個kafka.
我們可以保證業務在上面跑,其次kafka它緩存的這段數據,我們是可以對他進行流式計算,我們整體的架構是上圖這樣。
最后我簡單重復一下:我們的過程,第一我們這個系統通過數據層面展示了一個通訊,就是即時通訊的這樣一個系統,大概是怎么做的,數據庫怎么存的;第二是把我們通訊的能力應用到業務系統,我們解決了技術上或者組織上遇到了一些什么困難;第三是我們找一個比較簡單的方法,處理我們的一些離線計算,當然他做T+1也是可以的,謝謝大家。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。