:啟動鏈路、三層配置與分片表避坑)
簡介本資源為 Mycat2 分布式數(shù)據(jù)庫中間件的基礎安裝包面向需要在本地或服務器上搭建數(shù)據(jù)庫分片與讀寫分離架構(gòu)的 Java 開發(fā)者和運維人員用于解決單機數(shù)據(jù)庫在高并發(fā)、大數(shù)據(jù)量場景下的擴展性瓶頸。壓縮包共 51 個文件約 1.2MB以 sql 初始化腳本、so/jnilib/a/dll 等多平臺本地庫、json 與 xml 配置、bat/mycat 啟動腳本及 jar 依賴為主覆蓋 Windows、Linux、macOS、AIX、Solaris 等系統(tǒng)的 wrapper 適配文件解壓后按需修改 schemas、datasources、server 等配置即可啟動服務。已有 1072 人學習下載可作為理解數(shù)據(jù)分片規(guī)則、SQL 路由、讀寫分離與動態(tài)擴縮容的實踐起點包內(nèi)附帶的示例 SQL 與多平臺啟動腳本也便于對照排查環(huán)境問題、快速驗證部署流程。1. 拿到 mycat2 基礎安裝包先別急著 start單體 MySQL 撐到某個量級最先出問題的往往不是 SQL 寫得爛而是單表幾千萬行之后加個索引要鎖半小時、備份窗口越來越長、主從延遲一抖業(yè)務就報警。這時候團隊通常會想到分庫分表中間件mycat2 基礎安裝包就是干這個的它是一個說 MySQL 協(xié)議的代理層應用連它和連 MySQL 幾乎沒有區(qū)別分片路由、讀寫分離、全局表這些邏輯都在代理這一層做掉業(yè)務側(cè)的 JDBC 連接串改個端口就能接上去。它適合誰手上已經(jīng)有一套能跑的 MySQL、又不愿意為了分庫分表去改業(yè)務代碼里那堆 SQL 和連接的團隊。不適合誰指望著裝完就自動分片的人——mycat2 的元數(shù)據(jù)是要你自己一條條配出來的數(shù)據(jù)源、集群、邏輯庫、分片表四層東西少一層就跑不通。下面按我實際拆包的順序?qū)懴瓤窗Y(jié)構(gòu)和啟動鏈路再講三層配置怎么串然后是分片表落地和那五個必踩的坑。2. 解壓到第一次連上 8066包結(jié)構(gòu)與啟動鏈路拿到包之后第一件事不是敲 start而是把目錄看一遍。mycat2 的運行態(tài)很簡單一個 Java 進程讀 conf 下的元數(shù)據(jù)對外開一個 MySQL 協(xié)議端口把 SQL 轉(zhuǎn)發(fā)到后面真實的 MySQL 實例。所以它出問題的地方也就那么幾處——JDK 版本、conf 里的 JSON 格式、端口占用、以及后面物理庫連不連得上。把這幾處的檢查順序固定下來后面 debug 能省掉一大半時間。2.1 四個目錄各管什么解壓后基本就是 bin、conf、lib、logs 四個目錄其中 conf 是你以后動得最多的地方lib 基本不動logs 是每次啟動失敗的第一站。目錄/文件作用什么時候動它bin/啟停腳本Linux 下是 mycatWindows 下是 mycat.bat調(diào) JVM 堆大小、換啟動用戶conf/server.yaml服務端口、線程數(shù)這類進程級參數(shù)改端口、壓測時調(diào)線程池conf/datasources/每個物理 MySQL 一個 json加實例、改連接池大小conf/clusters/主從組寫節(jié)點和讀節(jié)點列表加從庫、調(diào)讀策略conf/schemas/邏輯庫和邏輯表元數(shù)據(jù)加庫、加分片表conf/users/連接代理用的賬號上線前必須改默認密碼lib/依賴 jar 和自帶的算法類換 MySQL 驅(qū)動、擴分片算法logs/mycat.log 及滾動歷史啟動失敗、路由異常一個容易忽略的點conf 下的這些 json 不是參考配置是運行時的唯一真相。你在客戶端里用命令動態(tài)建出來的數(shù)據(jù)源、集群、邏輯庫最終也是落到這幾個目錄里。所以千萬別一邊手改文件、一邊用命令建同名對象兩邊打架的時候 reload 會給你一個很難看懂的錯誤。2.2 JDK、啟動腳本與端口mycat2 是 Java 進程JDK 版本不匹配的典型表現(xiàn)是進程起來幾秒就沒了日志里只有一行看不懂的異常。所以第一步永遠是確認 java 版本再解壓。# 1. 確認 JDK8 或 11 是主流驗證過的組合別在第一次跑通前折騰太新的版本 java -version # 2. 解壓到固定目錄路徑里不要帶空格和中文 mkdir -p /opt/mycat2 unzip mycat2-basic.zip -d /opt/mycat2 cd /opt/mycat2 # 3. 先看啟動腳本里最終拼出來的 java 命令堆大小和 classpath 都在這 sed -n 1,80p bin/mycat # 4. 啟動并查狀態(tài) bin/mycat start bin/mycat status # 5. 確認端口真的監(jiān)聽了status 說 RUNNING 不代表端口起來了 ss -lntp | grep 8066這段里第 3 步是關(guān)鍵。啟動腳本本質(zhì)上就是拼一條java -cp ... 主類的命令行堆大小、GC 參數(shù)、配置文件路徑都寫在里面。第一次跑不通的時候把這條命令手工敲一遍去掉后臺重定向異常會直接打在終端上比翻日志快得多。堆大小我一般從 2g 起步代理層本身不存數(shù)據(jù)內(nèi)存主要花在連接緩沖和 SQL 解析上給太大反而拖長 GC 停頓。參數(shù)上要盯住兩個一是-Xmx二是 server.yaml 里的監(jiān)聽地址和端口。默認端口是 8066如果這臺機器上已經(jīng)有別的實例改 server.yaml 比改腳本靠譜。啟動腳本報端口已被占用的時候用ss -lntp | grep 8066找到占用進程別急著 kill先確認是不是上一次沒停干凈的自己。啟動失敗時不要從頭讀日志幾萬行讀不完# 只看最后 80 行異常棧的根因通常在最后 tail -n 80 logs/mycat.log # 直接把錯誤行撈出來Caused by 才是真正的根因 grep -nE ERROR|Exception|Caused by logs/mycat.log | tail -n 30如果 logs 下的 mycat.log 是空的去看同目錄下啟動腳本重定向產(chǎn)生的那個控制臺日志。這種情況一般是 JVM 在加載類階段就掛了業(yè)務日志框架還沒來得及初始化所以業(yè)務日志里什么都看不到——這也是為什么第 3 步手工執(zhí)行那么重要。2.3 用 mysql 客戶端驗證代理真的活了進程起來、端口監(jiān)聽只能說明它活著不能說明元數(shù)據(jù)是好的。真正的驗收標準是能用 MySQL 客戶端連上并且能看到數(shù)據(jù)源列表。# 客戶端版本別太老太老的可能在握手階段就斷連 mysql -h127.0.0.1 -P8066 -uroot -p123456-- 先看庫列表確認連的是代理而不是背后某個物理庫 show databases; -- 再看數(shù)據(jù)源這是管理命令寫在 /* */ 注釋里不是普通 SQL /* mycat:showDataSources{} */; /* mycat:showClusters{} */; /* mycat:showSchemas{} */;這三條命令返回的是代理內(nèi)存里的元數(shù)據(jù)快照能正常返回就說明配置文件解析沒問題、內(nèi)置的 prototype 數(shù)據(jù)源至少加載成功了。如果show databases只有一兩個庫、和你預期的對不上問題一定在 conf/schemas 下不在網(wǎng)絡。參數(shù)上注意兩點默認賬號密碼在 conf/users 下的 user json 里明文寫著上線前必須改掉并且要把這個賬號能訪問的邏輯庫列表一起收窄端口 8066 是服務端口管理命令走的是同一個連接不需要另開管理端口這一點和很多老版本中間件不一樣別去找 9066。3. 三層配置模型數(shù)據(jù)源、集群、邏輯庫怎么串起來mycat2 的配置之所以讓新手懵是因為它把一臺物理庫和一個邏輯庫徹底拆開了。你要先在數(shù)據(jù)源層描述清楚每臺真實的 MySQL 在哪、賬號是什么、連接池多大再在集群層把若干數(shù)據(jù)源組成一個主從組說明誰是寫、誰是讀最后在邏輯庫層給應用一個它認識的庫名并掛上這個庫的表元數(shù)據(jù)。三層缺一層SQL 就沒有完整的路由路徑。3.1 一條 SQL 到物理庫要過幾道關(guān)應用連上來的時候帶的是邏輯庫名和用戶名代理先按 users 里的配置校驗賬號、確認這個賬號能訪問哪些邏輯庫然后按邏輯庫找到它綁定的集群再按集群找到寫節(jié)點和讀節(jié)點列表最后根據(jù) SQL 是讀還是寫、表是不是分片表決定發(fā)到哪臺物理庫的哪個庫名上。這條鏈路里任何一環(huán)名字對不上報錯都長得很像——基本就是Unknown database或者連接被拒。所以配的時候有個笨辦法特別有效三個名字數(shù)據(jù)源名、集群名、邏輯庫名提前寫在紙上全程只用這一套命名別一會兒 dw0 一會兒 ds0。我見過不止一次因為手抖把 replicas 里寫了個不存在的數(shù)據(jù)源名折騰半天以為是網(wǎng)絡問題。3.2 數(shù)據(jù)源一個 json 就是一臺物理 MySQL數(shù)據(jù)源配置放在 conf/datasources 下一個文件一個實例。下面是骨架字段名以你包里自帶的樣例文件為準不同小版本可能有增刪。{ name: dw0, type: JDBC, dbType: mysql, url: jdbc:mysql://127.0.0.1:3306/db1?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/Shanghai, user: root, password: 123456, minCon: 2, maxCon: 50, maxConnectTimeout: 3000, maxRetryCount: 3, instanceType: READ_WRITE, weight: 0 }這個文件里真正需要你判斷的只有幾個字段。name是全局唯一的引用名后面集群里寫的就是它url里一定要帶serverTimezone否則時間字段跨時區(qū)會偏 8 小時這個坑排查起來極其費勁minCon和maxCon決定這個實例上常駐和峰值連接數(shù)把它乘以實例數(shù)、再乘以集群數(shù)就是你的 MySQL 端要扛的總連接數(shù)max_connections不夠就會在壓測時突然大面積超時maxConnectTimeout是建連超時內(nèi)網(wǎng) 3000 毫秒夠用跨機房要放大maxRetryCount是建連失敗重試次數(shù)從庫不穩(wěn)的時候它會救你一命但也會讓錯誤暴露得慢一點。instanceType用來標記這個實例的角色weight是讀流量權(quán)重多從庫分攤讀的時候用得上。其余字段保持樣例默認值就行別一上來就全改一遍——出問題的時候你不知道是哪個字段改壞的。3.3 集群masters 和 replicas 決定讀寫去哪集群層是很多人第一次用會寫錯的地方。masters里放的是寫節(jié)點replicas里放的是讀節(jié)點兩個列表里填的都是數(shù)據(jù)源的name不是主機名。{ name: c0, clusterType: MASTER_SLAVE, masters: [dw0], replicas: [dw1, dw2], readBalanceType: BALANCE_ALL, switchType: SWITCH, heartbeat: { replicas: SELECT 1, timeout: 1000 } }參數(shù)上有幾個邊界值得說清楚。masters只有一個元素時就是標準主從如果放多個它表達的是多個寫分片路由會再按分片規(guī)則分配別把它當成雙主同時寫。readBalanceType控制讀請求怎么分攤到 replicas全都可用的策略吞吐最好但對主從延遲最敏感如果業(yè)務里有寫完立刻查的場景要么把讀策略調(diào)保守要么讓這類查詢走事務別硬扛。switchType是主庫掛了要不要自動切測試環(huán)境建議關(guān)掉免得排查問題時主從角色自己變來變?nèi)ト罩径伎床欢?。heartbeat是探活語句和超時探活語句別寫太重的 SQLSELECT 1足夠。3.4 邏輯庫應用看到的那個庫邏輯庫是應用側(cè)唯一感知到的一層名字最好和原來的物理庫名保持一致這樣老代碼里的庫名不用動。{ schemaName: shop, targetName: c0, normalTables: {}, globalTables: {}, shardingTables: {}, customTables: {} }targetName指向集群名。下面四個空對象是四類表的容器用哪類表就往哪類里填normalTables是不分片的普通表globalTables是每個節(jié)點都存一份全量數(shù)據(jù)的字典表省份、類目、配置項這種shardingTables是分片表customTables是你要自己接管路由的表。新手最常見的誤操作是把所有表都塞進shardingTables結(jié)果字典表被切得七零八落join 的時候瘋狂跨節(jié)點。3.5 用命令在線建三條 SQL 頂三個文件除了改文件也可以在客戶端里直接建適合臨時加一個從庫驗證效果。命令寫在/* */注釋里末尾的分號不能少。-- 建數(shù)據(jù)源字段和 json 文件里的一一對應 /* mycat:createDataSource{ name:dw3, url:jdbc:mysql://127.0.0.1:3307/db1?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/Shanghai, user:root, password:123456, minCon:2, maxCon:50 } */; -- 建集群只有一個寫節(jié)點時replicas 可以留空 /* mycat:createCluster{name:c1,masters:[dw3]} */; -- 建邏輯庫掛到剛建的集群上 /* mycat:createSchema{schemaName:shop2,targetName:c1} */; -- 讓內(nèi)存里的元數(shù)據(jù)重新加載一遍 /* mycat:reload{} */;邏輯上要理解一件事這幾條命令改的是運行態(tài)元數(shù)據(jù)同時會往 conf 目錄寫對應文件所以它和手改文件是同一個數(shù)據(jù)源別兩邊同時改同一個對象。參數(shù)上和配置文件完全一致唯一要注意的是 JSON 必須寫在一行內(nèi)或者保證注釋完整透傳——有的客戶端會幫你把注釋刪掉一刪命令就退化成普通 SQL報一個莫名其妙的語法錯誤。4. 分片表落地shardingTables 骨架、分片鍵選型與落位驗證單庫單表跑通之后真正的工作量在分片表上。分片表的本質(zhì)是一條邏輯表名 一套路由函數(shù) 一組物理節(jié)點三者必須嚴格對齊節(jié)點數(shù)要和路由函數(shù)的參數(shù)匹配建表語句要和物理庫里的實際結(jié)構(gòu)一致。這兩條對不上表現(xiàn)是數(shù)據(jù)能插進去但查不出來或者干脆建表就報錯。4.1 分片表在配置里的骨架分片表掛在邏輯庫 json 的shardingTables下面鍵是邏輯表名。shardingTables: { order_detail: { createTableSQL: CREATE TABLE order_detail (id bigint NOT NULL, order_id bigint NOT NULL, user_id bigint NOT NULL, amount decimal(12,2) DEFAULT NULL, PRIMARY KEY (id), KEY idx_user (user_id)) ENGINEInnoDB DEFAULT CHARSETutf8mb4, function: { clazz: io.mycat.router.mycat1xfunction.PartitionByLong, name: partitionByLong, properties: { partitionCount: 2, partitionLength: 2 } }, shardingNodes: [c0_0.order_detail, c0_1.order_detail] } }四個字段各有分工。createTableSQL是建表模板代理在初始化時按它去物理庫建表所以它必須和你線上真實表結(jié)構(gòu)逐字一致包括字符集和索引——線上表加了個字段忘了同步這里重啟之后新節(jié)點建出來的表就少一列。function.clazz是路由算法類從 lib 目錄里包里自帶的那批io.mycat.router.開頭的類里選除非你非常清楚自己在做什么別自己寫一個。function.properties的鍵名跟著算法走取模類算法一般要分片鍵列名和分片數(shù)范圍類算法一般要列名和區(qū)間邊界具體以包里樣例為準。shardingNodes的寫法要特別注意它是節(jié)點名.表名而節(jié)點名的形式取決于你的集群怎么定義單主從集群里直接寫數(shù)據(jù)源名dw0.order_detail多寫節(jié)點的分片集群里是集群名加序號c0_0.order_detail、c0_1.order_detail。這兩種寫法在不同小版本、不同集群形態(tài)下不一樣最穩(wěn)的做法是打開 conf/schemas 下自帶的樣例文件照著它的形式寫別憑記憶敲。4.2 分片鍵和算法怎么選選算法之前先看查詢長什么樣別看數(shù)據(jù)長什么樣。分片鍵必須是絕大多數(shù)查詢都會帶上的那個條件否則每次查詢都要廣播到所有分片代理層的優(yōu)勢直接歸零。算法類型適合的分片鍵擴容代價典型翻車點取模/哈?;鶖?shù)高、寫入分散如 user_id、order_id高改分片數(shù)要整體重分布分片鍵為 NULL 或字符串大小寫不一致落到默認節(jié)點范圍/區(qū)間有時間語義如月份、號段低加區(qū)間即可冷熱不均新數(shù)據(jù)全壓最后一個分片自定義節(jié)點數(shù)會頻繁變動的場景中算法類改完 reload存量數(shù)據(jù)路由對不上我自己總結(jié)的三條硬標準第一分片鍵基數(shù)要足夠大性別、狀態(tài)這種字段分片等于沒分第二核心查詢的 where 里必須帶分片鍵報表類全表掃描的查詢要單獨規(guī)劃走不走代理第三分片鍵一旦寫入就不要更新跨分片改分片鍵等于一條 update 變兩條跨庫事務代理層幫不了你。三條都滿足再動手配比配完再改省事得多。4.3 三步驗證數(shù)據(jù)真的落對了配置寫完reload 之后必須驗證別信沒報錯就是對的。驗證分三步從代理側(cè)寫數(shù)據(jù)、從代理側(cè)讀回來、再到每個物理庫對數(shù)。# 第一步和第二步從代理寫入并讀回走的是應用的視角 mysql -h127.0.0.1 -P8066 -uroot -p123456 shop -e insert into order_detail(id, order_id, user_id, amount) values (1,1001,1,9.90),(2,1002,2,19.90),(3,1003,3,29.90); select count(*) as proxy_total from order_detail;# 第三步到每個物理庫分別數(shù)一遍兩邊的和要等于代理上看到的總數(shù) for p in 3306 3307; do mysql -h127.0.0.1 -P$p -uroot -p123456 -N -e \ select $p as port, count(*) from db1.order_detail; done第一段的邏輯是確認代理層的讀寫鏈路是通的proxy_total是代理聚合后的結(jié)果。第二段的關(guān)鍵在于分布而不是總數(shù)如果兩個物理庫各 3 行、但某個庫里出現(xiàn)了本該屬于另一個分片的分片鍵說明路由函數(shù)參數(shù)寫錯了如果總數(shù)對、但其中一個是 0說明shardingNodes只寫了一個節(jié)點或者分片數(shù)配成了 1。這種錯誤不會報異常只會在數(shù)據(jù)量漲起來之后以某個庫磁盤爆了的形式暴露所以每次改分片配置都要跑一遍。4.4 哪些表不該分片字典表、配置表、類目表這類數(shù)據(jù)量小、讀多寫少、又經(jīng)常被 join 的表放globalTables里每個節(jié)點存一份全量join 就在本地完成。業(yè)務主表和它的明細表如果總是按同一個維度查訂單和訂單明細都按 user_id 查把兩張表的shardingNodes一一對齊保證同一份數(shù)據(jù)落在同一個節(jié)點join 就不會跨庫。反過來任何需要跨分片聚合的查詢都要提前想清楚count(*)、sum、深分頁、多表 join在代理層的代價遠高于單庫。這些查詢要么在業(yè)務側(cè)改成按分片鍵查、要么單獨走一條不經(jīng)過代理的報表鏈路別指望中間件替你解決。5. 避坑與排查五個高頻翻車現(xiàn)場下面這五條是我自己在不同環(huán)境里反復遇到的按現(xiàn)象 → 原因 → 解決寫遇到問題時可以按順序?qū)φ铡?.1 啟動本文還有配套的精品資源點擊獲取