主頁 > 軟體設計 > 架構詳解——淘系圈品進化史

架構詳解——淘系圈品進化史

2020-12-11 12:12:02 軟體設計

引言


商品資料是營銷的基礎,很多營銷工具最終都會涉及到商品資料的處理,比如打標、修改商品的feature、呼叫各種下游系統,單個商品可以通過同步方式處理,實際業務上會依據一定業務規則圈定大量商品并對其進行處理,因此,卡券商品設定引擎應運而生,

卡券商品設定引擎(俗稱圈品)的作用是,依據一定的業務規則從資料源獲取商品,篩選符合規則的商品并按照業務自定義的操作設定商品優惠,設定商品優惠主要是圍繞商品中心、營銷中心等多個域進行操作,圈品的一個重要能力就是保障商品優惠設定后各個域的資料一致性,商品資料經常發生變化,變化后可能會使商品不符合圈品規則,圈品另外一個重要能力就是能夠監聽全量的商品中心變更,卡券商品設定引擎全域視角圖如下所示,

圈品三個關鍵要素:資料源、規則、業務處理,三要素都支持橫向擴展,資料源是圈品的資料來源,不同的資料源接入方式和查詢方式不同,規則用于資料過濾,只有符合規則的資料才能接下去處理,符合規則的資料在業務上需要進行一定的處理,業務處理可以自定義,

從2017年發展至今,圈品經歷了4個雙11以及數不清的大促和日常活動,圈品目前擁有千萬級商品實時處理能力、資料一致性保障能力、監聽全量商品變更能力以及平臺化能力等,

本文將圈品的發展劃分為兩個階段,第一個階段,奠基了圈品的架構,第二階段,提升了系統的穩定性和性能、增加了一致性保障能力,



第一階段



? 概述


生命周期

圈品通過活動概念來進行生命周期的管理,圈品池關聯了規則和業務,圈品池詳情是商品的集合,商品處理完成后會保存到商品池詳情中,活動、圈品池、圈品池詳情模型如圖2.1所示,

一個活動可以關聯多個圈品池,一個圈品池只屬于一個活動,圈品池設定圈品規則后會按照業務自定義的動作完成商品的處理,活動程序中商品發生變化時會產生商品變更訊息,圈品會通過監聽商品變更訊息動態處理商品,活動結束后會觸發結束后的動作,生命周期如圖2.2所示,

首次設定圈品池規則后會觸發圈品從資料源拉取全量商品進行處理,我們稱之為全量圈品,在全量圈品完成后,資料源會發生變化或者商品資訊發生變化,這些發生變化的商品需要重新經過圈品處理,稱之為增量圈品,

圖2.1 卡券商品設定引擎模型圖

?

圖2.2 卡券商品?p?設定引擎生命周期

?


系統架構

圈品框架圖如圖2.3所示,圈品可以劃分為四個模塊,分別是資料源模塊、動作模塊、規則模塊和業務處理模塊,設定端劃分為三個部分:活動設定、圈品池設定、規則設定,接下來將圍繞圈品四個核心模塊進行講解,

圖2.3 圈品框架圖


? 資料源模塊


資料源模塊是圈品的資料源來源,下面主要分四類進行講解,分別是商品串列、商家串列、同步庫表以及商品變更訊息,

這些資料源又衍生出了多種圈品方式,比如商品串列圈品方式、賣家串列圈品方式、大促現貨圈品方式、營銷站點圈品方式、賣家大促商品圈品方式、飛豬賣家圈品方式、新零售攤位圈品方式等等,拿大促現貨圈品方式舉例,每次大促營銷平臺招商都要招現貨商品,大促現貨圈品池方式可以圈指定大促的全部現貨商品,也可以結合類目、商品標等其他規則過濾商品,


商品串列

商品串列是最簡單的資料源,直接通過商品ID指定資料源的商品范圍,因為是直接填商品ID的方式,網路傳輸限制最大支持10W商品,全量圈品程序中從圈品規則中獲取全量商品進行處理,增量圈品是通過監聽商品變更訊息進行處理,


賣家串列

賣家串列資料源是賣家ID的集合,通過指定賣家ID來確定商品范圍,全量圈品的程序中根據賣家ID從店鋪搜索介面中獲取賣家商品,增量圈品是通過監聽變更訊息,賣家新發布商品或者變更商品都會觸發商品變更訊息,通過監聽商品變更訊息便可以進行增量圈品,


同步庫表

同步庫表是值使用精衛同步原資料庫到新的資料庫供圈品使用,采用這種方式比較靈活且不會對原資料源產生影響,全量圈品是通過掃表方式獲取全量的資料,增量圈品有兩個渠道,第一是通過精衛監聽資料庫的變更,第二是監聽商品變更訊息,

根據資料源的特性又可以衍生出多種圈品方式,營銷平臺招商資料源支持大促現貨圈品方式、營銷平臺站點方式、賣家大促商品圈品方式等,新零售商品資料源支持攤位和業務身份圈品方式,


商品變更訊息

由于商品資訊變更會導致商品不符合規則,需要對變更的商品進行增加或洗掉,比如小二設定圈選某個類目的商品,賣家可以對商品類目進行編輯,原來符合類目規則的商品變得不符合類目需要洗掉,原來不符合類目的商品現在符合類目需要進行增加,商品資訊的變更都會觸發商品變更訊息,所以增量圈品中都有一種途徑就是處理商品變更訊息,

商品變更訊息日常平均qps在1w左右,峰值QPS可達4w多,在這一階段,因為每一個圈品池規則都是獨立的且無法確定一個商品與商品池的關系,所以每個圈品池都處理了全量商品變更訊息,假設商品變更訊息QPS是1w,當前有效圈品池有5000個,那么圈品系統實際處理商品變更訊息QPS是5000W,因此只有進行本地計算的規則才能支持處理商品變更訊息,即使是這樣,圈品系統也嚴重消耗機器性能,曾經圈品系統有600多臺機器,CPU使用率也達到了60%以上,


? 規則模塊

框架設計

圈品規則模型類圖如圖2.4所示,ItemPoolRule是圈品池規則類,其中relationRuleList是圈選規則,exclusionRuleList是排除規則,一個商品必須符合圈選規則且沒有命中排除規則,這個商品才算符合圈品池的規則,RelationRule是具體規則內容,RuleHandler是規則處理介面,所有規則必須實作RuleHandler,如ItemTagRuleHandler——商品標規則handler、SellerRuleHandler——賣家規則handler等,

圖2.4 規則模型類圖


規則樹

規則樹設計如圖2.5所示,每個節點表示一個規則節點,頂級規則必須是可以做為資料源的規則,如商品串列規則、賣家串列規則等,判斷商品是否符合規則可以定義為:一個商品如果符合從頂級規則到某個葉子鏈路上所有規則節點(即從規則樹中可以找到一條從頂級規則通往任意葉子節點的鏈路),則認為該商品符合規則,

圖2.5 規則樹設計

為了更好的理解,舉個例子,如下圖2.6所示,運營通過商品串列方式進行圈品,左邊鏈路是圈商品串列中符合二級類目規則的商品,右邊鏈路是圈商品串列中符合一級類目規則以及指定商品標的商品,

圖2.6 規則樹舉例


頂級規則

由于該章節與“分批處理模塊”章節耦合較強,因此可以先看下面章節后,再看該章節,

頂級規則即是規則也是資料源,圈品從頂級規則中獲取資料源中所有的商品,商品串列圈品方式做為頂級規則時,規則內容包含商品ID,這些商品ID就是資料源的商品,賣家串列圈品方式做為頂級規則時,規則內容包含賣家ID,從頂級規則獲取商品ID時,根據賣家ID呼叫店鋪搜索介面獲取商品ID,大促現貨圈品方式做為頂級規則時,規則內容包含的是大促現歡訓動ID,從頂級規則獲取商品ID時,根據活動ID從同步過來的招商現貨表中拉取商品,


★ 局限性

從“分批處理模塊”章節可以知道,這一階段圈品都是先count規則中包含商品總數,然后分頁處理,這種方式存在局限性,當頂級規則變得復雜的時候,就沒辦法處理了,

舉個稍微復雜的例子,賣家串列圈品方式規則內容包含很多個賣家時,如何處理呢?這一階段圈品的處理方式跟圖9縱向處理方式一樣,找到所有賣家中擁有最大商品數量做為count,然后分頁處理,每一頁的處理程序中都需要回圈所有賣家,當賣家數量越大時,每頁包含的商品數量就越大,因此該圈品方式限制了最多只能指定300個賣家,

再舉個復雜的例子,假設一個品牌團中有多個賣家,一個賣家有很多商品,現在需要圈選多個品牌團下面所有賣家的所有商品,如何做呢?這一階段圈品還無法處理這么復雜的規則,具體做法詳見第二階段,


? 分批處理模塊


分布式處理

圈品將全量商品進行分頁處理拆分成很多部分,然后通過metaq進行分布式處理,流程圖如圖2.7所示,

當觸發全量圈品的時候會產生一條記錄規則變化的metaq訊息,規則變化訊息通過規則變化動作模塊進行處理,規則變化動作模塊首先計算資料源中最大可能的商品數量,然后再通過分頁處理分成很多部分,每一部分產生一條商品增加型別的訊息,商品增加訊息通過商品增加動作模塊進行處理,商品增加動作模塊首先從資料源拉取該部分對應的商品ID集合,然后過濾圈品池規則,最后選擇對應的業務進行處理,

圖2.7 分布式處理流程圖


分頁處理

分頁處理首先是計算全部最大可能的商品數量,然后按照固定間隔進行分頁,商品增加和商品洗掉訊息包含的關鍵資訊是:start、end,

拿最簡單的商品串列圈品方式來舉例,假設運營填入了5w個商品ID,那么分頁處理可以是500個商品ID做為一頁,第一頁start=0、end=500,最后一頁start=49500,end=50000,每一頁需要處理的商品ID都是確定的,

但是資料源往往不是這么簡單,拿一個稍微復雜的大促現貨圈品方式舉例,從招商同步的現貨商品存盤在64張表中,按照商品ID進行分庫分表,其中大促活動ID是索引欄位,如何高效獲取指定大促活動的全部商品ID呢,資料量較小的時候,我們可以通過資料庫count和limit分批取出,資料量大的時候使用limit就會有大翻頁問題,

為了避免使用limit在大翻頁時性能差的問題,圈品的處理方式如下圖2.8所示,把它看成橫向方式,首先通過大促活動ID,計算每張表的min(id)、max(id),總數count就等于所有表max(id)-min(id)相加,然后按間隔劃分任務,實際間隔是5000,為了畫圖方便圖中間隔是10,因此每個商品增加訊息包含的資訊只需要start和end,

圖2.8 橫向分頁處理

在處理商品增加訊息時,需要回圈64張表中求min和max直到找到該start和end在哪張表中,然后在該表中根據start和end取出符合的商品,核心代碼邏輯如下所示,

public List<CampaignItemRelationDTO> getCampaignItemRelationList(int start, int end,
                                                                     Function<Integer, Long> getMaxId,
                                                                     Function<Integer, Long> getMinId,
                                                                     Function<CampaignItemRelationQuery, List<CampaignItemRelationDTO>> queryItems) {
        List<CampaignItemRelationDTO> relationList = Lists.newArrayList();
        for (int i = 0; i < 64; i++) {
            //min以及max的值均走快取,不會對db產生壓力
            long minId = getMinId.apply(i);
            long maxId = getMaxId.apply(i);
            long tableTotal = maxId - minId + 1;
            if (minId <= 0 || maxId <= 0) {
                continue;
            }
            //起始減本表內總量,如果大于0,則一定是從下一張表開始的,直接跳出回圈,降低start以及end繼續
            if (start - tableTotal > 0) {
                start -= tableTotal;
                end -= tableTotal;
                continue;
            }
            // 進入到這里,說明一定已經有一部分落在這里了,那么繼續遍歷取值
            // 先判定是否是最后一張表,如果是,則去除需要的 ,然后回傳,如果不是最后一張表,那么需要取出本張表中所需的資料,然后進行下次迭代
            // 判定為最后一張表的條件是 表的起始點+pageSize < maxId,即(minId+start)+(end-start) <= maxId,簡化為 minId + end <= maxId
            if (minId + end <= maxId) {
                //如果minId + end 還小于本表的最大值,那么說明min以及max均落入了表內,那么只取本表的資料即可
                relationList.addAll(queryItems.apply(getQuery(start + minId, end + minId, i)));
                break;
            } else {
                //走入這里,說明資料進行了跨表
                //首先取出本表符合條件的全部資料,然后將起始值設定為0,然后降低
                relationList.addAll(queryItems.apply(getQuery(start + minId, maxId, i)));
                //新的結束值應該為pageSize-當前表中取得的數量總量,即(end-start)-(tableTotal-start),簡化后得到end-tableTotal
                end = (int) (end - tableTotal);
                start = 0;
            }
        }
        return relationList;
    }

這種處理方式存在幾個缺點:

    1. 對于資料集中的表來說是一種不錯的方法,但對于資料稀疏型表來說就非常低效,如果資料分布很稀疏,count很大,分批處理后任務數量非常大,最后獲得的商品ID也就幾百個,比如,新零售圈品方式由于框架限制,也采用了一樣的分頁處理方式,一次全量圈品商品增加訊息量可達20w,實際可能只獲得了幾百個商品,

    2. 每個訊息處理都需要回圈查詢很多張表直到start、end所在的那張表,通過max和min判斷start和end是否出自該表,頻繁取max、min也會給DB造成壓力,為了避免對DB的壓力,又需要利用快取max、min,

針對于第一個缺點:資料稀疏型的資料源訊息數量過大,可以在不改動框架的同時進行改善,只需換個角度計算總數count,如下圖2.9所示,count取的是所有表中的最大值和最小值的差,這樣即使是稀疏型資料源,count值也不會很大,然后任務處理的時候根據start和end回圈從所有表中取出對應的商品ID,而且這種方式也會稍微減少取max和min的次數,如果密集型資料源采用這種分頁處理方式,將會導致單頁資料量過大問題,

圖2.9 縱向分頁處理方式

針對于第二個缺點:頻繁取max和min問題,上面的處理方式是用全域的眼光計算count,然后分頁處理,因此無法直接定位start和end應該取自哪張表,其實,可以針對于每個表單獨分頁處理,訊息中不僅包含start、end,還包含分表的index資訊,但是這種方式依然存在對稀疏型資料源劃分任務數過多的問題,而且現在圈品分批框架也不支持這種方式,


? 動作模塊


動作模塊的作用是處理圈品metaq訊息,動作模塊與訊息型別是一一對應的,動作模塊分為:規則變化、商品增加、商品洗掉,


規則變化動作

規則變化動作模塊處理規則變化型別的訊息,該動作主要處理流程是,呼叫分批處理模塊進行分批,然后將每批包含的資訊通過metaq發送出去,也就是產出商品增加和商品洗掉訊息,


商品增加動作

商品增加動作模塊處理商品增加型別的訊息,動作處理流程圖如下圖2.10所示,

圖2.10 商品增加動作處理流程圖


商品洗掉動作

商品洗掉動作模塊處理商品洗掉型別的訊息,動作處理流程圖與圖2.10類似,只是最后業務處理模塊呼叫商品洗掉處理的方法,


? 業務處理模塊


業務處理模塊框架類圖如圖2.11所示,每一種業務都需要實作TargetHandler,其中handle方法處理圈品增加,rollback方法處理圈品洗掉,目前已經接入的幾個大業務分別是:品類券、免息券、會員卡等,

圖2.11 業務處理類圖


? 階段總結


這一階段,圈品從無到有,誕生于品類券,又脫胎于品類券,在業務方面,支撐了品類券、免息券、會員卡等業務,在性能方面,能處理百萬級甚至千萬級商品,系統是在不斷發展中完善,這一階段的圈品存在以下不足點,


處理商品變更訊息性能問題

2.2.4中講解了處理商品變更的必要性以及存在的問題,當有效的圈品池越來越多時,處理商品變更訊息QPS越來越高,系統性能越來越差,而且很多規則需要呼叫HSF或者查詢快取之類的耗時操作,因此這些規則無法支持處理商品變更訊息,這一階段,承載圈品系統集群CPU一直都在50%以上,即便集群擁有600多臺機器,


復雜頂級規則處理問題

面對復雜頂級規則,圈品沒有很好的辦法處理,然而在業務快速變化情況下,圈品需要有能力應對復雜規則,即使目前沒有出現太復雜的頂級規則,圈品在處理賣家串列圈品方式也存在局限性,


系統穩定性和可控性問題

  1. 穩定性問題:通過2.4節可以了解到,在進行大量圈品的時候,只要觸發圈品變更,規則變化訊息立馬會裂變出更多的圈品訊息,圈品metaq訊息堆積量可達到百萬,由于下游系統限流導致大量例外,系統負載又高,訊息處理又耗時長,metaq訊息處理存在雪崩風險,有時一條訊息重復處理上萬次,

  2. 可控性問題:由于觸發圈品變更時,會立馬裂變出更多的訊息,訊息大量堆積時,圈品不能選擇性處理、不能停止處理訊息、不能選擇性忽略訊息等等,這就意味著系統發生問題的時候,沒有抓手進行控制,只有眼巴巴的看著,舉個實體,兩條訊息重復執行幾萬次,一個是洗掉該商品,一個是增加該商品,不停的給商品打標去標,商品產生大量商品變更,導致搜索引擎同步延遲,當時就只能眼巴巴看著,再舉個例子,由于某一種圈品規則代碼有bug會導致fullGc,然后該規則相關的圈品池產生了大量訊息,由于無法選擇性處理訊息,導致整個圈品系統癱瘓,


分批處理缺陷問題

在2.4.2章節中講到了第一階段分頁處理的缺陷,不同資料源的分頁處理不能一概而論,框架應該給予更多的靈活性,


資料一致性問題

在進行大量圈品時,系統或下游系統例外無法避免,所以資料有可能存在不一致的情況,對于業務來說,該增加的商品沒有增加,可能還能接受,如果該洗掉的商品沒有洗掉,那么就很可能資損了,



第二階段



? 概述


第二階段,針對于第一階段的問題進行優化,新架構圖如圖3.1所示,其中黃色部分是新增部分,圈品總體可以劃分為六大塊,分別是資料源模塊、動作模塊、規則模塊、業務處理模塊、調度模塊和設定端,調度模塊是新增部分中最重要的,首先新增了任務的模型,如圖3.2所示,任務會先保存到DB中,scheduleX秒級定時觸發調度邏輯,最后通過metaq分發任務進行分布式處理,圖中紅色線條表示全量圈品的流程,圖中橘黃色表示增量圈品的流程,下面將分別詳細介紹新增部分,

圖3.1 第二階段圈品架構圖

圖3.2 任務模型


? 商品變更訊息優化


商品變更訊息處理流程圖如圖3.3所示,第一步,建立圈品池與商品的泛化關系,第二步,通過Blink根據商品與圈品池的泛化關系過濾商品變更訊息,剩下少量的商品變更訊息,第三步,根據圈品池與商品的泛化關系判斷哪些圈品池需要處理該商品變更訊息,


過濾后的商品變更訊息日常平均qps在200左右,而且只有與該商品相關的圈品池才需要處理該商品變更訊息,因此,具體到某些圈品池上來看,其處理商品變更訊息的qps在100以內,同時系統性能消耗也大大降低,集群機器從巔峰時期700多臺降低到現在300多臺(由于集群還承載其他業務,實際圈品需要的機器數量可以壓縮到100臺以內),

圖3.3 商品變更訊息流程圖

商品變更訊息過濾的關鍵點在于如何建立圈品池與商品的泛化關系,這里的思想是根據具體規則盡量大范圍的圈定可能的商品,

比如賣家圈品方式,當小二填寫賣家串列后,這個圈品池與哪些賣家有關系就已經確定了,除此之外的賣家肯定不會跟這個圈品池發生關系,因此可以將賣家與圈品池的關系存入tair,供Blink過濾商品變更訊息使用,賣家與圈品池的關系是比較通用的思路,其他圈品方式也可以轉化成這種關系,比如商品串列圈品方式,當小二填入商品ID后,這些商品屬于哪些賣家就確定了,除此之外的賣家的商品不會與該圈品池發生關系,

當然,賣家與圈品池的關系也有不適用的時候,比如大促活動圈品池方式,一次大促活動可能有幾十萬的賣家參與,而且賣家會不斷的報名參加大促,因此很難獲取圈品池與賣家的關系,針對大促活動圈品方式,可以建立tmc_tag與圈品池之間的關系,大促商品都有統一的tmc_tag,因此可以通過將tmc_tag與圈品池的關系存在diamonds供Blink過濾商品變更訊息使用,總之,其他圈品方式根據具體規則找到圈品池與商品的泛化關系,可以通過商品上的資訊和泛化關系判斷商品與商品池是否存在關系,


? 復雜頂級規則處理


維度定義

第一階段中已經解釋了頂級規則是能夠做為資料源的規則,為了更好支持復雜資料源規則,引入了維度的概念,然后通過降維將復雜規則變成簡單規則,最后的目的是從資料源中獲取所包含的商品ID,

定義1:單個商品ID為零維,即沒有維度

定義2:能夠直接獲取多個商品ID的規則為一維,例如商品串列規則,單個賣家規則

定義3:二維規則由多個一維規則組成,例如多個賣家規則

定義4:三維規則由多個二維規則組成,更高維規則由多個比它低一維的規則組成

從上面定義可以看出,賣家串列規則既有可能是一維規則,也有可能是二維規則,當規則只包含一個賣家時為一維規則,當規則包含多個賣家時為二維規則,為了更好理解,拿上面的復雜規則來講解,一個賣家有很多商品,一個品牌團有很多商家報名,如果現在運營設定圈多個品牌團下面所有商品,下圖3.5所示是該規則降維的程序,

圖3.5 規則降維程序


規則變化動作調整

在第一階段,規則變化動作處理流程就是呼叫分批處理模塊進行分批,然后將每批包含的資訊通過metaq發送出去,也就是產出商品增加和商品洗掉訊息,現在,規則變化動作處理流程調整為如下圖3.6所示,首先需要判斷規則是否為一維規則,只有一維規則才能直接通過分批處理,否則就要進行降維,產生的降維任務由規則降維動作進行處理,

圖3.6 新規則變化動作處理流程圖


增加規則降維動作

規則降維動作處理規則降維型別的任務,動作處理流程圖如下圖3.7所示,RuleHandler中自定義的降級方法指定了下一維度的規則,因此一次降維任務只能將規則降低一個維度,

降維只針對做為資料源的頂級規則,因此,首先遞回獲取頂級規則,接著呼叫自定義降維方法處理頂級規則后得到更低一維度的頂級規則集合,然后使用降維后的頂級規則替換規則樹中的頂級規則得到新的規則樹集合,最后,將新規則樹生成規則變化任務,由規則變化動作判斷是否繼續降維,

圖3.7 規則降維動作處理流程圖


? 新增調度模塊


任務調度

新增任務模型如圖3.2所示,任務相當于第一階段中的圈品訊息,第二階段中任務是需要先落庫,然后由調度器來進行調度的,

調度器是任務扭轉的動力,所有型別的任務都會插入DB中由調度器統一調度,任務表中已完成的任務會隔一段時間清理,即使是這樣,任務表也有可能存在幾百萬任務,而且圈品的速度很大程度由調度器決定,因此對調度器的性能要求是很高的,不僅如此,調度器應該具備更多的靈活性,

調度器處理流程圖如圖3.8所示,通過scheduleX秒級定時觸發調度邏輯,然后通過metaq分發任務ID,其實分發任務ID也可以通過scheduleX來完成,最初的實作也就是通過scheduleX來進行任務ID的分發,最后還是改成了通過metaq來分發任務,因為scheduleX分發大量任務時存在不可接受的延遲,

講回到圖3.8,任務調度的基礎是知道未完成任務的分布,為了避免統計未完成任務分布時產生慢sql,這里做了一個很重要的動作,即下文第一步,

第一步,首先獲取未完成任務所屬的圈品池ID的分布,由于這里只根據狀態統計圈品池ID,狀態和圈品池都有索引,利用了覆寫索引,因此性能很高;

第二步,隨機選擇十個圈品池保證任務調度分配均衡,同時減少任務統計的耗時;

第三步,統計這十個圈品池的未完成任務數量的分布;

第四步,根據第三步的數量統計以及系統配置,分配每個圈品池參與調度的任務數量;

第五步,根據任務分配數量獲取任務ID;

第六步,通過metaq將任務ID分批發送到不同的機器進行處理;

第七步,接收metaq訊息;

第八步,將任務ID提交異步處理,這里為了提升處理速度,維護了一個執行緒池,任務ID只需要提交到阻塞佇列中;

第九步,任務ID提交異步處理后,立馬更新任務狀態為處理中,避免任務再次被調度,處理中的任務不屬于未完成的任務,

圖3.8 任務調度流程圖

對比第一階段中圈品訊息模式,第二階段任務首先保存到DB,然后由調度器進行調度,調度器能夠提供更多的靈活性,可以獲取以下優點:

  1. 任務優先級可根據圈品池進行調整,部分圈品池出現問題不會影響整體;

  2. 任務調度速度可調整、可暫停,可以根據任務型別分配處理速度;

  3. 任務調度可監控、可精確統計;

  4. 圈品程序可查詢、可追蹤;


任務統計

一個完善的平臺少不了系統可視化,任務處理進度是可視化中重要的部分,任務數量統計就少不了group by和count,任務表最大的時候可能存在上百萬的資料,同步方式進行統計肯定是不行的,因此采用如下圖3.9所示異步方式,利用覆寫索引方式得到圈品池ID的分布,每個圈品池的任務不會很大,因此每個圈品池分開統計將不會產生慢sql,

圖3.9 任務統計思路


分批處理新思路

在第一階段中提到了分批處理的缺陷問題,這里將討論如何解決整個問題,新的分批處理方式還在開發當中,設計思路按照該章節所講,


★ 框架設計

圈品擁有各種各樣資料源,每種資料源的特性都有不同,所以無法用一種通用的分批方式處理所有資料源,因此圈品分批處理的框架應該更加通用,讓每種資料源都能自定義自己的分批處理方式,

在框架方面的調整如下圖3.11所示,新增基礎分批物件Pageable,考慮到和老框架到兼容,Pageable包含老框架的使用的分批引數start、end、pageSize,自定義分批物件TablePageable或其他都繼承自Pageable,RuleHandler增加自定義分批方式getPageabelList,老框架的分批方式可以寫在AbstractRuleHandler的getPageableList中,需要自定義分批方式的RuleHandler覆寫getPageabelList便可,

圖3.11 分批處理新框架類圖


★ 分頁處理思路

在第一階段中,分頁處理為了避免limit大翻頁問題,采用了通過主鍵id進行分頁的方式,在這里先討論下為什么limit會存在大翻頁問題,以及優化方案,

如下sql所示,當N值很大時,這個sql的查詢效率會很差,并發查詢時甚至會拖垮資料庫,因為執行這個sql時需要先回表查詢N+M行,然后根據limit回傳M行,前面查詢的N行最后被丟棄(具體討論可參考limit為什么會慢),一般遇到這種情況,業務上都是不允許大翻頁,應該根據條件過濾,但是圈品要分批取出所有資料,所以圈品就繞不開這個問題,

SELECT * FROM table WHERE campaing_id = 1024 LIMIT N,M

在這里總結了兩種解決思路,圈品為了獲取所有有效的商品,因此無需考慮資料整體的分頁,可以將分表獨立分頁處理,


id與limit組合優化

前面分析了使用limit大翻頁最大的問題是查詢前N(即offset)條資料所耗費的時間,在理想的情況下,id是連續自增,可以在where條件中使用id來代替offset,sql即如下所示,

SELECT * FROM table WHERE campaing_id = 1024 and id > N LIMIT M

優化思路中所說的理想情況,幾乎沒有場景能夠達到要求,但是這也不影響該思路的應用,根據上一次翻頁結果id使用limit查詢下一批,如果id不連續,limit將可以跳過很多不連續id,減少查詢次數,

結合圈品實際情況使用該思路,首先通過min和max得到資料在表中分布的最小值和最大值,針對稀疏型資料分批間隔可以很大(為了解決任務數量過多問題,比如間隔是2W,即end-start=2w),start和end分別是每批資料對應的開始id和結束id,然后根據id做為where條件使用limit取下一頁資料,接著根據下一頁最大id做為where條件使用limit取后面的資料,一直回圈下去,直到id>end,對于稀疏型資料,也許回圈1-2次就完成了,流程圖如下圖3.12所示,

圖3.12 圈品分頁處理優化思路


覆寫索引優化

當sql查詢是完全命中索引,即回傳引數和查詢條件都有索引時,利用覆寫索引方式查詢性能很高,先通過limit查詢出對應的主鍵id,然后再根據主鍵id查詢對應的資料,由于無需從磁盤中取資料,所以limit方式比之前性能要高,sql如下所示,

SELECT * FROM table AS t1 
INNER JOIN (
  SELECT id FROM table WHERE campaing_id = 1024 LIMIT N,M 
) AS t2 ON t1.id = t2.id

“覆寫索引優化“到底能優化到什么程度呢,對此進行了一個測驗,表item_pool_detail_0733包含10953646條資料,通過item_pool_id = 1129181 and status = -1條件篩選后剩下3865934條資料,item_pool_id和status建立了聯合索引,

測驗1

我們看下offset較小的時候,sql和執行計劃如下所示,執行平均耗時83ms,可以看到在offset較小的時候,sql性能是可以的,

SQL:

SELECT *  FROM `item_pool_detail_0733` WHERE item_pool_id = 1129181 and status = -1  LIMIT 3860000,100

執行計劃:













idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEitem_pool_detail_0733
refidx_pool_status,idx_itempoolididx_pool_status12const,const5950397100.00


測驗2

當offset較大的時候,sql如下所示,執行計劃和上面是一樣的,執行平均耗時6371ms,這個時候sql性能就很差了,

SQL:

SELECT *  FROM `item_pool_detail_0733` WHERE item_pool_id = 1129181 and status = -1  LIMIT 3860000,100

測驗3

現在使用“覆寫索引優化”思路優化測驗2的sql,sql和執行計劃如下所示,執行平均耗時1262ms,相比優化之前執行耗時6371ms,性能提高了5倍多,但是1s多的執行耗時對于圈品來說也是難以接受的,

實際情況下,分庫分表會把資料均勻分布在所有表中,因此,單表過濾后還剩下300w多資料的情況是很少的,為此,我接著測驗資料量不同時該sql的性能,當LIMIT 2000000,100時,執行平均耗時697ms;當LIMIT 1000000,100時,執行平均耗時390ms;當LIMIT 500000,100時,執行平均耗時230ms;

SQL:

SELECT * FROM `item_pool_detail_0733` as t1 
INNER JOIN (
    SELECT id  FROM `item_pool_detail_0733` WHERE item_pool_id = 1129181 and status = -1 LIMIT 3860000,100
) as t2 on t1.id = t2.id

執行計劃:

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARY<derived2>
ALL



3860100100.00
1PRIMARYt1
eq_refPRIMARYPRIMARY8t2.id1100.00
2DERIVEDitem_pool_detail_0733
refidx_pool_status,idx_itempoolididx_pool_status12const,const5950397100.00Using index

通過上面的測驗可以看出,使用limit查詢50w資料時,性能還可以,而且圈品的資料源都是根據商品ID進行分庫分表,因此,根據過濾條件過濾后剩余的資料幾乎都能在50w以內,如果圈品采用這個思路優化分頁處理,那么將可以完全解決第一階段中的兩個缺點問題,而且分頁處理邏輯相比之前簡單很多,


? 資料一致性保障

資料一致性保障解決方案充分復用了圈品框架,只需三步,第一,在動作模塊增加一致性檢查動作,第二,新增自動產出一致性檢查任務的scheduleX任務,第三,在業務處理模塊中增加自定義一致性檢查方法,


新增一致性檢查動作

一致性檢查動作處理一致性檢查任務,其處理流程圖如圖3.13所示,

圖3.13 一致性檢查動作處理流程圖


自動產出一致性檢查任務

圈品當前有效的圈品池已經超過5000,如果一次性檢查5000個圈品池,那么產出的檢查任務數量可能上百萬,因此,需要一個定時任務不斷監控任務表中未完成任務的數量,在數量較少的情況下一次選擇幾個圈品池產出一致性檢查任務,一致性檢查任務的產出依然是復用圈品規則變化處理流程,


業務自定義一致性檢查

新業務處理類圖在之前的基礎之上增加自定義一致性檢查方法consistencyCheck,需要自定義一致性檢查的業務實作該方法便可,

圖3.14 新業務處理類圖


? 性能資料


卡券商品設定引擎的性能主要通過三個指標衡量,分別是:任務調度吞吐量、商品處理速度,


任務調度吞吐量

一個任務可能包含幾個商品也有可能包含上千個商品,取決于資料源的稀疏程度,當資料是稀疏的時候,任務數量將會很多,這個時候圈品的速度就取決于任務調度的速度,目前,任務調度的速度可以達到5w個/分鐘,這還并不是最大值,還有上升的空間,


商品處理速度

商品的處理速度受下游系統的影響需要限流,不考慮業務處理速度,理論上處理商品速度可以達到6w TPS,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/233123.html

標籤:其他

上一篇:OpenStack的探索之路——基礎篇

下一篇:自學JAVA的我終于拿到阿里offer了,給我的秋招交上滿意答卷,分享一下面試題和我的資料!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more