国产精品chinese,色综合天天综合精品网国产在线,成午夜免费视频在线观看,清纯女学生被强行糟蹋小说

    <td id="ojr13"><tr id="ojr13"><label id="ojr13"></label></tr></td>
        • <source id="ojr13"></source>
            <td id="ojr13"><ins id="ojr13"><label id="ojr13"></label></ins></td>

            當(dāng)前位置:文章中心>技術(shù)教程
            公告通知 新聞快遞 技術(shù)教程 產(chǎn)品展示

            消息復(fù)雜計(jì)算的抽象和簡(jiǎn)化

            發(fā)布時(shí)間:2022-01-10 點(diǎn)擊數(shù):882

            作者 | 四點(diǎn)

            文章來源 | 阿里巴巴淘系技術(shù)


            消息客戶端計(jì)算的復(fù)雜性


            在客戶端的設(shè)計(jì)中,一般的分層會(huì)至少包含下層的數(shù)據(jù)服務(wù)層和上層的UI層,下層的數(shù)據(jù)模型主要由所在領(lǐng)域決定,相對(duì)獨(dú)立、穩(wěn)定,而UI則更多變,且會(huì)對(duì)多種數(shù)據(jù)進(jìn)行組合。由于UI的相對(duì)多變性與模型的相對(duì)穩(wěn)定性,在數(shù)據(jù)層和UI之間,就需要對(duì)數(shù)據(jù)進(jìn)行若干的處理才能交給UI展示。比較簡(jiǎn)單的情況比如將原始數(shù)據(jù)的時(shí)間戳轉(zhuǎn)換為 PD 要求的字符串,如果涉及到對(duì)不同數(shù)據(jù)進(jìn)行關(guān)聯(lián)、分頁(yè)加載、變更計(jì)算,這部分?jǐn)?shù)據(jù)處理邏輯就會(huì)比較復(fù)雜。


            消息作為富客戶端,這部分邏輯非常復(fù)雜,加上狀態(tài)的存在,可以說是消息客戶端中最復(fù)雜的邏輯之一,這種復(fù)雜主要體現(xiàn)在這些維度:


            1.本地部分?jǐn)?shù)據(jù):客戶端只存部分消息數(shù)據(jù),獲取數(shù)據(jù)時(shí)本地?cái)?shù)據(jù)不全需要再異步請(qǐng)求服務(wù)端,還需要支持上層指定請(qǐng)求策略,這使得接口無法采用 request-response 的形式,必須使用流式接口,數(shù)據(jù)回調(diào)和結(jié)果回調(diào)的分離,以及多次數(shù)據(jù)回調(diào),增加了處理邏輯的復(fù)雜度;


            2.支持變更同步:除了主動(dòng)拉取,會(huì)話消息數(shù)據(jù)需要支持變更的推送,且對(duì)于所有的變更,需要保證數(shù)據(jù)(包括緩存和UI展示)的一致性;


            3.多個(gè)數(shù)據(jù)來源:由于歷史的原因,消息的同一種數(shù)據(jù)(比如會(huì)話)存在多個(gè)來源,因此需要請(qǐng)求多次,將多次數(shù)據(jù)回調(diào)合并,處理錯(cuò)誤,還要盡量保證加載速度。經(jīng)過眾多同學(xué)的努力,手淘和千牛中下掉了OpenIM、DT兩個(gè)數(shù)據(jù)源,今天用戶在手淘和千牛中看的會(huì)話和消息,依然有BC、CC、IMBA三個(gè)來源;


            4.多種數(shù)據(jù)聚合:UI展示需要把會(huì)話、消息、Profile(頭像昵稱)、群、群成員信息以及其他業(yè)務(wù)數(shù)據(jù)進(jìn)行聚合,把相關(guān)的多個(gè)不同數(shù)據(jù)按照不同的規(guī)則聚合在一起;


            5.支持分頁(yè)請(qǐng)求:總的數(shù)據(jù)量比較大,需要通過分頁(yè)機(jī)制加載,除了標(biāo)準(zhǔn)的分頁(yè)加載之外,還要支持定位到某條消息從中間開始加載,這就出現(xiàn)了雙向分頁(yè)加載,以及 進(jìn)入和退出 中間加載時(shí)的狀態(tài)轉(zhuǎn)換和異常處理;


            6.多條數(shù)據(jù)合并:由于業(yè)務(wù)的需要,消息之間存在更新和替換關(guān)系(比如同一條訂單的物流狀態(tài)更新),拉取的新數(shù)據(jù)要修改已存的消息狀態(tài)數(shù)據(jù),而非僅僅添加在頭部或尾部,新的消息會(huì)導(dǎo)致已有消息的更新以及在數(shù)據(jù)結(jié)構(gòu)中的位置變化;


            7.數(shù)據(jù)結(jié)構(gòu)復(fù)雜:消息存在列表、樹兩種UI形態(tài),對(duì)應(yīng)的狀態(tài)也有兩種形態(tài),對(duì)于這些數(shù)據(jù)結(jié)構(gòu)的變更計(jì)算邏輯比較復(fù)雜,對(duì)于樹來說,還需要支持虛擬節(jié)點(diǎn)計(jì)算和結(jié)構(gòu)動(dòng)態(tài)變化。


            這塊邏輯在消息客戶端涉及會(huì)話、消息、profile、群、群成員、關(guān)系等所有核心服務(wù)數(shù)據(jù)模型,總計(jì)大約25000行代碼,占消息總代碼的8%左右,是核心的數(shù)據(jù)處理。由于這些邏輯很容易耦合在一起,形成一些高維的邏輯,表現(xiàn)為大量的條件分支和遞歸嵌套,這種高維的邏輯很難寫,也很難維護(hù),并且占據(jù)了不少包大小,因此有必要對(duì)這些邏輯抽象和簡(jiǎn)化。


            目標(biāo)


            1.在不同的模型、不同的接口和相似的邏輯之上再建立一層抽象,統(tǒng)一客戶端的數(shù)據(jù)處理;


            2.將高維的數(shù)據(jù)處理邏輯簡(jiǎn)化為一個(gè)更加清晰的處理模型,代碼量下降60%;


            3.實(shí)現(xiàn)數(shù)據(jù)處理的雙端一致。


            消息數(shù)據(jù)處理過程分析


            通常會(huì)將客戶端劃分為數(shù)據(jù)服務(wù)層、邏輯層、UI層三層,這部分?jǐn)?shù)據(jù)獲取和計(jì)算會(huì)被歸到邏輯層。這里的問題在于,數(shù)據(jù)服務(wù)層對(duì)應(yīng)于領(lǐng)域定義,UI層對(duì)應(yīng)于渲染、動(dòng)畫和交互事件處理,這樣邏輯層很容易變成一個(gè)縫合怪,數(shù)據(jù)請(qǐng)求、數(shù)據(jù)轉(zhuǎn)換、上下文維護(hù)、異步處理、遞歸邏輯、狀態(tài)管理、變更同步,所有不屬于另外兩層的部分都會(huì)被扔到邏輯層,導(dǎo)致邏輯層的臃腫。


            下圖左側(cè)是這個(gè)處理過程的工作內(nèi)容和上下游,右側(cè)為數(shù)據(jù)拉取和變更處理的數(shù)據(jù)流向和計(jì)算過程:

            消息數(shù)據(jù)處理過程分析


            可以看到,將這部分?jǐn)?shù)據(jù)處理僅僅定義為邏輯是過于寬泛的,不利于針對(duì)性的優(yōu)化,因此有必要進(jìn)行深入的分析和研究。


            在對(duì)會(huì)話、消息、profile、群、群成員、關(guān)系6大核心數(shù)據(jù)處理鏈路進(jìn)行歸納、分解、分析和綜合之后,我們可以將數(shù)據(jù)處理過程簡(jiǎn)化為如下的過程:


            1.請(qǐng)求每個(gè)通道的 會(huì)話\消息 數(shù)據(jù),并將多次結(jié)果回調(diào)合并為一次結(jié)果回調(diào),處理多次數(shù)據(jù)回調(diào),請(qǐng)求會(huì)話\消息對(duì)應(yīng)的Profile、群、群成員、關(guān)系數(shù)據(jù)、業(yè)務(wù)數(shù)據(jù);


            2.建立會(huì)話\消息 和Profile、群、群成員、關(guān)系數(shù)據(jù)、業(yè)務(wù)數(shù)據(jù)之間的 關(guān)聯(lián)關(guān)系 ,生成聚合數(shù)據(jù),并處理聚合數(shù)據(jù)之間的依賴、優(yōu)先級(jí)和緩存一致性;


            3.將數(shù)據(jù)轉(zhuǎn)換為數(shù)組\樹形結(jié)構(gòu),支持請(qǐng)求來的數(shù)據(jù)與數(shù)據(jù)結(jié)構(gòu)中已存的數(shù)據(jù)進(jìn)行替換、更新合并計(jì)算,支持樹結(jié)構(gòu)和虛擬節(jié)點(diǎn)的動(dòng)態(tài)計(jì)算,支持UI局部更新;


            4.響應(yīng)各種數(shù)據(jù)的增刪改等變更事件,根據(jù)事件處理計(jì)算變更和結(jié)果,保證數(shù)據(jù)的一致性;


            5.在進(jìn)入和退出中間加載時(shí),處理各種數(shù)據(jù)緩存、關(guān)聯(lián)關(guān)系、加載信息的正確性;


            6.支持特殊邏輯,如實(shí)時(shí)新數(shù)據(jù)不按照時(shí)間排序,而是直接添加在頭部或尾部;


            7.中間每個(gè)邏輯的異常處理、超時(shí)機(jī)制、線程同步、上屏?xí)r間優(yōu)化、日志、監(jiān)控等邏輯。


            我們可以將這些邏輯分為兩類:


            作為計(jì)算的邏輯


            對(duì)應(yīng)于上面的過程2、3、4、5、6。


            如果我們將這塊邏輯看做黑盒,關(guān)心它的輸入輸出和功能,可以得出這塊邏輯的核心工作是將各種各樣的輸入數(shù)據(jù)轉(zhuǎn)換為特定的輸出數(shù)據(jù),這完美的對(duì)應(yīng)著計(jì)算的概念的結(jié)論,即:


            輸入數(shù)據(jù)轉(zhuǎn)換為特定的輸出數(shù)據(jù)

            基于計(jì)算的概念,可以將這段計(jì)算過程形式化地抽象為一個(gè)函數(shù) f,從而實(shí)現(xiàn)對(duì)狀態(tài)計(jì)算的抽象,上圖很直觀的體現(xiàn)了入?yún)檩斎牒彤?dāng)前狀態(tài),輸出為新的狀態(tài)和結(jié)果:


            f :: (Input, State1) -> (State2, Result)


            來分析一下函數(shù)f的入出參和形式:


            第一,這里的Input可以能是拉取回來的數(shù)據(jù),也可以是增刪改等數(shù)據(jù)變更,或者是消息已讀等明確的事件。這里我們可以通過定義插入、更新、刪除三個(gè)來統(tǒng)一所有的事件,因?yàn)樗械氖录壿嬌隙急囟梢晕ㄒ坏挠成涞竭@三個(gè)事件上(盡管實(shí)際上,由于部分服務(wù)不具備計(jì)算變更細(xì)節(jié)的能力,我們還支持了RemoveAll和Reload兩個(gè)事件)。


            第二,結(jié)合高階函數(shù),Input實(shí)際上已經(jīng)決定了這個(gè)函數(shù)的形式,即對(duì)于一個(gè)數(shù)據(jù)插入事件,其對(duì)應(yīng)的f必然為 \state -> insert someData into state 的形式,即 Input 已經(jīng)包含在 f 的實(shí)現(xiàn)中了,因此可以將函數(shù) f 進(jìn)一步簡(jiǎn)化為:


            f :: (State1) -> (State2, Result)


            其中 f 的形式由輸出的事件決定,這樣就得到了一個(gè)非常簡(jiǎn)化的函數(shù)抽象。


            第三,上面的分析還能得出一個(gè)推論,即事件和函數(shù)是等價(jià)的(可以互相轉(zhuǎn)換),這使得我們可以通過處理事件來實(shí)現(xiàn)對(duì)函數(shù)的處理,從而可以通過數(shù)據(jù)的處理來優(yōu)化計(jì)算的性能,可以看到,數(shù)據(jù)和過程邊界的打破賦予我們更強(qiáng)的能力。


            第四,對(duì)于 State 參數(shù),需要包含聚合后的數(shù)據(jù),因此需要處理數(shù)據(jù)的關(guān)聯(lián),一般的,我們可以將數(shù)據(jù)的關(guān)聯(lián)場(chǎng)景抽象為 一個(gè)主數(shù)據(jù)對(duì)應(yīng)多個(gè)附屬數(shù)據(jù)的形式,通過定義一個(gè) pair 函數(shù)來進(jìn)行關(guān)聯(lián)關(guān)系的判斷:


            pair :: (mainData, subData) -> Bool


            這樣就可以通過注入 pair 函數(shù)來實(shí)現(xiàn)主數(shù)據(jù)和附屬數(shù)據(jù)的關(guān)聯(lián),然后將有關(guān)聯(lián)關(guān)系的數(shù)據(jù)進(jìn)行聚合。


            第五,State還涉及數(shù)據(jù)\樹形結(jié)構(gòu)計(jì)算,這里在不同的場(chǎng)景是不一樣的,可以抽象為一個(gè) DataStructure ,定義增刪改查接口,然后在不同的場(chǎng)景使用不同的 DataStructure。


            作為結(jié)構(gòu)化數(shù)據(jù)獲取的邏輯


            對(duì)應(yīng)于上面的過程1、6。


            這部分邏輯的作用是會(huì)話消息Profile等數(shù)據(jù)的獲取和變更事件監(jiān)聽,由于6大服務(wù)的接口各不相同,之前的實(shí)現(xiàn)是一一對(duì)接。通過抽象之后,我們可以通過定義具備拉取接口和變更接口的Inject來實(shí)現(xiàn)這部分邏輯的抽象,這屬于標(biāo)準(zhǔn)操作,不再贅述。


            這部分?jǐn)?shù)據(jù)獲取的第二個(gè)特點(diǎn)是請(qǐng)求的平行分發(fā)和垂直組合,舉例來說,有多個(gè)通道決定了數(shù)據(jù)請(qǐng)求時(shí)需要平行的請(qǐng)求每個(gè)通道,每個(gè)通道的請(qǐng)求則根據(jù)不同的請(qǐng)求策略和每一步的回調(diào)數(shù)據(jù)決定下一次請(qǐng)求(這里與標(biāo)準(zhǔn)的 Future/Promise 的區(qū)別在于,F(xiàn)uture/Promise前后步驟的任務(wù)是不同的,后面的邏輯需要前面的數(shù)據(jù),這里前后步驟的邏輯相同,可能上一步請(qǐng)求本地,下一次請(qǐng)求遠(yuǎn)端,因此可以比 Future/Promise 更簡(jiǎn)化)。


            如果不進(jìn)行抽象,這里是一個(gè)至少三維的邏輯,即對(duì)于多個(gè)通道的多個(gè)步驟進(jìn)行主數(shù)據(jù)的獲取,然后對(duì)獲取的主數(shù)據(jù)再獲取附屬數(shù)據(jù),邏輯會(huì)寫的非常復(fù)雜。這里的關(guān)鍵在于每個(gè)通道的請(qǐng)求,每個(gè)步驟的請(qǐng)求都是非常相似的,主要是多次請(qǐng)求的結(jié)構(gòu)不同,并且數(shù)據(jù)請(qǐng)求的結(jié)構(gòu)由參數(shù)和數(shù)據(jù)決定,因此可以把它稱為結(jié)構(gòu)化數(shù)據(jù)獲取,即這里可以通過對(duì)請(qǐng)求結(jié)構(gòu)的抽象進(jìn)行簡(jiǎn)化。


            可以定義出結(jié)構(gòu)化數(shù)據(jù)獲取任務(wù)平行和垂直組合的核心函數(shù):


            dispatch :: [param] -> [task]
            compose :: strategy -> task


            其中 dispatch 函數(shù)對(duì)應(yīng)于Rx中的 flatMap,不過由于手淘iOS沒有集成 RxSwift 和 OpenCombine,官方的 Combine 框架要iOS13之上才能使用,因此只能自己實(shí)現(xiàn)一個(gè)輕量的。


            這樣通過 dispatch 和 compose 將任務(wù)進(jìn)行結(jié)構(gòu)化組合實(shí)現(xiàn)任務(wù)獲取的抽象和簡(jiǎn)化。


            技術(shù)方案


            核心技術(shù)方案

            MergeDispatcher核心技術(shù)方案

            核心模塊:


            1.MergeDispatcher : 實(shí)現(xiàn)數(shù)據(jù)獲取的結(jié)構(gòu)化,并將數(shù)據(jù)和變更統(tǒng)一為變更,處理所有的異常


            2.Calculator : 實(shí)現(xiàn)主數(shù)據(jù)和附屬數(shù)據(jù)的關(guān)聯(lián)和聚合,計(jì)算的多線程同步,變更上報(bào)


            3.DataStructure : 進(jìn)行主數(shù)據(jù)的結(jié)構(gòu)計(jì)算


            此外,Inject為計(jì)算提供請(qǐng)求接口和變更事件,為所有數(shù)據(jù)的注入點(diǎn),上層通過 ModelService 獲取計(jì)算后有聚合數(shù)據(jù)構(gòu)成的數(shù)據(jù)結(jié)構(gòu),以及變更事件。


            調(diào)用關(guān)系與數(shù)據(jù)流向


            ModelService

            ModelService會(huì)使用初始化DataStructure、CalCulator、主數(shù)據(jù)、附屬數(shù)據(jù)的Inject,并用來初始化MergeDispatcher


            1.當(dāng)UI需要數(shù)據(jù)時(shí),調(diào)用ModelService的load接口;


            2.ModelService直接調(diào)用MergeDispatcher的load接口;


            3.MergeDispatcher 平行調(diào)用主數(shù)據(jù)Inject的load接口,在每次回調(diào)主數(shù)據(jù)時(shí),調(diào)用附屬數(shù)據(jù)Inject的load接口請(qǐng)求附屬數(shù)據(jù),根據(jù)場(chǎng)景執(zhí)行對(duì)應(yīng)的超時(shí)邏輯,將主數(shù)據(jù)和附屬數(shù)據(jù)給到Calculator進(jìn)行計(jì)算,超時(shí)后的數(shù)據(jù)也繼續(xù)給到Calculator進(jìn)行計(jì)算;


            4.Calculator 執(zhí)行計(jì)算的多線程同步,更新主數(shù)據(jù)、附屬數(shù)據(jù)、關(guān)聯(lián)關(guān)系的緩存,生成聚合數(shù)據(jù),并將主數(shù)據(jù)給到DataStructure計(jì)算結(jié)構(gòu),然后將返回的全量和變更進(jìn)行上報(bào);


            5.數(shù)據(jù)結(jié)構(gòu)接收數(shù)據(jù)后,對(duì)當(dāng)前狀態(tài)(數(shù)據(jù)結(jié)構(gòu))執(zhí)行增刪改操作,并返回對(duì)應(yīng)的新狀態(tài)和變更數(shù)組。


            技術(shù)效果


            最終,我們實(shí)現(xiàn)了計(jì)算和數(shù)據(jù)獲取的分離,計(jì)算過程全部在Calculator,數(shù)據(jù)獲取主要在MergeDispatcher,兩部分獨(dú)立實(shí)現(xiàn),不再耦合,將邏輯層次從原來的模型數(shù)量 * 接口數(shù)量 * 數(shù)據(jù)結(jié)構(gòu) 降為 事件數(shù)量 * 數(shù)據(jù)結(jié)構(gòu),處理模型非常清晰,且適用于任意模型。


            針對(duì)計(jì)算過程,邏輯上抽象出一個(gè)高階計(jì)算函數(shù) f :: (State1) -> (State2, Result),這個(gè)函數(shù)形式上非常簡(jiǎn)單,卻緊緊抓住這種復(fù)雜狀態(tài)計(jì)算的本質(zhì),讓我們得以統(tǒng)一計(jì)算過程,整個(gè)計(jì)算過程的正確性有完備的理論基礎(chǔ),后續(xù)新增模型不會(huì)增加計(jì)算邏輯。


            針對(duì)數(shù)據(jù)獲取,我們將數(shù)據(jù)類型化為主數(shù)據(jù)和附屬數(shù)據(jù),并針對(duì)由請(qǐng)求的結(jié)構(gòu)進(jìn)行抽象,實(shí)現(xiàn)了所有的數(shù)據(jù)獲取的統(tǒng)一和簡(jiǎn)化。