態(tài)路由與一致性保障實(shí)戰(zhàn)指南)
1. 這不是簡(jiǎn)單的“主從切換”而是數(shù)據(jù)路由的精密編排很多人第一次聽(tīng)說(shuō) ShardingSphere 的讀寫分離功能下意識(shí)會(huì)把它等同于“配置個(gè)主庫(kù)、配幾個(gè)從庫(kù)、寫操作走主、讀操作自動(dòng)分發(fā)到從”——聽(tīng)起來(lái)很美實(shí)操起來(lái)卻頻頻翻車。我見(jiàn)過(guò)太多團(tuán)隊(duì)在壓測(cè)階段突然發(fā)現(xiàn)明明配置了三臺(tái)從庫(kù)90% 的讀請(qǐng)求卻全打在第一臺(tái)從庫(kù)上也見(jiàn)過(guò)業(yè)務(wù)上線后某天凌晨監(jiān)控告警瘋狂刷屏查下來(lái)是某個(gè)復(fù)雜 JOIN 查詢被錯(cuò)誤地路由到了從庫(kù)而該從庫(kù)因延遲積壓了 37 秒導(dǎo)致訂單狀態(tài)頁(yè)面大面積超時(shí)。這些都不是配置沒(méi)填對(duì)而是對(duì) ShardingSphere 讀寫分離背后那套動(dòng)態(tài)路由決策鏈缺乏基本認(rèn)知。ShardingSphere 的讀寫分離本質(zhì)不是靜態(tài)分流而是一套嵌入在 SQL 解析、執(zhí)行計(jì)劃生成、連接池管理全流程中的上下文感知型路由引擎。它不只看“SELECT 還是 INSERT”還要判斷這條 SQL 是否涉及未同步完成的寫后讀Read-After-Write、是否包含跨庫(kù)關(guān)聯(lián)比如JOIN t_order ON t_user.id t_order.user_id、是否命中了強(qiáng)制主庫(kù)的 Hint如/* shardingSphere: hint: writeRouteOnlytrue */甚至還要結(jié)合當(dāng)前各從庫(kù)的實(shí)時(shí)延遲、連接數(shù)、負(fù)載水位做加權(quán)調(diào)度。這已經(jīng)超出了傳統(tǒng)中間件“簡(jiǎn)單代理”的范疇更接近一個(gè)輕量級(jí)的數(shù)據(jù)庫(kù)流量治理中樞。關(guān)鍵詞里雖然沒(méi)寫但實(shí)際落地中繞不開(kāi)三個(gè)核心概念邏輯庫(kù)Logical Database、數(shù)據(jù)源Data Source和負(fù)載均衡策略Load-Balance Algorithm。邏輯庫(kù)是你在應(yīng)用代碼里寫的jdbc:mysql://xxx/sharding_db它背后映射的是一個(gè)主庫(kù) N 個(gè)從庫(kù)的物理集合數(shù)據(jù)源則是每個(gè)物理庫(kù)的 JDBC 連接定義而負(fù)載均衡策略才是決定“哪條 SELECT 最終落到哪個(gè)從庫(kù)”的最終裁決者。很多人卡在第一步就是誤以為只要把master-slave-rules配好就萬(wàn)事大吉卻忽略了策略選型本身就是一個(gè)需要反復(fù)驗(yàn)證的工程決策。比如輪詢ROUND_ROBIN看似公平但在高并發(fā)短連接場(chǎng)景下由于連接池復(fù)用率低實(shí)際效果可能遠(yuǎn)不如基于響應(yīng)時(shí)間的WEIGHT策略穩(wěn)定。提示ShardingSphere 并不會(huì)主動(dòng)探測(cè)從庫(kù)延遲。它依賴你配置的replica-query-delay-threshold-ms參數(shù)默認(rèn) 0僅當(dāng)顯式開(kāi)啟并設(shè)置閾值后才會(huì)在路由前檢查SHOW SLAVE STATUS中的Seconds_Behind_Master。這意味著如果你沒(méi)開(kāi)這個(gè)開(kāi)關(guān)哪怕從庫(kù)已落后 5 分鐘ShardingSphere 依然會(huì)照常把讀請(qǐng)求發(fā)過(guò)去——故障不是出在配置而是出在對(duì)機(jī)制的誤判。我曾在某電商促銷系統(tǒng)中遇到一個(gè)典型反例團(tuán)隊(duì)為追求極致吞吐將load-balance-algorithm-type設(shè)為RANDOM理由是“避免熱點(diǎn)”。結(jié)果大促開(kāi)始 12 分鐘后監(jiān)控顯示一臺(tái)從庫(kù) CPU 持續(xù) 98%其余兩臺(tái)不到 30%。排查發(fā)現(xiàn)RANDOM算法在連接池初始化階段會(huì)一次性創(chuàng)建大量連接而該從庫(kù)恰好位于網(wǎng)絡(luò)延遲最低的可用區(qū)DNS 解析優(yōu)先返回其 IP導(dǎo)致連接池初始連接大量集中于此。這不是算法缺陷而是沒(méi)理解RANDOM在連接建立階段的冷啟動(dòng)行為。后來(lái)我們切回WEIGHT并為每臺(tái)從庫(kù)配置了基于歷史 P95 延遲計(jì)算出的權(quán)重值如weight: 80,65,55負(fù)載才真正均勻下來(lái)。2. 配置不是填空題而是對(duì)數(shù)據(jù)一致性的契約聲明ShardingSphere 的 YAML 配置文件表面看是幾組 key-value實(shí)則是一份關(guān)于“你愿意為讀性能犧牲多少一致性”的正式契約。很多團(tuán)隊(duì)把sharding-sphere.yaml當(dāng)成數(shù)據(jù)庫(kù)連接字符串的擴(kuò)展版只填master-data-source-name和slave-data-source-names就提交上線結(jié)果在業(yè)務(wù)高峰期遭遇不可預(yù)知的臟讀。這不是框架 bug是你在配置里悄悄簽下了“允許讀取任意延遲從庫(kù)數(shù)據(jù)”的免責(zé)條款而自己渾然不覺(jué)。我們先拆解最核心的master-slave-rules區(qū)塊。它至少包含四個(gè)必須審慎決策的字段name邏輯數(shù)據(jù)源名稱如ds_master_slave。這個(gè) name 會(huì)出現(xiàn)在你的DataSourceBean 名稱和 SQL 日志里建議帶環(huán)境后綴如ds_master_slave_prod避免測(cè)試環(huán)境配置誤刷生產(chǎn)。master-data-source-name主庫(kù)數(shù)據(jù)源名必須與>load-balance-algorithm: type: WEIGHT props: # 權(quán)重值需為正整數(shù)總和無(wú)限制框架按比例分配 # 此處權(quán)重比為 4:3:2意味著 A 承擔(dān)約 44% 流量B 33%C 22% ds_slave_0: 4 ds_slave_1: 3 ds_slave_2: 2這個(gè)props下的鍵名必須與slave-data-source-names中的名稱完全匹配。我曾幫某金融客戶排查過(guò)一個(gè)詭異問(wèn)題他們配置了slave-data-source-names: ds_slave_a,ds_slave_b但props里寫的是slave_a: 5, slave_b: 3結(jié)果所有讀請(qǐng)求 100% 走了主庫(kù)。原因很簡(jiǎn)單——ShardingSphere 在WEIGHT算法初始化時(shí)遍歷props的 key發(fā)現(xiàn)slave_a不在slave-data-source-names列表中于是整個(gè)權(quán)重配置失效自動(dòng)降級(jí)為ROUND_ROBIN而ROUND_ROBIN又因列表為空slave-data-source-names解析失敗最終 fallback 到主庫(kù)。這種錯(cuò)誤不會(huì)報(bào)配置異常只會(huì)靜默降級(jí)非常隱蔽。注意WEIGHT算法的權(quán)重值是靜態(tài)的無(wú)法根據(jù)從庫(kù)實(shí)時(shí)負(fù)載動(dòng)態(tài)調(diào)整。如果你需要真正的自適應(yīng)負(fù)載均衡必須自行實(shí)現(xiàn)LoadBalanceAlgorithm接口并在props中指定type: com.xxx.CustomWeightAlgorithm。我們內(nèi)部封裝了一個(gè)基于 Prometheus 拉取node_exporterCPU/Network 指標(biāo) 本地緩存SHOW SLAVE STATUS延遲的動(dòng)態(tài)權(quán)重算法上線后從庫(kù) CPU 方差從 ±35% 降至 ±8%。另一個(gè)關(guān)鍵配置是props下的sql-show和executor-size。前者控制是否打印重寫后的 SQL調(diào)試必備但生產(chǎn)必須關(guān)后者決定底層ExecutorService的線程池大小。很多人設(shè)為200以為越大越好結(jié)果 GC 頻繁。真實(shí)經(jīng)驗(yàn)是executor-size應(yīng) ≈ 主庫(kù)最大連接數(shù) × 1.2。因?yàn)?ShardingSphere 的 SQL 執(zhí)行是異步的線程池負(fù)責(zé)分發(fā)任務(wù)而非直接執(zhí)行 SQL。線程數(shù)過(guò)多反而導(dǎo)致上下文切換開(kāi)銷劇增。我們?cè)谝粋€(gè) 32 核服務(wù)器上將executor-size從 200 降到 48 后P99 延遲下降了 22ms。3. SQL 路由的隱性規(guī)則哪些查詢注定無(wú)法走從庫(kù)ShardingSphere 的讀寫分離不是“所有 SELECT 都走從庫(kù)”它有一套嚴(yán)格的 SQL 語(yǔ)義分析規(guī)則決定某條查詢是否具備“可路由到從庫(kù)”的資格。很多開(kāi)發(fā)者抱怨“配置明明生效了但監(jiān)控里從庫(kù)流量幾乎為零”根本原因往往是業(yè)務(wù) SQL 觸發(fā)了框架的“安全熔斷”機(jī)制。這不是 Bug而是設(shè)計(jì)者對(duì)數(shù)據(jù)一致性的敬畏。我們來(lái)逐條拆解那些會(huì)讓 SQL強(qiáng)制路由到主庫(kù)的典型場(chǎng)景3.1 寫后讀Read-After-Write場(chǎng)景的自動(dòng)保護(hù)這是最常被忽視的隱性規(guī)則。假設(shè)用戶剛完成一筆支付代碼中緊跟著查詢?cè)撚唵巫钚聽(tīng)顟B(tài)// 第一步更新訂單狀態(tài)為 PAID orderMapper.updateStatus(orderId, PAID); // 第二步立即查詢?cè)撚唵卧斍楹钚聽(tīng)顟B(tài) Order order orderMapper.selectById(orderId);即使第二步是純 SELECTShardingSphere 也會(huì)將其路由到主庫(kù)。原理在于框架在事務(wù)上下文中維護(hù)了一個(gè)writeDataSourceNames緩存記錄本次事務(wù)中發(fā)生過(guò)寫操作的數(shù)據(jù)源名稱。當(dāng)后續(xù) SELECT 執(zhí)行時(shí)它會(huì)檢查該 SQL 的table和where條件是否與緩存中的寫操作存在強(qiáng)關(guān)聯(lián)如相同主鍵、相同分片鍵。一旦匹配立即觸發(fā)“寫后讀保障”強(qiáng)制走主庫(kù)。這個(gè)機(jī)制默認(rèn)開(kāi)啟無(wú)法關(guān)閉——因?yàn)殛P(guān)閉意味著你主動(dòng)放棄強(qiáng)一致性。驗(yàn)證方法很簡(jiǎn)單在sharding-sphere.yaml中開(kāi)啟sql-show: true觀察日志。你會(huì)看到類似這樣的輸出Actual SQL: ds_master ::: SELECT * FROM t_order WHERE order_id ?注意Actual SQL后面的ds_master這就是路由決策的鐵證。如果期望它走從庫(kù)唯一辦法是打破事務(wù)邊界將更新和查詢拆成兩個(gè)獨(dú)立事務(wù)或在查詢前手動(dòng)清除上下文不推薦易引發(fā)其他一致性問(wèn)題。3.2 復(fù)雜 DML 語(yǔ)句的副作用INSERT ... SELECT、UPDATE ... JOIN、DELETE ... LIMIT這類語(yǔ)句表面是讀操作實(shí)則隱含寫意圖。ShardingSphere 的 SQL 解析器基于 Antlr4會(huì)識(shí)別其 AST抽象語(yǔ)法樹(shù)中的InsertSelectStatement、UpdateStatement等節(jié)點(diǎn)類型一旦檢測(cè)到直接標(biāo)記為“寫操作”路由到主庫(kù)。某次我們優(yōu)化報(bào)表導(dǎo)出將一個(gè)SELECT COUNT(*) FROM t_log WHERE create_time ?改為INSERT INTO t_report_cache SELECT ...結(jié)果導(dǎo)出接口 QPS 斷崖式下跌——因?yàn)樗袑?dǎo)出請(qǐng)求都擠在主庫(kù)上。解決方案是改用物化視圖或定時(shí)任務(wù)預(yù)計(jì)算而非在查詢路徑中混入寫邏輯。3.3 Hint 強(qiáng)制指令的絕對(duì)權(quán)威/* shardingSphere: hint: writeRouteOnlytrue */這類注釋是 ShardingSphere 提供的“上帝模式”。它會(huì)覆蓋所有路由規(guī)則強(qiáng)制走主庫(kù)。我們?cè)盟鉀Q一個(gè)棘手問(wèn)題某第三方支付回調(diào)接口要求 500ms 內(nèi)返回 HTTP 200但回調(diào)后需立即查詢訂單并觸發(fā)發(fā)貨。由于發(fā)貨服務(wù)依賴強(qiáng)一致數(shù)據(jù)我們就在回調(diào) SQL 前加上此 Hint確保萬(wàn)無(wú)一失。但要注意Hint 是侵入式方案必須修改業(yè)務(wù)代碼且容易被后續(xù)開(kāi)發(fā)者遺忘。我們內(nèi)部約定所有含 Hint 的 SQL 必須在代碼旁添加// TODO: 待從庫(kù)延遲 100ms 后移除注釋并接入 SonarQube 掃描對(duì)超過(guò) 30 天未清理的 Hint 發(fā)送企業(yè)微信告警。3.4 全局臨時(shí)表Global Temporary Table的陷阱ShardingSphere 支持通過(guò)CREATE GLOBAL TEMPORARY TABLE創(chuàng)建全局臨時(shí)表其數(shù)據(jù)在會(huì)話間共享。但這類表的讀寫操作框架一律路由到主庫(kù)。原因是臨時(shí)表元數(shù)據(jù)只存在于主庫(kù)從庫(kù)無(wú)法保證其結(jié)構(gòu)一致性。某次數(shù)據(jù)遷移腳本中開(kāi)發(fā)同學(xué)為加速處理創(chuàng)建了tmp_user_batch臨時(shí)表存放待處理用戶 ID然后SELECT * FROM tmp_user_batch JOIN t_user—— 結(jié)果整個(gè) JOIN 查詢被路由到主庫(kù)拖垮了主庫(kù) IO。教訓(xùn)是臨時(shí)表只應(yīng)用于極簡(jiǎn)場(chǎng)景如單表INSERT/SELECT嚴(yán)禁用于復(fù)雜關(guān)聯(lián)查詢。4. 延遲監(jiān)控不是錦上添花而是故障預(yù)警的生命線在 ShardingSphere 讀寫分離架構(gòu)中從庫(kù)延遲Replication Lag是比 CPU、內(nèi)存更關(guān)鍵的黃金指標(biāo)。CPU 90% 可能只是暫時(shí)抖動(dòng)但從庫(kù)延遲突破閾值意味著你正在向業(yè)務(wù)輸送過(guò)期數(shù)據(jù)。很多團(tuán)隊(duì)直到用戶投訴“剛付款訂單狀態(tài)還是待支付”才去查延遲此時(shí)故障已發(fā)生數(shù)小時(shí)。一套可靠的延遲監(jiān)控體系必須覆蓋三個(gè)層面SQL 層、連接層、系統(tǒng)層。4.1 SQL 層SHOW SLAVE STATUS的精準(zhǔn)解析這是最直接的延遲來(lái)源。ShardingSphere 默認(rèn)通過(guò)執(zhí)行SHOW SLAVE STATUS獲取Seconds_Behind_Master字段。但這個(gè)值有嚴(yán)重局限它只反映 IO 線程的延遲不包含 SQL 線程的執(zhí)行耗時(shí)。在高并發(fā)寫入場(chǎng)景下SQL 線程可能積壓大量事件Seconds_Behind_Master卻顯示為 0。我們因此開(kāi)發(fā)了一個(gè)增強(qiáng)版探針在SHOW SLAVE STATUS基礎(chǔ)上額外執(zhí)行SELECT UNIX_TIMESTAMP() - UNIX_TIMESTAMP(apply_time) FROM performance_schema.replication_applier_status_by_coordinatorMySQL 5.7直接獲取 SQL 線程的實(shí)際應(yīng)用時(shí)間差。這個(gè)值比Seconds_Behind_Master平均早暴露故障 4.2 分鐘。監(jiān)控配置的關(guān)鍵參數(shù)是replica-query-delay-threshold-ms。它的單位是毫秒但含義常被誤解。它不是“延遲超過(guò)此值就拒絕讀請(qǐng)求”而是“延遲超過(guò)此值時(shí)該從庫(kù)不參與負(fù)載均衡”。也就是說(shuō)如果閾值設(shè)為10001秒而某從庫(kù)延遲為1500msShardingSphere 會(huì)將其從slave-data-source-names列表中臨時(shí)剔除后續(xù)請(qǐng)求只在剩余從庫(kù)中分配。這個(gè)機(jī)制是軟性的不會(huì)導(dǎo)致請(qǐng)求失敗但會(huì)加劇其他從庫(kù)壓力。我們的生產(chǎn)配置是500配合告警策略延遲 500ms 持續(xù) 30 秒企業(yè)微信告警 1000ms 持續(xù) 10 秒電話告警。4.2 連接層連接池健康度的隱形殺手從庫(kù)延遲不僅來(lái)自 MySQL 自身更常源于連接池配置不當(dāng)。HikariCP 是 ShardingSphere 官方推薦的連接池但其connection-timeout和validation-timeout參數(shù)若設(shè)置不合理會(huì)引發(fā)連鎖反應(yīng)。例如connection-timeout: 3000030秒意味著當(dāng)從庫(kù)因高負(fù)載無(wú)法及時(shí)響應(yīng)建連請(qǐng)求時(shí)HikariCP 會(huì)等待整整 30 秒才拋異常。這 30 秒內(nèi)所有新請(qǐng)求都會(huì)堆積在連接池隊(duì)列里造成雪崩。我們的標(biāo)準(zhǔn)配置是hikari: connection-timeout: 3000 # 3秒超時(shí)快速失敗 validation-timeout: 2000 # 2秒驗(yàn)證超時(shí) idle-timeout: 600000 # 10分鐘空閑回收 max-lifetime: 1800000 # 30分鐘最大生命周期規(guī)避 MySQL wait_timeout特別注意max-lifetime。MySQL 默認(rèn)wait_timeout288008小時(shí)但云廠商 RDS 常將其調(diào)低至 300 秒5分鐘。如果 HikariCP 連接存活時(shí)間超過(guò)此值連接會(huì)被 MySQL 主動(dòng)斷開(kāi)而 HikariCP 不會(huì)立即感知導(dǎo)致后續(xù)請(qǐng)求拿到無(wú)效連接報(bào)Connection reset錯(cuò)誤。我們將max-lifetime設(shè)為wait_timeout - 60秒留出緩沖窗口。4.3 系統(tǒng)層網(wǎng)絡(luò)與磁盤 I/O 的底層真相有時(shí)SHOW SLAVE STATUS顯示延遲為 0但業(yè)務(wù)仍感知到數(shù)據(jù)不一致。這時(shí)必須深入系統(tǒng)層。我們用pt-heartbeat工具在主庫(kù)定時(shí)插入心跳記錄在從庫(kù)查詢?cè)撚涗浀臅r(shí)間差這是業(yè)界公認(rèn)的最準(zhǔn)延遲測(cè)量法。同時(shí)我們監(jiān)控從庫(kù)的iostat -x 1輸出重點(diǎn)關(guān)注await平均IO等待時(shí)間和%util設(shè)備利用率。當(dāng)%util持續(xù) 95% 且await 50ms時(shí)基本可判定是磁盤 I/O 瓶頸。某次故障中從庫(kù)%util達(dá)到 99.8%但Seconds_Behind_Master為 0pt-heartbeat卻顯示延遲 8.3 秒。根因是 RAID 卡電池老化寫緩存被禁用所有寫操作變?yōu)橥铰浔P拖垮了 SQL 線程執(zhí)行速度。更換 RAID 卡后延遲歸零。提示不要依賴單一監(jiān)控指標(biāo)。我們構(gòu)建了一個(gè)“延遲健康度評(píng)分卡”綜合Seconds_Behind_Master、pt-heartbeat延遲、iostat await、HikariCP activeConnections四個(gè)維度加權(quán)計(jì)算出 0-100 分。分?jǐn)?shù) 60 時(shí)自動(dòng)觸發(fā)預(yù)案將該從庫(kù)權(quán)重臨時(shí)設(shè)為 0并通知 DBA 緊急介入。5. 故障復(fù)盤一次從庫(kù)全掛的真實(shí)排查鏈路去年雙十二前壓測(cè)我們?cè)庥隽艘淮蔚湫偷摹皬膸?kù)集體失聯(lián)”事件?,F(xiàn)象是所有讀請(qǐng)求 100% 路由到主庫(kù)主庫(kù) CPU 爆表從庫(kù)監(jiān)控顯示連接數(shù)為 0但SHOW SLAVE STATUS一切正常。整個(gè)排查過(guò)程耗時(shí) 47 分鐘最終定位到一個(gè)反直覺(jué)的根源。我把完整鏈路還原出來(lái)因?yàn)樗昝勒故玖?ShardingSphere 讀寫分離中配置、網(wǎng)絡(luò)、JDBC 驅(qū)動(dòng)、MySQL 參數(shù)四者如何環(huán)環(huán)相扣。5.1 現(xiàn)象確認(rèn)先排除最可能的錯(cuò)誤第一步確認(rèn)是否配置錯(cuò)誤。檢查sharding-sphere.yamlslave-data-source-names明確列出ds_slave_0,ds_slave_1,ds_slave_2load-balance-algorithm-type為WEIGHTprops權(quán)重值也正確。接著用telnet測(cè)試從庫(kù)端口連通性全部Connected。再登錄從庫(kù)執(zhí)行SHOW PROCESSLIST發(fā)現(xiàn)大量Sleep狀態(tài)連接說(shuō)明連接確實(shí)建立了但沒(méi)有 SQL 請(qǐng)求進(jìn)來(lái)。此時(shí)我們高度懷疑是 ShardingSphere 的路由模塊出了問(wèn)題。5.2 日志深挖發(fā)現(xiàn)連接池的沉默異常開(kāi)啟sql-show: true和props.sql-show: true重啟服務(wù)。日志中只看到主庫(kù)的Actual SQL完全不見(jiàn)從庫(kù)。這證實(shí)了路由失效。我們轉(zhuǎn)而查看 HikariCP 的 DEBUG 日志發(fā)現(xiàn)關(guān)鍵線索HikariPool-1 - Cannot acquire connection from data source: java.sql.SQLNonTransientConnectionException: Could not create connection to database server.但奇怪的是這個(gè)異常只在啟動(dòng)時(shí)出現(xiàn)一次之后日志一片寂靜。我們意識(shí)到HikariCP 在連接失敗后會(huì)進(jìn)入指數(shù)退避重試而默認(rèn)重試間隔長(zhǎng)達(dá)數(shù)分鐘。于是我們手動(dòng)觸發(fā)一次讀請(qǐng)求同時(shí)tcpdump抓包tcpdump -i any port 3306 -w slave_debug.pcap抓包結(jié)果顯示應(yīng)用服務(wù)器向從庫(kù) IP 發(fā)送了 TCP SYN 包但從庫(kù)沒(méi)有任何響應(yīng)SYN-ACK。這說(shuō)明問(wèn)題不在 ShardingSphere而在網(wǎng)絡(luò)或從庫(kù)本身。5.3 網(wǎng)絡(luò)層穿透防火墻的隱形攔截我們登錄從庫(kù)服務(wù)器執(zhí)行iptables -L -n -v發(fā)現(xiàn) INPUT 鏈中有一條規(guī)則0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306這條規(guī)則是上周安全加固時(shí)運(yùn)維同學(xué)為“禁止所有未授權(quán)訪問(wèn)”而添加的。但疏忽了ShardingSphere 的連接是從應(yīng)用服務(wù)器發(fā)起的而應(yīng)用服務(wù)器的 IP 段未被加入白名單。更致命的是這條DROP規(guī)則排在ACCEPT規(guī)則之前導(dǎo)致所有 3306 端口請(qǐng)求被靜默丟棄。telnet能連通是因?yàn)樗粰z測(cè) TCP 握手而telnet成功后立即斷開(kāi)不發(fā)送任何 MySQL 協(xié)議數(shù)據(jù)而 HikariCP 在建連后會(huì)發(fā)送Handshake Initialization Packet這個(gè)包被防火墻攔截所以連接池認(rèn)為“連接建立失敗”。5.4 根因閉環(huán)JDBC 驅(qū)動(dòng)的超時(shí)陷阱為什么telnet能通而 JDBC 連接失敗我們對(duì)比了telnet和 JDBC 的建連行為。telnet的 TCP 握手超時(shí)是操作系統(tǒng)級(jí)別的通常 30 秒而 MySQL JDBC 驅(qū)動(dòng)mysql-connector-java有自己的connectTimeout參數(shù)默認(rèn)為 0無(wú)限等待。當(dāng)防火墻靜默丟棄 SYN 包時(shí)JDBC 驅(qū)動(dòng)會(huì)一直等待 SYN-ACK直到操作系統(tǒng) TCP 重傳超時(shí)約 3 分鐘才拋出異常。這解釋了為何 HikariCP 日志只在啟動(dòng)時(shí)報(bào)錯(cuò)一次——它在首次建連失敗后就進(jìn)入了長(zhǎng)達(dá)數(shù)分鐘的重試等待期間不再嘗試新連接。最終解決方案是三管齊下運(yùn)維緊急修改防火墻規(guī)則將應(yīng)用服務(wù)器網(wǎng)段加入白名單在 JDBC URL 中顯式添加connectTimeout3000socketTimeout30000確保快速失敗在 ShardingSphere 配置中為每個(gè)從庫(kù)數(shù)據(jù)源增加props: { connectTimeout: 3000 }讓框架層也具備超時(shí)感知能力。這次故障教會(huì)我們一個(gè)鐵律在分布式系統(tǒng)中任何一個(gè)環(huán)節(jié)的靜默失敗Silent Failure都比明確報(bào)錯(cuò)更危險(xiǎn)。ShardingSphere 的優(yōu)雅降級(jí)機(jī)制如連接失敗時(shí) fallback 到主庫(kù)本是優(yōu)點(diǎn)但在監(jiān)控缺失時(shí)會(huì)掩蓋底層真實(shí)的網(wǎng)絡(luò)或配置問(wèn)題。因此我們后續(xù)強(qiáng)制要求所有生產(chǎn)環(huán)境的slave-data-source-names必須配置health-check子項(xiàng)啟用ping探活并將探活結(jié)果接入 Prometheus與Seconds_Behind_Master一起構(gòu)成多維健康視圖。6. 生產(chǎn)就緒 checklist一份可直接抄作業(yè)的核對(duì)清單經(jīng)過(guò)數(shù)十個(gè)項(xiàng)目的錘煉我們總結(jié)出一份 ShardingSphere 讀寫分離的生產(chǎn)就緒 checklist。它不是理論羅列而是每一項(xiàng)都對(duì)應(yīng)過(guò)真實(shí)踩坑現(xiàn)場(chǎng)。你可以把它打印出來(lái)貼在顯示器邊框上每次上線前逐條核對(duì)。6.1 配置層12 項(xiàng)必檢硬指標(biāo)檢查項(xiàng)合規(guī)標(biāo)準(zhǔn)違規(guī)后果實(shí)測(cè)案例1. 數(shù)據(jù)源名稱一致性master-data-source-name和slave-data-source-names中的名稱必須與>