張舵, 程磊, 張之江
(上海大學 通信與信息工程學院,上海 200444)
隨著我國汽車保有量的增長以及近年來移動互聯網熱潮,互聯網車險取得了長足的發展[1]。通過APP購買車險相比于傳統業務模式具有更靈活、快捷的優勢,相應的也需要開發配套的運營管理平臺來完成車險規則與活動等信息的動態配置與業務數據的高效管理。
本文涉及到車險運營平臺中訂單數據匯總模塊的功能完善與優化。該模塊負責對每天的車險交易數據跟據承保合作方、出單地區以及出單時間分類匯總,并支持運營人員通過運營管理平臺網站查詢報表。該模塊原有方案為“T+1”全表統計,且隨著數據量的增長該匯總方案的執行效率明顯下降。本文將對該模塊進行完善,使其支持運營人員實時查詢匯總數據且通過數據庫優化提升該功能執行效率,同時避免影響日常下單功能的數據寫入性能。
MySQL是基于主庫的二進制日志文件內容實現了主從復制功能。用戶對主數據庫的創建、修改、刪除等操作以及對表的增、刪、改、查操作都會記錄到二進制日志文件中,從數據庫通過I/O線程來連接主服務器,通過主服務器創建的新線程獲取二進制日志的內容并拷貝到它的中繼日志當中,之后利用SQL線程從中繼日志讀取事件,并重放其中的事件而更新到從數據庫當中,使其與主數據庫中的數據保持一致[2]。
Spring框架是一個由 7個定義良好的模塊組成的分層架構。這些Spring模塊構建在核心容器定義了創建、配置和管理bean的方式的核心容器之上。Spring框架的兩大特性是“控制反轉”和“面向切面”[3]。應用了“控制反轉”,一個對象就不需要自己創建或者查找依賴對象,其依賴對象會通過被動的方式傳遞進來。這樣降低了對象之間的耦合,這個特性在web框架、數據庫框架以及通過分布式框架遠程調用服務時都會用到,使得項目中各組件在開發時互相解耦合,但不影響各組件在運行時的互相調用。
Dubbo是阿里巴巴提供的、基于Java的Http Client請求、高性能的開源RPC遠程服務調用方案,通過Dubbo可以把業務邏輯分離出來,作為一個獨立的模塊,使得前端應用能快速和穩定地響應請求。使用Dubbo架構可以支撐高并發的項目,且低耦合,擴展性、穩定性都很強[4]。
Dubbo中注冊中心負責服務地址的注冊與查找,相當于目錄服務,服務提供者和消費在啟動時與注冊中心交互。服務提供者向注冊中心注冊其提供的服務,服務消費者向注冊中心獲取服務提供者地址列表,并根據負載算法直接調用提供者。在項目中,我們選擇采用ZooKeeper來作為Dubbo的注冊中心[5]。
原有的訂單數據匯總功能通過在每天凌晨執行定時任務,對已配置的合作方與業務開展地區的訂單數據進行匯總,并將匯總結果保存在匯總數據表中。該方案需要反復讀取數據,對數據庫IO占用較高,且在統計當年、當月以及累計數據時需要查詢全表數據,因此查詢效率較低耗時較長,不能滿足運營人員即時查詢的需求,而且在工作時間執行該匯總方案會影響車險購買流程的數據寫入性能。
出單即時匯總模塊在設計上需要解決以下兩點問題:
1) 即時查詢的請求多發起于工作時間,此時出單業務繁忙,會影響到下單速度。
2) 由于訂單數據表數據量過大,進行全表查詢會導致數據表暫時鎖死或低速響應。
為了避免查詢時下單業務卡死,可以采用數據庫讀寫分離。訂單數據匯總操作連接到從數據庫執行,并將用戶權限設置為只讀。主數據庫設置如下:
Server-id=1 #這是數據庫ID,此ID是唯一的,主庫默認為1
log-bin=wysq1-bin #二進制日志文件,此項為必填項,否則不能同步數據
binlog-do-db=bussiness #需要同步的數據庫,如果需要同步多個數據庫
binlog-ignore-db=account #不需要同步的數據庫
從數據庫設置如下:
Server-id=2 #這里ID改為2 因為主庫為1;
log-bin=mysq1-bin #必填項,用于數據同步;
master-host=123.56.XXX.XXX #主庫IP;
master-user=slave #同步用的賬戶
master-password=password #同步賬戶密碼;
master-port=3306 #同步數據庫的端口號。
對應主從數據庫,需要配套負責訂單數據寫入的生產者以及負責訂單數據讀取的生產者,兩個生產者分別負責對主數據庫與從數據庫的操作,供消費者調用不同服務。對應的Dubbo生產者配置如下:
〈!--dubbo配置--〉
〈dubbo: application name="wallet-provider" owner="it" organization="capli"/〉
〈dubbo: registry address="${dubbo.register)"/〉
〈dubbo: protocol name="dubbo" host="${dubbo.host}" port="${dubbo.port}"〉〈/dubbo: protocol〉
〈dubbo: annotation package=""/〉
消費者配置如下:
〈!--賬戶注冊包--〉
〈dubbo: reference interface="com.mobisoft.wallet.api.OrdreWriteApi" id="OrderWriteApi" version="1.0.0" timeout="100000"〉〈/dubbo: reference〉
〈dubbo: reference interface="com.mobisoft.wallet.api.OrdreReadApi"〉 id="OrderReadApi" version="1.0.0" timeout="100000"〉〈/dubbo: reference〉
〈!--提供方應用信息,用于計算依賴關系--〉
〈dubbo: application name="wallet_web" owner="baobei_it" organization="baobei"〉
〈!--使用zookeeper注冊中心暴露服務地址--〉
〈dubbo: registry address="${dubbo.register} timeout="100000"/〉
〈dubbo: annotation package="com.mobisoft.wallet"/〉
即時統計有兩種思路:
1) 數據庫總體情況統計,即執行即時統計功能時對于全表數據進行統計,這種方式邏輯簡單、統計結果全面但存在執行時間隨著數據增長而變長的情況。
2) 數據庫增量數據統計,即記錄每次更新數據的時間點和結果,新一次查詢時只需統計兩次查詢之間的增量數據即可。這種方式的查詢量減小了幾個數量級,不過只能依據已配置的合作方與地區進行統計,對于新增合作方與地區維度的支持不佳。
由于在生產中存在統計維度變更的情況,所以我綜合兩種思路的優劣,采用了對新增維度進行全表查詢,對已有統計數據的維度做增量查詢的方式,方案流程圖如圖1所示。

圖1 方案流程
為了提高匯總執行速度并實現代碼邏輯與業務數據的分離,主要匯總邏輯選擇在存儲過程中實現。采用存儲過程有以下幾點優點[6]:
1) 提高性能:普通SQL語句會在創建過程時進行分析和編譯。而存儲過程則會在首次運行一個存儲過程時預編譯,查詢優化器對其進行分析、優化,并將得到的最終存儲計劃保存在系統表中,這樣,在執行過程時便可節省此開銷。
2) 降低網絡開銷:只需要提供存儲過程名和必要的參數信息就可以完成調用,從而降低了網絡的流量。
由于出單統計涉及到大量數據表的讀寫,采用存儲過程可以將這些操作在數據庫內完成,減少了對數據庫IO的占用。
為了支持增量數據匯總功能,我在匯總數據表中增加了上次統計ID字段,并將數據做了垂直拆分以減少數據呈現時的冗余數據[7],修改后的訂單統計報表結構如圖2所示。

圖2
垂直拆分之后,主表用來保存省級匯總信息,明細表用于保存次級地區匯總信息。這樣在頁面展現省級數據時減少冗余數據對帶寬的占用,并能有效提高查詢次級地區匯總信息時的速度。
跟據系統需求以及設計方案,我們設計了測試用例對出單數據即時統計的功能進行了測試,并記錄了系統相關運行參數。具體數據如表1所示。

表1 系統相關運行參數
從測試數據可以看到,增量匯總方案單個地區匯總耗時僅為全表匯總的1/14,且在實際操作中,執行全合作方查詢操作用時也僅為原匯總方案的1/10。并且由于功能調整后單合作方查詢時僅需匯總對應合作方數據,所以單合作方數據匯總耗時減少到了10.6秒,大幅提升了用戶使用體驗。
本文跟據訂單數據即時匯總需求完善了其匯總數據與分類呈現功能,并通過數據庫讀寫分離、分別設置讀寫功能的Dubbo微服務、利用存儲函數減少數據庫IO性能壓力以及對有歷史記錄的統計信息進行增量統計的方式來對訂單數據匯總模塊性能進行優化。系統測試顯示,這些優化措施使得系統運行速度提升的同時也很好地改善了運營人員的使用體驗。