設計了一個自動歸檔工具

背景

隨着業務的發展,一些事務類表(源源不斷產生業務數據)會越來越大,最終演變成我們說的大表,普通的查詢可能毫秒級、秒級返回,但是稍微複雜的就會超時,甚至佔滿數據庫cpu,進而導致大面積請求超時、堆積,jvm fullgc,觸發熔斷等連鎖反應。

前幾天業務高峯期的時候收到客戶反饋,說系統訪問卡頓,已經嚴重影響業務,需要立即處理,根據以往的經驗我第一時間查看了數據庫監控,果不其然cpu 100%。

 

 

爲了快速恢復業務,我將運行時間較長的會話kill掉。

事後分析了事發前的慢查語句,其中有一條查詢操作日誌的sql引起了我的注意,類似於select * from op_log where create_time between xxx  and  yyy and log like '%keyword%',我一看op_log這個表已經超過2000w行數據,這麼查肯定會查死啊,往羣裏一發老大立馬回覆到“這個表是有歸檔的,一般只保留幾個月的,不應該有這麼大,需要跟蹤一下”。

我查了代碼,的確是有一個歸檔功能,簡單來說就是定時(每月一次)執行以下三行邏輯:
1.List<OpLog> opLogs = select * from op_log where create_time<當前時間-3個月

2.for each insert into op_log_history values xxx,yyy,zzz 

3.delete from op_log where create_time<當前時間-3個月

 

那爲什麼還會有這麼多的數據呢,訪問人數並沒有明顯增加,帶着疑問我搜索了9月1號的執行日誌,原來是執行的時候發生了OOM,所以當次也就失敗了,由於8月份的日誌已經刪除,所以不知道8月1號的執行情況,猜測應該也失敗了,因爲這個歸檔功能做的實在是太過於簡陋,初看就有以下兩個明顯缺點:

1.select * from op_log where create_time<當前時間-3個月 一次性查詢所有滿足歸檔條件的數據,很容易佔滿jvm內存;

2.使用Spring的Schedule實現,沒有加任何重試、報警機制,不滿足系統可觀測性原則。

 

期望

第一時間調研了一些現成的解決方案,比如pt-archiver,優點是功能完善而且運行時間較久穩定性高,缺點是隻侷限於一些主流的數據庫,而有些客戶採購了一些較爲小衆的數據庫,pt-archiver這類工具並不能覆蓋所有場景,鑑於此我們希望自己造輪子,針對不同的數據庫做簡單的適配改造即可,不至於被第三方工具牽絆,查閱了一些資料,常規的數據歸檔方式如下:

  • 開發:寫個轉儲邏輯、寫個清理邏輯,部署在某個應用服務器,週期調度這段代碼。

  • DBA/運維:寫個轉儲SQL、寫個清理SQL,提交crontab部署在數據庫服務器,週期調度這個腳本。

常規的歸檔方式存在以下不足:

  • 每個業務表都需要重複一次這樣的開發與配置。

  • 無法有效全局管控,如遇到重大活動、變更等重要窗口無法有效的暫停任務的調度。

  • 任務未有效調度時無法及時、有效的通知介入,容易造成在線表數據量過大的問題降級服務性能。

  • 執行日誌無法統一管理,有效溯源查看。

工作流程

針對上面提到的常規的歸檔方式做了一些改進,將歸檔這個動作做了相應的抽象,不需要每個業務表都重複開發一套歸檔邏輯,只需要簡單的配置即可形成一個歸檔任務,最終將歸檔任務同步給xxl-job,這樣就可以複用xxl-job的調度、故障重試、報警、查看執行日誌等功能。

 

 

 

 

組件設計

控制端

功能列表如下:

1.歸檔任務創建、查看、刪除;

2.歸檔歷史查看、手動執行;

3.歸檔任務導出、導入。

 

任務列表

 

 

創建&編輯任務

 

 

查看任務執行歷史(複用xxx-job)

 

 

表關係e-r圖

 

 目標表支持固定表和動態表兩種類型,當類型爲動態表時,需要指定動態表生成規則,目前支持“每月一張”和“每年一張”兩種生成規則,假設目標表名爲dst_table,下面表格列出不同組合下的目標表。

目標表名

目標表類型

動態表生成規則

最終目標表

dst_table

固定表

NA

dst_table

dst_table

動態表

每月一張

dst_table_yyyyMM,比如dst_table_202201

dst_table

動態表

每年一張

dst_table_yyyy如dst_table_2022

 

歸檔任務處理器

功能列表如下:

1.根據表達式創建歸檔表;

2.根據歸檔條件拉取歸檔數據並插入目標表;

3.執行後置行爲,如刪除原表數據等;

4.記錄歸檔日誌。

 

涉及到的一些實現細節

1.源表必須要有主鍵

這個主要是考慮異常情況下的重試功能,我們的歸檔邏輯是先插入目標表然後刪除源表,中間如果出現異常情況,可能會出現已經插入到目標表但是沒有從源表刪除的情況,下一次執行就會出現目標表中數據重複的情況,有了主鍵就可以利用數據庫的一些特性來規避,比如mysql中的insert ingore into,也許有人會問爲什麼不引入事務機制保證插入和刪除的ACID特性呢,主要是怕麻煩,因爲代碼運行在spring+mybatis框架之下,開啓事務就要引入事務管理器那一套東西,倒不如用一些巧方法規避過去。

通過DatabaseMetaData可以獲取到表的主鍵信息
DatabaseMetaData.getPrimaryKeys

2.歸檔條件的校驗

歸檔條件是開發手動錄入的sql,難免有手抖的情況,最大的風險在於漏加where條件,所以歸檔條件必須是要校驗的,這裏藉助了jsqlparser框架解析sql,判斷是否包含where。

String archivecondition = "select * from test_table";
Statements statements = CCJSqlParserUtil.parseStatements(archivecondition);
List<Statement> statementList = statements.getStatements();
for(Statement statement : statementList) {
    if (!(statement instanceof Select)) {
       throw new Exception("歸檔條件只支持select");
    }

    Select select = (Select) statement;
    PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
    Expression expression = plainSelect.getWhere();
    Assert.isNull(expression,"歸檔條件不包含where");

}

3.運行一段時間後源表和目標表字段不一致導致保存失敗  

 

 由於業務發展過程中源表中增加了新字段,但是目標表由於是歸檔處理器自動創建,開發人員一般不會同步增加字段,這就導致歸檔失敗,也有開發跟我提過需求:“歸檔處理器能不能識別到這類異常自動補全缺失的字段”,我的回答是這類問題由人工處理,主要考慮到目標表經過長時間的運行可能已經變得異常龐大,貿然的加字段必然引起數據庫的不穩定,線上大表的變更一定要謹慎,多和dba溝通,儘量選擇夜深人靜的時候

 

總結

看似一個小功能也要多方面考慮,性能、兼容性、普適性、易用性、可觀測性等等都值得我們深入推敲,想清楚了再幹,當你抱怨CRUD沒有技術含量的時候,就應該考慮怎麼把CRUD做出一朵花出來,這個歸檔功能就是一個再明顯不過的CRUD了。

 拍攝於陝西張裕瑞那城堡酒莊
 

  

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章