瓜子二手車封宇:瓜子IM智慧客服系統資料架構設計

伺服器頻道發表於2018-11-26

  本文根據封宇在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也是可以的,謝謝大家。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31545803/viewspace-2221565/,如需轉載,請註明出處,否則將追究法律責任。

相關文章