這篇文章記錄了給 Apache 頂級項(xiàng)目 - 分庫分表中間件 ShardingSphere 提交 Bug 的歷程。
說實(shí)話,這是一次比較曲折的 Bug 跟蹤之旅。10月28日,我們在 GitHub 上提交 issue,中途因?yàn)楣俜介_發(fā)者的主觀臆斷被 Close 了兩次,直到 11 月 20 日才被認(rèn)定成 Bug 并發(fā)出修復(fù)版本,歷時 20 多天。
1、疑難問題的排查思路
2、數(shù)據(jù)庫中間件 Sharding Proxy 的原理
3、MySQL 預(yù)編譯的流程和交互協(xié)議
4、Wireshark 抓包分析 MySQL 的奇淫技巧
?01 問題描述?
這個 Bug 來源于我的公號讀者,他替公司預(yù)研 ShardingProxy(屬于 ShardingSphere 的子產(chǎn)品,可用作分庫分表,后文會詳細(xì)介紹)。他按照官方文檔寫了一個很簡單的 demo,但是運(yùn)行后無法查詢出數(shù)據(jù)。
下面是他遇到問題后發(fā)給我的信息,希望我能幫忙一起定位下原因。
截圖中的 doc 詳細(xì)記錄了 ShardingProxy 的配置、調(diào)試分析日志、以及問題的具體現(xiàn)象。
為了方便大家理解,我重新描述下這個 Demo 的業(yè)務(wù)邏輯以及問題表象。
1.?Demo 的業(yè)務(wù)邏輯說明
這個 Demo 很簡單,主要為了跑通 ShardingProxy ?的分庫分表功能。程序用 SpringBoot + MyBatis 實(shí)現(xiàn)了一個單表的查詢邏輯,然后用這張表的一個 long 類型字段作為分區(qū)鍵,并通過 ShardingProxy 進(jìn)行了分表。
前兩個字段的作用如下:
-
BIZ_DT:業(yè)務(wù)字段,date類型,和Bug有關(guān) -
ECIF_CUST_NO:bigint 類型,用做分區(qū)鍵
jdbc:mysql://127.0.0.1:3307/sharding_db?useServerPrepStmts=true&cachePrepStmts=true&serverTimezone=UTC
shardingColumn: ecif_cust_no
algorithmExpression: pscst_prdt_cvr${ecif_cust_no % 2}
2.?問題描述
然后啟動 demo 程序,使用 curl 發(fā)起 post 請求,查詢 ecifCustNo 等于 10000 的那條記錄,居然查詢不出數(shù)據(jù):
至此,背景基本交代清楚了,為什么數(shù)據(jù)庫中明明有數(shù)據(jù),但是程序卻查詢不出來呢?問題到底出現(xiàn)在 ShardingProxy,還是應(yīng)用程序本身?
?02 ShardingProxy 原理簡介?
ShardingSphere 的目標(biāo)是一個生態(tài)圈,它由非常著名的 ShardingJDBC、ShardingProxy、ShardingSidecar 3 款獨(dú)立的產(chǎn)品組成。本文重點(diǎn)普及下 ShardingProxy,另外兩個就不展開了。
ShardingProxy 屬于和 MyCat 對標(biāo)的產(chǎn)品,定位為透明化的數(shù)據(jù)庫代理端,可以理解成:一個實(shí)現(xiàn)了 MySQL 協(xié)議的 Server(獨(dú)立進(jìn)程),可用于讀寫分離、分庫分表、柔性事務(wù)等場景。
對于應(yīng)用程序或者 DBA 來說,可以把 ShardingProxy 當(dāng)做數(shù)據(jù)庫代理,能用 MySQL 客戶端工具(Navicat)或者命令行和它直接交互,而 ShardingProxy 內(nèi)部則通過 MySQL 原生協(xié)議與真實(shí)的 MySQL 服務(wù)器通信。
圖1:ShardingProxy 的應(yīng)用架構(gòu)圖
從架構(gòu)圖來看,ShardingProxy 就相當(dāng)于 MySQL,它本身不存儲數(shù)據(jù),但是對外屏蔽了 Database 的存儲細(xì)節(jié),你可以用連接 MySQL 的方式去連接 ShardingProxy(除了端口不同),用你熟悉的 ORMapping 框架使用它。
再來看下 ShardingProxy 的內(nèi)部架構(gòu),后續(xù)源碼分析時會涉及到此部分。
圖2:ShardingProxy 的內(nèi)部架構(gòu)圖
整個架構(gòu)分為前端、核心組件和后端:
前端(Frontend)負(fù)責(zé)與客戶端進(jìn)行網(wǎng)絡(luò)通信,采用的是 NIO 框架,在通信的過程中完成對MySQL協(xié)議的編解碼。
核心組件(Core-module)得到解碼的 MySQL 命令后,開始調(diào)用 Sharding-Core 對 SQL 進(jìn)行解析、改寫、路由、歸并等核心功能。
后端(Backend)與真實(shí)數(shù)據(jù)庫交互,采用 Hikari 連接池,同樣涉及到 MySQL 協(xié)議的編解碼。
3. ShardingProxy 的預(yù)編譯 SQL 功能
本文的 Bug 跟 ShardingProxy 的預(yù)編譯 SQL 有關(guān),這里單獨(dú)介紹下此功能以及與之相關(guān)的 MySQL 協(xié)議,這個是本文的關(guān)鍵,請耐心看完。
熟悉數(shù)據(jù)庫開發(fā)的同學(xué)一定了解:預(yù)編譯 SQL(PreparedStatement),在數(shù)據(jù)庫收到一條 SQL 到執(zhí)行完畢,一般分為以下 3 步:
1、詞法和語義解析
2、優(yōu)化 SQL,制定執(zhí)行計劃
3、執(zhí)行并返回結(jié)果
SELECT?*?FROM?t_user?WHERE?user_id?=?10;
?03?問題分析?
因?yàn)檎麄€代碼很簡單,代碼層面唯一有可能存在問題的是 Mybatis 這一層。為了確認(rèn)這一點(diǎn),我修改了 SpringBoot 的配置,將 MyBatis 的 debug 日志也打印了出來。再次發(fā)起請求后,能從控制臺中看到以下詳細(xì)日志:
jdbc:mysql://127.0.0.1:3306/db1?useServerPrepStmts=true&cachePrepStmts=true&serverTimezone=UTC
通過這一步,我將懷疑對象再次轉(zhuǎn)移到 ShardingProxy 上了,并將 dataSource 配置改回成原樣,繼續(xù)排查。
首先,查看 ShardingProxy 的運(yùn)行日志,沒發(fā)現(xiàn)任何異常;其次,能看到日志中的 Actual SQL 是正確的,它已經(jīng)根據(jù)分區(qū)鍵正確路由到了??pcsct_prdt_cvr0??這張表:
[INFO?]?17:25:48.804?[ShardingSphere-Command-15]?
ShardingSphere-SQL?-?Actual?SQL:?ds_0?:::?SELECT
BIZ_DT,ECIF_CUST_NO,DEP_FLG?...
FROM?pscst_prdt_cvr0
WHERE?ECIF_CUST_NO?=???:::?[10000]
我開始懷疑:是否跟 ShardingProxy 所使用的數(shù)據(jù)庫驅(qū)動有關(guān)?因?yàn)檫@個 Jar 包是應(yīng)用方選擇版本,手動放到 ShardingProxy 安裝目錄中的。因此,我將驅(qū)動版本從 5.1.47 版本改成了 8.0.13 (和 Demo 使用了相同的版本),但是問題仍然存在。
另外,還能想到的是:是否是 ShardingProxy 的這個最新版本引入了 Bug?然后,我又另外安裝了它的上一個版本 4.1.0,重新測試了一遍,還是有問題。
這個時候,真感覺沒有其他可疑點(diǎn)了,所有能想到的點(diǎn)都排查了一遍。我再次回到了 Demo 程序本身,它和 ShardingProxy 唯一的結(jié)合點(diǎn)就在 DataSource 的 url 上。
jdbc:mysql://127.0.0.1:3307/sharding_db?useServerPrepStmts=true&cachePrepStmts=true&serverTimezone=UTC
找到這個問題的解決方案后,我同步給了讀者。與此同時,他也在 ShardingProxy ?的 GitHub 上提交了 issue,反饋了這個最新進(jìn)展。
由于工作原因,這個問題我就暫時放一邊了,準(zhǔn)備抽空再接著排查。
大概過了一周我想起了這個問題,然后打開 issue 想了解下調(diào)查進(jìn)度,讓我非常驚訝的是:官方開發(fā)者居然在復(fù)現(xiàn)此問題后,主觀臆斷地認(rèn)為是應(yīng)用程序的問題,然后莫名奇妙的把這個 issue 關(guān)閉了,他們的答復(fù)是這樣的:
mysql || tcp.port==3307
?04?根本原因定位?
當(dāng)天晚上,官方開發(fā)者就定位到了根本原因,發(fā)出了 Pull Request。我看了下代碼改動,僅僅修改了一行代碼。
1、為什么代碼拋異常了,但是 ShardingProxy 的控制臺沒打印呢?
2、為什么 ShardingProxy 需要做 date 到 Timestamp 的類型轉(zhuǎn)換呢?
簡單理解就是:ShardingProxy 在代碼實(shí)現(xiàn)時,用了一個范圍最大的 timestamp 存了三種可能的值 date, datetime 和 timestamp,然后再按照上面這個協(xié)議規(guī)范進(jìn)行二進(jìn)制的寫入。
3、這個 Bug 是只有在使用 SQL 預(yù)編譯功能時才會被觸發(fā)嗎?
?05?寫在最后?
本文詳細(xì)復(fù)盤了這個 Bug 的分析過程,并對其中的原理知識和排查經(jīng)驗(yàn)進(jìn)行了總結(jié)。
對于 ShardingSphere 這種頂級開源項(xiàng)目來說,我個人覺得同樣值得做一次深度復(fù)盤。我不認(rèn)同他們對于 issue 的處理方式,另外在核心功能的自動化測試上,也一定是存在 case 不完善的,不然不可能連續(xù)多個版本都沒發(fā)現(xiàn)這個嚴(yán)重 Bug。
哈嘍,我是小林,就愛圖解計算機(jī)基礎(chǔ),如果覺得文章對你有幫助,歡迎分享給你的朋友,也給小林點(diǎn)個「在看」,這對小林非常重要,謝謝你們,給各位小姐姐小哥哥們抱拳了,我們下次見!
推薦閱讀
小小的 float,藏著大大的學(xué)問
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點(diǎn),不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!