1. 程式人生 > >有贊訂單匯出的配置化實踐

有贊訂單匯出的配置化實踐

引子

背景

有贊訂單匯出業務隸屬於有贊交易訂單管理組,主要職能是將有贊商家的訂單資料通過報表的形式匯出並提供下載給商家使用。目前承接了有贊所有的訂單匯出業務,報表的欄位覆蓋交易、支付、會員、優惠、發貨、退款、特定業務等,合計超過 100 個。

挑戰

隨著有讚的迅速發展,有讚的行業、業務與產品覆蓋面越來越廣。從行業角度來看,覆蓋了微商城、新零售、餐飲、美業、教育等,從模組角度來看,覆蓋了交易、資產、客戶、營銷、店鋪等,從產品角度來看,覆蓋了分銷、精選等。每個行業、模組、產品都會在訂單匯出報表中有所訴求。如下圖所示,展示了有贊訂單匯出的域模型:

訂單匯出需要跨越來自不同行業、不同產品、不同模組,對各個業務域的儲存和設計有整體理解;同時,需要通過技術手段(資料域、儲存域、報表域、檔案域)聚合來自各個域的資料集合,生成可讀的報表下載給商家。

由此可見,其主要的挑戰是:如何快速支援各個域靈活多變的匯出欄位需求。如何應對這一挑戰呢?

架構重構

訂單匯出的最初實現是從交易的多個 DB 及多個業務 API,分別獲取交易、支付、會員、發貨、退款、核銷、分銷等多個數據,組裝到一起生成報表。採用 PHP 任務指令碼來實現。這種做法有兩個痛點:

  • 在小量訂單匯出的情形尚能應付,一旦同時有多個數萬訂單匯出任務時,資源佔用非常大,CPU 基本被打滿,PHP 匯出程序被阻塞,從而阻塞了所有的訂單匯出,匯出就無法提供服務了。
  • 直接訪問業務資料庫存在一種潛在風險:如果訪問業務資料庫的資料量很大,SQL 編寫不當導致慢查,往往會給業務資料庫帶來訪問壓力,嚴重影響正常核心業務流程。

基於這兩個痛點,有贊訂單管理組進行了架構升級,詳見有贊技術博文《有贊訂單管理的三生三世與“十面埋伏》。 得益於此,訂單匯出也遷移到基於 ES + Hbase 的技術棧。其中訂單搜尋採用 ES 服務實現,訂單詳情則儲存在 Hbase 中,通過 API 來獲取。整體流程如下所示:

重構之後,訂單匯出的效能和穩定性有了很大的提升:

  • 支援百萬級訂單的匯出,且匯出的速度比之前大幅提升。以前匯出幾萬訂單慢且易阻塞,現在平均能匯出 1w/1min,大多數匯出可在十幾秒內完成。
  • ES 和 Hbase 具有天然的彈性和容量擴充套件性,即使總訂單量有數量級的增長,匯出的速度和穩定性也不受影響。
  • 擺脫了容易被阻塞的困境,不再直接訪問業務資料庫,關閉了匯出對核心業務流程的潛在威脅。

接下來,開始了配置化之旅。

配置之旅

初嘗配置-設下伏筆

訂單匯出常常要面臨新增新的報表欄位的需求。最初實現不太靈活,是來一個欄位,在程式碼流程裡新增一個欄位。每次增加新的欄位,都需要修改多處。因此,第一個優化是採用函式介面程式設計,將欄位定義做成列舉可配置化的,然後遍歷指定的報表欄位列表,拿到對應的欄位定義,計算欄位的值,寫入報表檔案。

根據報表欄位列表生成報表行的虛擬碼如下:

public List<String> generateReportLineData(List<String> fields) {
        return StreamUtil.map(fields, field -> {
            try {
                FieldDefinition fieldDef = getFieldDefinition(field);
                FieldMethod method = getMethod(fieldDef);
                String value = method.invoke(this.reportItem);
                return postproc(value);
            }catch (Exception e){
                logger.warn("failed to get value for field: {} orderNo: {}", field, reportItem.getOrderNo());
                return "";
            }
        });
    }

這個小小的優化,為進一步的配置化設下伏筆。當需要新增報表欄位時,只要增加新的欄位定義,而不需要在流程裡增加程式碼。增強軟體可擴充套件性的一個重要方法是,將流程變得通用,只要增刪流程裡的環節及定義即可。

凡基礎必要總是正確的方向。

報表配置-破局之時

有贊新零售、餐飲的迅速興起和發展,需要低成本快速地搭建起零售和餐飲的訂單匯出。這要求訂單匯出具有更大的靈活性,能夠根據不同行業的要求配置不同的欄位列表及匯出格式,同時又能互不影響。此外,不同商家有個性化的匯出需求。然而,原來的訂單匯出,是專門為微商城開發的商品級別的報表。要加一個欄位,往往會影響所有的有贊商家,使用體驗不佳,訂單報表本身也變得臃腫不堪。

如何突破原來的侷限,支援更靈活的訂單匯出呢?這是訂單匯出面臨的一個破局點。通過訂單匯出模板解決了這個問題。針對行業、產品配置的匯出模板儲存在 DB 表 export_biz_conf 裡;針對有贊商家的匯出模板儲存在 DB 表 export_customized_conf 裡。每個匯出模板包括瞭如下資訊:報表欄位列表、匯出維度(訂單及商品)、報表檔案格式、可選項等,做到足夠靈活。

若要匯出不同報表欄位,只要新增相應欄位,指定報表欄位列表即可;若要生成不同維度的報表,可使用策略模式。比如,

  • 匯出大訂單量,採用批量併發策略更高效;匯出小訂單量,採用序列策略更易理解;
  • 可以把欄位定義寫在原生代碼裡直接引用,或者配置在 Groovy 腳本里更加靈活;
  • 可以根據指定的訂單級別或商品級別進行維度聚合,然後計算報表欄位的值;
  • 可以根據指定的 csv 或 excel 生成相應的檔案。

如圖所示: 針對匯出流程的各環節,可採用策略模式來選擇不同實現,然後將策略組合起來。

通過實現報表配置功能,突破了之前的侷限,可以支援不同行業、產品的標準化和定製化匯出需求,並且做到相互隔離不干擾。

配置深化-更快更穩

隨著有贊進入更多的行業,面臨著更加多變和個性化的匯出需求。比如,有贊教育需要匯出知識訂單的學員資訊和課程資訊,有贊零售需要匯出導購員和發貨倉庫門店名稱。 顯然,如果要完成某個匯出需求,還需要修改程式碼、釋出系統,這種操作會非常頻繁,導致開發和維護成本提升,影響系統穩定性。

如果能夠在應用執行中動態地新增報表欄位並載入和使用,無需修改匯出工程程式碼,無需重新發布系統,就能更加快速地支援匯出需求,將會大幅降低匯出需求支援的開發和維護成本,保持系統穩定性。

為了解決這個問題,引入了動態指令碼語言 Groovy. Groovy 是能夠與 Java 無縫對接的好夥伴,可以直接使用 Java 類的功能。編寫 Groovy 指令碼實現報表欄位邏輯,儲存在欄位配置表 export_field_conf 裡, 在報表配置表 export_biz_conf 或 export_customized_conf 裡引用,然後在應用啟動時快取到記憶體裡並使用。比如粉絲姓名的 Groovy 指令碼如下:

    import com.youzan.trade.orderexport.util.PublicUtil 

    def fansInfo = reportItem.orderInfo.extra["FANS"]
    PublicUtil.fetch(fansInfo, "nickname")  

PublicUtil 是匯出工程裡封裝的一個工具類,可以讓編寫欄位配置指令碼更加簡單。值得提及的是,為了避免使用 Groovy 指令碼可能導致的記憶體洩露,需要對編譯後的 Groovy 指令碼進行快取和執行。

為了實現無需改動程式碼和釋出系統,還需要在整體流程上打通。整體流程如下:

Step1: 當用戶下單後,源資料落到業務資料庫的擴充套件資訊裡;
Step2: 通過資料同步,自動同步到 Hbase 表;
Step3: 通過 Apollo 配置和可擴充套件的資料聚合機制,將資料自動輸送到用來計算報表欄位值的報表物件裡;
Step4: 新增報表欄位的配置;
Step5: 在報表配置中引用該欄位的標識。

下圖展示了通過配置自定義欄位快速支援匯出需求的整體流程。

整體流程打通後,當需要新增個性化欄位時,通常只要做兩步:

  1. 增加個性化欄位的配置,包括 Groovy 指令碼;
  2. 測試通過後,重新整理應用的配置即可。

個性化欄位配置能力已經在線上穩定執行,比如拼團訂單成團時間、零售導購員、有贊教育的課程欄位等。

通用匯出-錦上添花

緊接著,訂單匯出又面臨分銷採購單的匯出需求。分銷採購單匯出流程跟訂單匯出有所不同,需要分別匯出分銷買家訂單和供貨訂單的詳情資訊再匯出。這個流程跟通用的訂單匯出流程是有所區別的。如果通過修改訂單匯出的通用流程來支援,顯然會影響所有的訂單匯出,使訂單匯出流程不清晰。

最終採用的解決方案是:對分銷採購單的匯出需求和所需技術進行抽象,實現一個更加通用的匯出能力模型,支援交易領域的各種潛在匯出需求,而不僅僅侷限於分銷採購單匯出。通過分析訂單匯出流程可以發現,絕大多數匯出都遵循如下核心流程:
外掛化匯出流程

可以將核心流程做成外掛式的。首先,定義一個外掛介面,包含其配置和功能等;其次,實現常用的外掛列表,支援從 ES, HBase, API 查詢或獲取資料,以及常用的過濾、排序、格式化、生成報表等功能;最後,將這些外掛列表串聯成一個具體的匯出例項。整體流程則採用模板方法模式複用了訂單匯出流程。

比如微商城分銷採購單匯出通過依次執行ES查詢外掛、訂單詳情外掛、資料排序外掛、報表欄位格式化外掛、報表生成外掛來實現,其中訂單詳情外掛針對分銷買家單和供貨訂單分別呼叫了一次。

質量保障

前面提到,訂單匯出的報表欄位非常多,匯出資料量大,如何保證程式碼改動或重構後訂單匯出的服務質量和資料準確性?主要手段如下:

  • 質量流程保障是第一位的。最主要的三項是:單測嚴格全部通過; CodeReview 由應用責任人及經驗豐富的高階工程師同時通過;預發線上匯出對比工具通過。
  • 整體架構設計保證了訂單匯出的效能、穩定性和可擴充套件性。
  • 持續小幅重構使得系統能夠持續優化,避免一次性大改造傷筋動骨且容易導致線上故障。
  • 設計先行,對程式碼質量非常重視。
  • 執行預發線上訂單匯出自動化對比工具,很大程度上增強了成功釋出的信心,是釋出前保障質量的一道重要防線。

此外,採用函式程式設計及設計模式,使程式碼實現層面更具複用性和柔軟性。18K 行程式碼,程式碼重複率約為 1.8%。

小結與致謝

本文簡要講述了有贊訂單匯出的配置化實踐。通過配置化之後,訂單匯出的能力和穩定性有了大幅提升。當然,還有一些需要提升的地方。比如,可以增加擴充套件點機制,允許業務方定製化匯出;區域性細節可以打磨得更細膩。歡迎對海量訂單業務感興趣有經驗的小夥伴與我們一起共建訂單管理大局!簡歷可直郵 [email protected]

在這個過程中,有許多小夥伴給予了有力的支援,比如產品同學對訂單報表的細緻的規劃設計、客滿運營同學提出的及時反饋、有贊技術團隊的支援以及自己的付出。

更多技術文章,詳見有贊公眾號: 有贊coder 及 有贊技術部落格