張舵, 程磊, 張之江
(上海大學(xué) 通信與信息工程學(xué)院,上海 200444)
隨著我國汽車保有量的增長以及近年來移動互聯(lián)網(wǎng)熱潮,互聯(lián)網(wǎng)車險取得了長足的發(fā)展[1]。通過APP購買車險相比于傳統(tǒng)業(yè)務(wù)模式具有更靈活、快捷的優(yōu)勢,相應(yīng)的也需要開發(fā)配套的運營管理平臺來完成車險規(guī)則與活動等信息的動態(tài)配置與業(yè)務(wù)數(shù)據(jù)的高效管理。
本文涉及到車險運營平臺中訂單數(shù)據(jù)匯總模塊的功能完善與優(yōu)化。該模塊負責對每天的車險交易數(shù)據(jù)跟據(jù)承保合作方、出單地區(qū)以及出單時間分類匯總,并支持運營人員通過運營管理平臺網(wǎng)站查詢報表。該模塊原有方案為“T+1”全表統(tǒng)計,且隨著數(shù)據(jù)量的增長該匯總方案的執(zhí)行效率明顯下降。本文將對該模塊進行完善,使其支持運營人員實時查詢匯總數(shù)據(jù)且通過數(shù)據(jù)庫優(yōu)化提升該功能執(zhí)行效率,同時避免影響日常下單功能的數(shù)據(jù)寫入性能。
MySQL是基于主庫的二進制日志文件內(nèi)容實現(xiàn)了主從復(fù)制功能。用戶對主數(shù)據(jù)庫的創(chuàng)建、修改、刪除等操作以及對表的增、刪、改、查操作都會記錄到二進制日志文件中,從數(shù)據(jù)庫通過I/O線程來連接主服務(wù)器,通過主服務(wù)器創(chuàng)建的新線程獲取二進制日志的內(nèi)容并拷貝到它的中繼日志當中,之后利用SQL線程從中繼日志讀取事件,并重放其中的事件而更新到從數(shù)據(jù)庫當中,使其與主數(shù)據(jù)庫中的數(shù)據(jù)保持一致[2]。
Spring框架是一個由 7個定義良好的模塊組成的分層架構(gòu)。這些Spring模塊構(gòu)建在核心容器定義了創(chuàng)建、配置和管理bean的方式的核心容器之上。Spring框架的兩大特性是“控制反轉(zhuǎn)”和“面向切面”[3]。應(yīng)用了“控制反轉(zhuǎn)”,一個對象就不需要自己創(chuàng)建或者查找依賴對象,其依賴對象會通過被動的方式傳遞進來。這樣降低了對象之間的耦合,這個特性在web框架、數(shù)據(jù)庫框架以及通過分布式框架遠程調(diào)用服務(wù)時都會用到,使得項目中各組件在開發(fā)時互相解耦合,但不影響各組件在運行時的互相調(diào)用。
Dubbo是阿里巴巴提供的、基于Java的Http Client請求、高性能的開源RPC遠程服務(wù)調(diào)用方案,通過Dubbo可以把業(yè)務(wù)邏輯分離出來,作為一個獨立的模塊,使得前端應(yīng)用能快速和穩(wěn)定地響應(yīng)請求。使用Dubbo架構(gòu)可以支撐高并發(fā)的項目,且低耦合,擴展性、穩(wěn)定性都很強[4]。
Dubbo中注冊中心負責服務(wù)地址的注冊與查找,相當于目錄服務(wù),服務(wù)提供者和消費在啟動時與注冊中心交互。服務(wù)提供者向注冊中心注冊其提供的服務(wù),服務(wù)消費者向注冊中心獲取服務(wù)提供者地址列表,并根據(jù)負載算法直接調(diào)用提供者。在項目中,我們選擇采用ZooKeeper來作為Dubbo的注冊中心[5]。
原有的訂單數(shù)據(jù)匯總功能通過在每天凌晨執(zhí)行定時任務(wù),對已配置的合作方與業(yè)務(wù)開展地區(qū)的訂單數(shù)據(jù)進行匯總,并將匯總結(jié)果保存在匯總數(shù)據(jù)表中。該方案需要反復(fù)讀取數(shù)據(jù),對數(shù)據(jù)庫IO占用較高,且在統(tǒng)計當年、當月以及累計數(shù)據(jù)時需要查詢?nèi)頂?shù)據(jù),因此查詢效率較低耗時較長,不能滿足運營人員即時查詢的需求,而且在工作時間執(zhí)行該匯總方案會影響車險購買流程的數(shù)據(jù)寫入性能。
出單即時匯總模塊在設(shè)計上需要解決以下兩點問題:
1) 即時查詢的請求多發(fā)起于工作時間,此時出單業(yè)務(wù)繁忙,會影響到下單速度。
2) 由于訂單數(shù)據(jù)表數(shù)據(jù)量過大,進行全表查詢會導(dǎo)致數(shù)據(jù)表暫時鎖死或低速響應(yīng)。
為了避免查詢時下單業(yè)務(wù)卡死,可以采用數(shù)據(jù)庫讀寫分離。訂單數(shù)據(jù)匯總操作連接到從數(shù)據(jù)庫執(zhí)行,并將用戶權(quán)限設(shè)置為只讀。主數(shù)據(jù)庫設(shè)置如下:
Server-id=1 #這是數(shù)據(jù)庫ID,此ID是唯一的,主庫默認為1
log-bin=wysq1-bin #二進制日志文件,此項為必填項,否則不能同步數(shù)據(jù)
binlog-do-db=bussiness #需要同步的數(shù)據(jù)庫,如果需要同步多個數(shù)據(jù)庫
binlog-ignore-db=account #不需要同步的數(shù)據(jù)庫
從數(shù)據(jù)庫設(shè)置如下:
Server-id=2 #這里ID改為2 因為主庫為1;
log-bin=mysq1-bin #必填項,用于數(shù)據(jù)同步;
master-host=123.56.XXX.XXX #主庫IP;
master-user=slave #同步用的賬戶
master-password=password #同步賬戶密碼;
master-port=3306 #同步數(shù)據(jù)庫的端口號。
對應(yīng)主從數(shù)據(jù)庫,需要配套負責訂單數(shù)據(jù)寫入的生產(chǎn)者以及負責訂單數(shù)據(jù)讀取的生產(chǎn)者,兩個生產(chǎn)者分別負責對主數(shù)據(jù)庫與從數(shù)據(jù)庫的操作,供消費者調(diào)用不同服務(wù)。對應(yīng)的Dubbo生產(chǎn)者配置如下:
〈!--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〉
〈!--提供方應(yīng)用信息,用于計算依賴關(guān)系--〉
〈dubbo: application name="wallet_web" owner="baobei_it" organization="baobei"〉
〈!--使用zookeeper注冊中心暴露服務(wù)地址--〉
〈dubbo: registry address="${dubbo.register} timeout="100000"/〉
〈dubbo: annotation package="com.mobisoft.wallet"/〉
即時統(tǒng)計有兩種思路:
1) 數(shù)據(jù)庫總體情況統(tǒng)計,即執(zhí)行即時統(tǒng)計功能時對于全表數(shù)據(jù)進行統(tǒng)計,這種方式邏輯簡單、統(tǒng)計結(jié)果全面但存在執(zhí)行時間隨著數(shù)據(jù)增長而變長的情況。
2) 數(shù)據(jù)庫增量數(shù)據(jù)統(tǒng)計,即記錄每次更新數(shù)據(jù)的時間點和結(jié)果,新一次查詢時只需統(tǒng)計兩次查詢之間的增量數(shù)據(jù)即可。這種方式的查詢量減小了幾個數(shù)量級,不過只能依據(jù)已配置的合作方與地區(qū)進行統(tǒng)計,對于新增合作方與地區(qū)維度的支持不佳。
由于在生產(chǎn)中存在統(tǒng)計維度變更的情況,所以我綜合兩種思路的優(yōu)劣,采用了對新增維度進行全表查詢,對已有統(tǒng)計數(shù)據(jù)的維度做增量查詢的方式,方案流程圖如圖1所示。
圖1 方案流程
為了提高匯總執(zhí)行速度并實現(xiàn)代碼邏輯與業(yè)務(wù)數(shù)據(jù)的分離,主要匯總邏輯選擇在存儲過程中實現(xiàn)。采用存儲過程有以下幾點優(yōu)點[6]:
1) 提高性能:普通SQL語句會在創(chuàng)建過程時進行分析和編譯。而存儲過程則會在首次運行一個存儲過程時預(yù)編譯,查詢優(yōu)化器對其進行分析、優(yōu)化,并將得到的最終存儲計劃保存在系統(tǒng)表中,這樣,在執(zhí)行過程時便可節(jié)省此開銷。
2) 降低網(wǎng)絡(luò)開銷:只需要提供存儲過程名和必要的參數(shù)信息就可以完成調(diào)用,從而降低了網(wǎng)絡(luò)的流量。
由于出單統(tǒng)計涉及到大量數(shù)據(jù)表的讀寫,采用存儲過程可以將這些操作在數(shù)據(jù)庫內(nèi)完成,減少了對數(shù)據(jù)庫IO的占用。
為了支持增量數(shù)據(jù)匯總功能,我在匯總數(shù)據(jù)表中增加了上次統(tǒng)計ID字段,并將數(shù)據(jù)做了垂直拆分以減少數(shù)據(jù)呈現(xiàn)時的冗余數(shù)據(jù)[7],修改后的訂單統(tǒng)計報表結(jié)構(gòu)如圖2所示。
圖2
垂直拆分之后,主表用來保存省級匯總信息,明細表用于保存次級地區(qū)匯總信息。這樣在頁面展現(xiàn)省級數(shù)據(jù)時減少冗余數(shù)據(jù)對帶寬的占用,并能有效提高查詢次級地區(qū)匯總信息時的速度。
跟據(jù)系統(tǒng)需求以及設(shè)計方案,我們設(shè)計了測試用例對出單數(shù)據(jù)即時統(tǒng)計的功能進行了測試,并記錄了系統(tǒng)相關(guān)運行參數(shù)。具體數(shù)據(jù)如表1所示。
表1 系統(tǒng)相關(guān)運行參數(shù)
從測試數(shù)據(jù)可以看到,增量匯總方案單個地區(qū)匯總耗時僅為全表匯總的1/14,且在實際操作中,執(zhí)行全合作方查詢操作用時也僅為原匯總方案的1/10。并且由于功能調(diào)整后單合作方查詢時僅需匯總對應(yīng)合作方數(shù)據(jù),所以單合作方數(shù)據(jù)匯總耗時減少到了10.6秒,大幅提升了用戶使用體驗。
本文跟據(jù)訂單數(shù)據(jù)即時匯總需求完善了其匯總數(shù)據(jù)與分類呈現(xiàn)功能,并通過數(shù)據(jù)庫讀寫分離、分別設(shè)置讀寫功能的Dubbo微服務(wù)、利用存儲函數(shù)減少數(shù)據(jù)庫IO性能壓力以及對有歷史記錄的統(tǒng)計信息進行增量統(tǒng)計的方式來對訂單數(shù)據(jù)匯總模塊性能進行優(yōu)化。系統(tǒng)測試顯示,這些優(yōu)化措施使得系統(tǒng)運行速度提升的同時也很好地改善了運營人員的使用體驗。