用Java設(shè)計實現(xiàn)多實例多庫查詢方式
服務(wù)的邊界職責(zé)
大數(shù)據(jù)層取數(shù)統(tǒng)一實現(xiàn)入口(數(shù)據(jù)源的路由,ADB/CK/HBASE... 大數(shù)據(jù)操作層數(shù)據(jù)源的路由)
支持多實例、多庫、多表的異構(gòu)數(shù)據(jù)查詢
通過 查詢 語義分析+元信息解析,拆解 查詢輸入 中的異構(gòu)數(shù)據(jù)源處理,所有異構(gòu)數(shù)據(jù)處理采用異步 Callback 方式
解決的問題
多個數(shù)據(jù)來源寫入到不同實例、不同庫中,并且一個圈選可以支持圈多個實例(不同庫)中的標(biāo)簽、事件數(shù)據(jù)
單表數(shù)據(jù)量過大,目前只能通過壓縮保留時間解決,需要可以進(jìn)行按時間段拆表分表查詢數(shù)據(jù)
單實例配置上限問題,實例升級配置是有上限的成本也很高,支持多實例后就可以使用多實例中等配置無限擴(kuò)展
無法支持場景
同一個圈選不支持跨不同類型數(shù)據(jù)庫混用,類似一個圈選包含了ADB+CK兩種類型數(shù)據(jù)庫的配置
創(chuàng)建庫表的時候,需要有原則:
不同實例的庫、表名字如果完全一樣,默認(rèn)他們代表的業(yè)務(wù)語意也是一致的,如果不一致會出現(xiàn)問題
場景描述:
架構(gòu)全景圖
傳統(tǒng)數(shù)據(jù)庫中間件的幾種模式:
種類 | 優(yōu)勢 | 缺點(diǎn) |
JAR方式嵌入到應(yīng)用端 |
|
|
中間件方式存在,偽裝數(shù)據(jù)庫代理層 |
|
|
中間件方式存在,不偽裝數(shù)據(jù)庫代理層 |
|
|
模塊說明:
模塊名 | 執(zhí)行邊界 | 輸入描述 | 輸出描述 |
代理模塊 | 模型接入 | 請求模型 | 結(jié)果模型 |
運(yùn)行模式模塊 | 執(zhí)行線程池處理 | 在線模式/離線模式 | 執(zhí)行線程池 |
計劃模塊 | 離線模式和在線模式都需要創(chuàng)建入口執(zhí)行計劃和執(zhí)行日志 離線異構(gòu)取數(shù)場景,每一層內(nèi)嵌取數(shù)都是單獨(dú)計劃和執(zhí)行日志,完成后上推執(zhí)行計劃 計劃存在父子計劃任務(wù) | 創(chuàng)建計劃 查詢可執(zhí)行計劃任務(wù)數(shù)據(jù) 更新計劃狀態(tài) | 可執(zhí)行的計劃任務(wù) |
解析模塊 | 解析查詢請求的參數(shù),轉(zhuǎn)換成 AST 語法樹 | 查詢語句,可以是 SQL,可以是 JSON 也可以是規(guī)則XML | AST語法樹對象、核心解析模型 |
權(quán)限校驗?zāi)K | 判斷token是否有使用的實例、庫、表權(quán)限 | 根據(jù)token,庫,表查詢到有權(quán)限的實例信息 | 是否有權(quán)限,以及有權(quán)限的實例信息 |
路由模塊 | 獲取有權(quán)限且最優(yōu)的實例 | 請求來源的系統(tǒng),請求來源的庫,請求來源的表 | 符合權(quán)限校驗的實例 ID+庫信息 |
連接池管理模塊 | 管理各種數(shù)據(jù)源的連接池 | 庫元信息 | 連接池連接 |
數(shù)據(jù)執(zhí)行模塊 | 取數(shù)執(zhí)行邏輯,根據(jù)改寫后的SQL進(jìn)行分實例分庫分表查詢,最終匯總到臨時表,在進(jìn)行完整原始SQL改寫的執(zhí)行 大數(shù)據(jù)量臨時表通過生成SQL執(zhí)行語句導(dǎo)入到OSS | 取數(shù)語句分析拆分后的執(zhí)行語句 | 異構(gòu)數(shù)據(jù)的話返回實例名+庫名+表名 非異構(gòu)數(shù)據(jù)返回數(shù)據(jù)結(jié)果集 |
計費(fèi)模塊 | 根據(jù)臨時表數(shù)據(jù)量,跨庫+跨表數(shù)量進(jìn)行公式化計費(fèi) | AST語法樹對象、SQL | 最終成本費(fèi)用 |
復(fù)用模塊 | 數(shù)據(jù)復(fù)用的邏輯判斷 | 執(zhí)行語句,間隔時間 | 是否復(fù)用模型 |
熔斷模塊 | 對執(zhí)行中的計劃進(jìn)行強(qiáng)制熔斷,沒有任何業(yè)務(wù)邏輯,只是提供熔斷標(biāo)示 | 計劃ID | 是否熔斷 |
項目模塊依賴描述:(開發(fā)分之:multiple-instances)(項目名:datacenter-night-watchman)
模塊間邏輯交互
- 支持單實例多庫查詢
- 支持多實例多庫查詢
- 支持單實例單庫查詢
- 異構(gòu)數(shù)據(jù)統(tǒng)一異步匯總臨時表,非異構(gòu)數(shù)據(jù)默認(rèn)實時傳輸返回
運(yùn)行模式模塊
- 在線模式(只支持非異構(gòu)取數(shù)),先落地實時計劃表,然后實時交互查詢數(shù)據(jù),返回數(shù)據(jù)
- 離線模式(支持異構(gòu)和非異構(gòu)取數(shù)),先實時創(chuàng)建父子計劃,然后返回父計劃ID,異步調(diào)度執(zhí)行計劃進(jìn)行取數(shù),接入方通過計劃ID查詢計劃狀態(tài)和異構(gòu)存儲表
- 每一個計劃都對應(yīng)一個取數(shù)任務(wù)
計劃表結(jié)構(gòu):(Mysql-watchman庫)
CREATE TABLE `extract_data_calculation_log` ( `id` bigint(32) NOT NULL, `exe_id` bigint(32) NOT NULL COMMENT '計劃表主鍵ID', `last_exe_id` bigint(32) NOT NULL COMMENT '最大用戶ID', `exe_state` tinyint(1) NOT NULL COMMENT '執(zhí)行狀態(tài),1-執(zhí)行中 2-執(zhí)行成功 3-執(zhí)行失敗', `create_time` datetime(0) NOT NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '記錄創(chuàng)建時間', `updat_time` datetime(0) NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '記錄更新時間', `version` int(8) NOT NULL COMMENT '記錄版本號', `exe_quantity` bigint(32) NULL COMMENT '數(shù)據(jù)冗余字段,執(zhí)行的數(shù)據(jù)量', PRIMARY KEY (`id`), INDEX `exe`(`circle_exe_id`) ) COMMENT = '提取數(shù)據(jù)日志表'; CREATE TABLE `extract_data_execute` ( `id` bigint(32) NOT NULL, `storage_result` json NOT NULL COMMENT '{"type":"存儲類型,1-adb 2-rmq 3-oss 4-ck","result":"adb/ck 代表表名,rmq代表topic,oss代表存儲地址","example":"實例地址","database":"庫地址"}', `create_time` timestamp(0) NOT NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '記錄創(chuàng)建時間', `updat_time` timestamp(0) NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '記錄更新時間', `plan_logic` tinyint(1) NOT NULL DEFAULT 1 COMMENT '計劃邏輯,1-正常 2-暫不執(zhí)行', `priority` int(8) NOT NULL DEFAULT 1 COMMENT '任務(wù)優(yōu)先級,數(shù)字越小優(yōu)先級越高', `data_type` tinyint(1) NOT NULL COMMENT '執(zhí)行模式,1-立即執(zhí)行 2-離線執(zhí)行', `parent_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '父級計劃ID ', `rewrite_result` json NOT NULL COMMENT '改寫模型', PRIMARY KEY (`id`) ) COMMENT = '數(shù)據(jù)取數(shù)計劃表'; ALTER TABLE `extract_data_calculation_log` ADD CONSTRAINT `exe` FOREIGN KEY (`circle_exe_id`) REFERENCES `extract_data_execute` (`id`); ALTER TABLE `extract_data_execute` ADD CONSTRAINT `config` FOREIGN KEY (`circle_config_id`) REFERENCES `circle_config` (`id`);
查詢解析模塊
- 解析模塊使用shardingjdbc5內(nèi)部的sql解析引擎,druid很久不更新了,很多新的語法支持不好
- 解析模塊代碼寫到底層工具包,包含SQL、JSON解析
Sharding5 的解析引擎已經(jīng)支持多種數(shù)據(jù)庫包含各種數(shù)據(jù)庫新增的函數(shù)語法解析,主要是Mysql、Pg、Sqlserver、Oracle
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-sql-parser-engine</artifactId> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-sql-parser-mysql</artifactId> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-infra-federation-optimizer</artifactId> </dependency> <dependency> <groupId>org.apache.calcite</groupId> <artifactId>calcite-core</artifactId> </dependency>
Clickhouse和ADB目前都是支持原生mysql協(xié)議的,那么進(jìn)入的數(shù)據(jù)庫解析方言使用mysql引擎即可
ShardingSphere解析引擎模塊代碼示例:
public static void main(String[] args) { CacheOption cacheOption = new CacheOption( 128, 1024L, 4 ); SQLParserEngine sqlParserEngine = new SQLParserEngine( "MySQL", cacheOption, true ); ParseContext parseContext = sqlParserEngine.parse( "select * from user where id in (select id from city where id = 11);", true ); SQLVisitorEngine visitorEngine = new SQLVisitorEngine( "MySQL", "STATEMENT", new Properties() ); SelectStatement selectStatement = visitorEngine.visit( parseContext ); SelectStatementConverter selectStatementConverter = new SelectStatementConverter(); SqlSelect sqlSelect = (SqlSelect) selectStatementConverter.convertToSQLNode( selectStatement ); System.out.println( sqlSelect.getSelectList() ); System.out.println( sqlSelect.getFrom() ); System.out.println( sqlSelect.getWhere() ); } 輸出結(jié)果: 查詢字段:* 查詢表:user 查詢條件:`id` IN (SELECT `id` FROM `city` WHERE `id` = 11)
解析模塊注意點(diǎn):
- 解析引擎只做原始解析,然后封裝到輸出模型中
- 解析輸出模型需要考慮嵌套SQL的模型存在以及同層級union的模型存在
- 解析輸出模型聚合根已經(jīng)調(diào)整好,缺失的字段或者模型在進(jìn)行微調(diào)
權(quán)限校驗?zāi)K:(此模塊代碼接口預(yù)留,邏輯暫不實現(xiàn))
- 有了圈選解析模型,已經(jīng)獲取到使用的實例、庫、表相關(guān)所有信息了
- 根據(jù)入?yún)oken判斷實例和庫的權(quán)限情況
- 表結(jié)構(gòu)需要有,數(shù)據(jù)需要按照規(guī)范填入,后期做代碼邏輯實現(xiàn)
- 暫時無實現(xiàn),所以默認(rèn)所有實例都可用,全部返回到模型
改寫引擎
- 負(fù)責(zé)將可能涉及到多實例、多庫、多表的聯(lián)合查詢拆分
- 拆分過程中需要考慮聯(lián)合查詢的where條件、group by 條件、order by 條件
- 如果解析模型傳遞過來的數(shù)據(jù)中,不存在跨庫場景,那么改寫引擎不進(jìn)行任何操作邏輯
改寫案例描述:
- 將復(fù)合嵌套的SQL平鋪,按照庫為單位,最內(nèi)層開始為最小粒度
- 每一層都會同時存在4中類型改寫語句
- 取數(shù)語句
- 替換符語句
- 聚合語句
- 建表語句
改寫模型描述:
路由模塊
- 通過權(quán)限模型中返回的有權(quán)限的實例,判斷最優(yōu)的CPU實例
- 根據(jù)實例+庫名去連接池模塊中獲取相應(yīng)的連接池信息
- 分庫分表邏輯后續(xù)實現(xiàn),暫不做設(shè)計
鏈接池模塊
需要支持通配符方式的連接配置,案例:
# 同一個實例下不同庫的通配連接 db.datasource.watchman.jdbcUrl=jdbc:mysql://A.mysql.rds.aliyuncs.com:3306/{db1,db2,db3} db.datasource.watchman.username={A.db1:dw_datacenter_A,A.db2:dw_datacenter_B,A.db3:dw_datacenter_C} db.datasource.watchman.password= {A.db1:password_A,A.db2:password_B,A.db3:password_C} # 不同實例下同庫的通配連接 db.datasource.watchman.jdbcUrl=jdbc:mysql://{A,B}.mysql.rds.aliyuncs.com:3306/db1 db.datasource.watchman.username={A.db1:dw_datacenter_A,B.db1:dw_datacenter_B} db.datasource.watchman.password= {A.db1:password_A,B.db2:password_B} # 同一個實例下不同庫的區(qū)間通配連接 db.datasource.watchman.jdbcUrl=jdbc:mysql://A.mysql.rds.aliyuncs.com:3306/{db[1~20]} db.datasource.watchman.username={A.db1:dw_datacenter_A,A.db2:dw_datacenter_B,A.db3:dw_datacenter_C,A.db...} db.datasource.watchman.password= {A.db1:password_A,A.db2:password_B,A.db3:password_C,A.db...} # 多數(shù)據(jù)源連接配置 db.datasource.watchman.jdbcUrl.ck=jdbc:mysql://A.mysql.rds.aliyuncs.com:3306/{db[1~20]} db.datasource.watchman.username.ck={A.db1:dw_datacenter_A,A.db2:dw_datacenter_B,A.db3:dw_datacenter_C,A.db...} db.datasource.watchman.password.ck= {A.db1:password_A,A.db2:password_B,A.db3:password_C,A.db...} db.datasource.watchman.jdbcUrl.adb=jdbc:mysql://A.mysql.rds.aliyuncs.com:3306/{db[1~20]} db.datasource.watchman.username.adb={A.db1:dw_datacenter_A,A.db2:dw_datacenter_B,A.db3:dw_datacenter_C,A.db...} db.datasource.watchman.password.adb= {A.db1:password_A,A.db2:password_B,A.db3:password_C,A.db...}
鏈接池內(nèi)部使用druid框架,沒有單獨(dú)對數(shù)據(jù)源進(jìn)行druid參數(shù)配置的話全部采用守夜人默認(rèn)提供的運(yùn)行參數(shù),如果需要單獨(dú)對不同數(shù)據(jù)源進(jìn)行配置,那么原先druid的配置加上對應(yīng)的后綴
db.datasource.watchman.jdbcUrl.adb=jdbc:mysql://A.mysql.rds.aliyuncs.com:3306/{db[1~20]} db.datasource.watchman.username.adb={A.db1:dw_datacenter_A,A.db2:dw_datacenter_B,A.db3:dw_datacenter_C,A.db...} db.datasource.watchman.password.adb= {A.db1:password_A,A.db2:password_B,A.db3:password_C,A.db...}
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot中自定義首頁(默認(rèn)頁)及favicon的方法
這篇文章主要介紹了SpringBoot中如何自定義首頁(默認(rèn)頁)及favicon,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08Spring實現(xiàn)在非controller中獲取request對象
這篇文章主要介紹了Spring實現(xiàn)在非controller中獲取request對象方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08SpringMVC中redirect重定向(帶參數(shù))的3種方式
Spring MVC中做form表單功能提交時,防止用戶客戶端后退或者刷新時重復(fù)提交問題,需要在服務(wù)端進(jìn)行重定向跳轉(zhuǎn),本文主要介紹了SpringMVC中redirect重定向(帶參數(shù))的3種方式,感興趣的可以了解一下2024-07-07Springboot中的@ConditionalOnBean注解詳細(xì)解讀
這篇文章主要介紹了Springboot中的@ConditionalOnBean注解詳細(xì)解讀,@ConditionalOnMissingBean注解兩個類,一個Computer類,一個配置類,想要完成;如果容器中沒有Computer類,就注入備用電腦Computer類,如果有Computer就不注入,需要的朋友可以參考下2023-11-11Java實現(xiàn)郵箱發(fā)送功能實例(阿里云郵箱推送)
這篇文章主要給大家介紹了關(guān)于Java實現(xiàn)郵箱發(fā)送功能的相關(guān)資料,利用阿里云郵箱推送,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09