背景說明
當(dāng)前業(yè)務(wù)場景我們使用原生SpringBoot整合Hikari數(shù)據(jù)源連接池提供服務(wù),但是近期業(yè)務(wù)迭代需要使用動(dòng)態(tài)多數(shù)據(jù)源,很自然想到dynamic-source,結(jié)果一系列慘案離奇發(fā)生。。。
蒙蔽雙眼
原生SpringBoot整合HikariCp數(shù)據(jù)源連接池配置【這個(gè)是沒問題的配置】
spring.datasource.hikari.allow-pool-suspension = true
spring.datasource.hikari.connection-timeout = 10000
spring.datasource.hikari.pool-name = HikariPool
spring.datasource.hikari.idle-timeout = 60000
spring.datasource.hikari.maximum-pool-size = 300
spring.datasource.hikari.max-lifetime = 120000
spring.datasource.hikari.minimum-idle = 30
spring.datasource.type = com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://a.com:4000/payment?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username = xx
spring.datasource.password = sx
而升級后的動(dòng)態(tài)多數(shù)據(jù)源配置如下:【有嚴(yán)重問題】
spring.datasource.dynamic.primary = tidb-payment
spring.datasource.dynamic.strict = false
spring.datasource.dynamic.hikari.idle-timeout = 60000
spring.datasource.dynamic.hikari.max-lifetime = 120000
spring.datasource.dynamic.hikari.connection-timeout = 10000
spring.datasource.dynamic.hikari.minimum-idle = 30
spring.datasource.dynamic.hikari.maximum-pool-size = 300
spring.datasource.url = jdbc:mysql://a.com:4000/payment?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username = xxx
spring.datasource.password = xxx
spring.datasource.type = com.zaxxer.hikari.HikariDataSource
mysql-payment.username = root
mysql-payment.password = xxx
mysql-payment.url = jdbc:mysql://xxx:3306/payment?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
mysql-cashier.username = xxx
mysql-cashier.password = xx
mysql-cashier.url = jdbc:mysql://xxx:3306/cashier?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.dynamic.primary = tidb-payment
spring.datasource.dynamic.datasource.tidb-payment.url = ${spring.datasource.url}
spring.datasource.dynamic.datasource.tidb-payment.username = ${spring.datasource.username}
spring.datasource.dynamic.datasource.tidb-payment.password = ${spring.datasource.password}
spring.datasource.dynamic.datasource.tidb-payment.type = ${spring.datasource.type}
spring.datasource.dynamic.datasource.mysql-payment.url = ${mysql-payment.url}
spring.datasource.dynamic.datasource.mysql-payment.username = ${mysql-payment.username}
spring.datasource.dynamic.datasource.mysql-payment.password = ${mysql-payment.password}
spring.datasource.dynamic.datasource.mysql-payment.type = ${spring.datasource.type}
spring.datasource.dynamic.datasource.mysql-cashier.url = ${mysql-cashier.url}
spring.datasource.dynamic.datasource.mysql-cashier.username = ${mysql-cashier.username}
spring.datasource.dynamic.datasource.mysql-cashier.password = ${mysql-cashier.password}
spring.datasource.dynamic.datasource.mysql-cashier.type = ${spring.datasource.type}
來,無論幾年經(jīng)驗(yàn)的道友看看此配置有什么問題?剛使用的童鞋很難發(fā)現(xiàn),因?yàn)闆]有一定的并發(fā)量, 幾乎很難發(fā)現(xiàn)其中 很致命的2個(gè)問題
:
- 全局配置是各自獨(dú)享,不是共享
- 當(dāng)前配置的最大活躍連接數(shù)和最小活躍連接數(shù)實(shí)際運(yùn)行都是10,即配置是錯(cuò)誤的
實(shí)話說,我也是遇到我人生第一個(gè)職業(yè)滑鐵盧:
- 只要服務(wù)一發(fā)版,消息服務(wù)一直處于積壓狀態(tài),而這個(gè)服務(wù)業(yè)務(wù)邏輯又很單一就是消費(fèi)數(shù)據(jù)寫TIDB,加上匱乏的測試人員,非生產(chǎn)環(huán)境根本看不出任何問題
- 只要一回滾就正常
- 服務(wù)消息積壓根本沒有任何錯(cuò)誤
這期間一直懷疑是新升級代碼過多創(chuàng)建線程,但是幾經(jīng)確認(rèn)是規(guī)范的創(chuàng)建線程池,自信注釋掉所有可能過多創(chuàng)建線程地方,發(fā)布后繼續(xù)消息積壓,幾經(jīng)嘗試無果
最搞笑的是,在期間做的修補(bǔ)策略還因?yàn)榭床坏疆惓?,而引入一個(gè)新的問題:
WARN com.baomidou.dynamic.datasource.DynamicRoutingDataSource [240] - dynamic-datasource initial loaded [0] datasource,Please add your primary datasource or check your configuration
當(dāng)你第一次看到這個(gè)警告切記不要忽略,因?yàn)榇藭r(shí)服務(wù)雖然只是啟動(dòng)告警,但是只要一嘗試sql連接,直接異常:Caused by: com.baomidou.dynamic.datasource.exception.CannotFindDataSourceException: dynamic-datasource can not find primary datasource
本來我不需要單獨(dú)講,因?yàn)樽詼y是基本的素養(yǎng),但是因?yàn)樵诋?dāng)時(shí)上線修補(bǔ)過程中是缺少測試【過于自信】,所以任務(wù)服務(wù)發(fā)版沒問題忽略,而異常還是我后來從rocketmq_client.log
找到,還不是自身配置logback-spring.xml
對應(yīng)日志文件,所以一直沒在意,關(guān)鍵RocketMQ還吃掉了異常,直接當(dāng)回滾處理.
口說無憑
修補(bǔ)引發(fā)的新問題
首先對著回滾前最后一次修補(bǔ)代碼分支先直接在本地壓測,瞬間發(fā)現(xiàn)baomidou.dynamic.datasource.exception.CannotFindDataSourceException:ception
但問題來了,線上為什么沒有這個(gè)異常,搜遍了日志無果,后來想到當(dāng)前直接監(jiān)聽RocketMQ消費(fèi),統(tǒng)一在consumeMessage方法處理,如下
坑啊,當(dāng)時(shí)沒發(fā)現(xiàn)是因?yàn)槌绦驔]有任何錯(cuò)誤還傻傻以為是程序處理正常,只是線程積壓了
話說回來,這個(gè)錯(cuò)誤算比較低級了,因?yàn)橐肓薲ynamic-datasource 數(shù)據(jù)源但是卻沒有配置好數(shù)據(jù)源,而默認(rèn)引入依賴就會(huì)在業(yè)務(wù)的sql操作中使用改配置數(shù)據(jù)源連接池【當(dāng)時(shí)回滾代碼邏輯是不清晰的,只回滾配置注釋代碼是不夠的,要么基于老分支直接重寫邏輯本地驗(yàn)證后再試,要么所有新代碼一起移除,包括mave依賴】
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
這里可以推理得到:既然這個(gè)錯(cuò)誤是RocketMQ捕獲了,那么自然打在了RocketMQ配置的日志文件中:rockemq_client.log 注意這個(gè)不配置就自動(dòng)生成,關(guān)鍵還只保留8小時(shí),最終本地驗(yàn)證是找到了,生產(chǎn)因?yàn)檫^了一天所以看不到
解決配置問題
現(xiàn)在我們回來看看配置兩個(gè)問題是怎么回事,這個(gè)比較隱晦了,我加好了數(shù)據(jù)源后拷貝生產(chǎn)一份配置到本地,開始debug定位發(fā)現(xiàn),配置最大活躍連接、最小活躍連接數(shù)首先是-1 然后在校驗(yàn)合法性時(shí)改成了默認(rèn)值10
what?沒生效本能想到這不可能,因?yàn)樯a(chǎn)一直這么使用的,甚至懷疑生產(chǎn)一直是錯(cuò)誤的,但是生產(chǎn)讓SRE查詢監(jiān)控信息確認(rèn)是正確的,瞬間再次懷疑自己,索性仔細(xì)比對生產(chǎn)老配置發(fā)現(xiàn)和源代碼排查
才知道maximum-pool-size
和 minimum-idle
在升級使用dynamic-source是不對的,屬性名發(fā)生了變更分別變成了max-pool-size 和 min-idle , 本以為原路拷貝即可誰知在dynamic-datasource源碼中配置HikariCp做了替換,真的坑爹
這里就可以解釋,線上是并發(fā)比較高的,所以很快把10個(gè)連接占滿,甚至已經(jīng)拋出了連接不可用的異常由于被RocketMQ捕獲,所以很難發(fā)現(xiàn),于是修正了屬性值再次Debug正常設(shè)置成功。
修正了屬性值還不夠,接下來有第二個(gè)問題,請回到開頭再次觀察連接池配置是全局配置,最初也是沒有好好看源碼以為是三個(gè)數(shù)據(jù)源共享配置,直到我在調(diào)試過程中看到源碼確實(shí)是獨(dú)自設(shè)置,我才恍然
是否允許全局獨(dú)享取決你的業(yè)務(wù)場景,如果你的數(shù)據(jù)庫的所在數(shù)據(jù)源都是獨(dú)立部署的那么 共享除了失去定制的靈活性沒啥性能問題,但是如果你的本質(zhì)是一個(gè)數(shù)據(jù)源多個(gè)數(shù)據(jù)庫 這么配置會(huì)撐爆數(shù)據(jù)庫連接,使用時(shí)需要謹(jǐn)慎!
有人要問了誰叫你不看文檔,這里要diss 一下 dynamic-source官方文檔說明這一塊是真的黑心
所以經(jīng)過上面分析最正確的配置模版如下,注意我只保證屬性一定設(shè)置生效,但是value數(shù)值需要各自工業(yè)實(shí)踐結(jié)果:
spring.datasource.url = jdbc:mysql://a.com:4000/payment?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username = xxx
spring.datasource.password = xxx
spring.datasource.type = com.zaxxer.hikari.HikariDataSource
mysql-payment.username = root
mysql-payment.password = xxx
mysql-payment.url = jdbc:mysql://xxx:3306/payment?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
mysql-cashier.username = xxx
mysql-cashier.password = xx
mysql-cashier.url = jdbc:mysql://xxx:3306/cashier?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.dynamic.primary = tidb-payment
spring.datasource.dynamic.datasource.tidb-payment.url = ${spring.datasource.url}
spring.datasource.dynamic.datasource.tidb-payment.username = ${spring.datasource.username}
spring.datasource.dynamic.datasource.tidb-payment.password = ${spring.datasource.password}
spring.datasource.dynamic.datasource.tidb-payment.type = ${spring.datasource.type}
spring.datasource.dynamic.datasource.tidb-payment.hikari.max-pool-size = 50
spring.datasource.dynamic.datasource.tidb-payment.hikari.min-idle = 4
spring.datasource.dynamic.datasource.tidb-payment.hikari.max-lifetime = 120000
spring.datasource.dynamic.datasource.tidb-payment.hikari.connection-timeout = 10000
spring.datasource.dynamic.datasource.tidb-payment.hikari.idle-timeout = 60000
spring.datasource.dynamic.datasource.tidb-payment.hikari.allow-pool-suspension = true
spring.datasource.dynamic.datasource.mysql-payment.url = ${mysql-payment.url}
spring.datasource.dynamic.datasource.mysql-payment.username = ${mysql-payment.username}
spring.datasource.dynamic.datasource.mysql-payment.password = ${mysql-payment.password}
spring.datasource.dynamic.datasource.mysql-payment.type = ${spring.datasource.type}
spring.datasource.dynamic.datasource.mysql-payment.hikari.max-pool-size = 25
spring.datasource.dynamic.datasource.mysql-payment.hikari.min-idle = 4
spring.datasource.dynamic.datasource.mysql-payment.hikari.max-lifetime = 120000
spring.datasource.dynamic.datasource.mysql-payment.hikari.connection-timeout = 10000
spring.datasource.dynamic.datasource.mysql-payment.hikari.idle-timeout = 60000
spring.datasource.dynamic.datasource.mysql-payment.hikari.allow-pool-suspension = true
spring.datasource.dynamic.datasource.mysql-cashier.url = ${mysql-cashier.url}
spring.datasource.dynamic.datasource.mysql-cashier.username = ${mysql-cashier.username}
spring.datasource.dynamic.datasource.mysql-cashier.password = ${mysql-cashier.password}
spring.datasource.dynamic.datasource.mysql-cashier.type = ${spring.datasource.type}
spring.datasource.dynamic.datasource.mysql-cashier.hikari.max-pool-size = 25
spring.datasource.dynamic.datasource.mysql-cashier.hikari.min-idle = 3
spring.datasource.dynamic.datasource.mysql-cashier.hikari.max-lifetime = 120000
spring.datasource.dynamic.datasource.mysql-cashier.hikari.connection-timeout = 10000
spring.datasource.dynamic.datasource.mysql-cashier.hikari.idle-timeout = 60000
spring.datasource.dynamic.datasource.mysql-cashier.hikari.allow-pool-suspension = true
本地監(jiān)控佐證
至此問題排查和解決已經(jīng)確定,但是這么debug修改我還是不太放心,比較之前自信修改的教訓(xùn)讓我歷歷在目,有了解到SpringBoot自帶監(jiān)控肯定有關(guān)于數(shù)據(jù)源連接池的信息,如果能看到自己期望的結(jié)果,那么一定不會(huì)有問題了
所以這里參考網(wǎng)上如何打開本地健康檢查【不推薦生產(chǎn)環(huán)境使用】:Springboot整合Prometheus本地監(jiān)控多數(shù)據(jù)源 ,這一篇不僅給出了方案,還發(fā)現(xiàn)了SpringBoot監(jiān)控多數(shù)據(jù)源的bug,即只監(jiān)控到一個(gè)問題:配置之后把之前的流程走一遍確實(shí)走到了默認(rèn)值10
不用不知道,我又陷入另一個(gè)自我懷疑階段:在本地和測試環(huán)境啟動(dòng)參數(shù)、apollo配置、代碼完全一致的情況,都使用錯(cuò)誤的數(shù)據(jù)連接池配置后, 測試和本地展現(xiàn)兩種不同的數(shù)據(jù)源監(jiān)控結(jié)果:云服務(wù)器是-1,而本地一直都是10,詳情分析請看這一篇:【沉淀之華】SpringBoot使用HikariCP數(shù)據(jù)源兩次初始化過程 & 服務(wù)器與本地完全一致卻不同數(shù)據(jù)源結(jié)果定位
萬法歸元
從上面坎坷的排查過程看,需要注意3點(diǎn)文章來源:http://www.zghlxwxcb.cn/news/detail-790513.html
- 平時(shí)迭代一定要盡可能做好自測,甚至是壓測
- 不要定式思維,按技術(shù)文檔或者源碼配置【無奈官方文檔都成了資本手下,只恨無開源精神】
- 不要讓RocketMQ去處理我們的業(yè)務(wù)異常,一定要手動(dòng)捕獲處理,否則很多未知的問題很難定位發(fā)現(xiàn)
持續(xù)分享,持續(xù)輸出…文章來源地址http://www.zghlxwxcb.cn/news/detail-790513.html
到了這里,關(guān)于[前車之鑒] SpringBoot原生使用Hikari數(shù)據(jù)連接池升級到動(dòng)態(tài)多數(shù)據(jù)源的深坑解決方案 & RocketMQ吞掉異常問題排查的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!