背景說明
公司的整個電商系統搭建在華為雲上,根據老總的估計,上線3個月之后日訂單量會達到百萬級別,保守估計3個月之后總訂單個數預計會有5千萬。MySQL單表達到千萬級別,就會出現明顯的性能問題。根據如此規模的數據,當時考慮了2套解決方案:
方案一:在業務上根據用戶ID做拆分,將數據打散放在5台32U128G的華為雲RDS上邊
方案二:直接使用華為雲的分布式數據庫中間件DDM
方案一的好處是,分片算法全部在業務上實現,整個方案都在自己的控制下。后續問題定位,方案修改都會好很多;壞處是,整個方案需要業務代碼支撐,訪問到做了拆分的數據都需要做特殊處理,代價還是比較大的,而且對開發人員的能力要求很高。后續運維的工作也比較大。
方案二的好處是,直接使用雲服務后續不需要擔心運維的事情,另外DDM從中間件層屏蔽了分庫分表的具體實現,業務可以當做單庫來操作,易用性以及對代碼的要求、對開發人員的要求都會低了很多。缺點就是,使用了DDM之后,對華為雲的粘性會大很多。
綜合考慮了兩個方案的優缺點,最終選擇了方案二,主要是基於對華為雲技術能力和后續蓬勃發展的信心。
對DDM做了一定的調研,的確是一個非常不錯的分庫分表服務。支持超大規模數據,10備於單機數據庫的超強性能,百萬並發,讀寫分離,支持平滑擴容等等。。。優點數不勝數~
搭建到華為雲之后,一直平穩運行,但是前陣子出了個奇怪的問題,在DDM技術專家的協助下,很快定位了出來,結果是MySQL-JDBC的一個bug導致。作為一個具有打破砂鍋問到底、不破樓蘭誓不還的程序員,決定對MySQL的相關參數做個詳細的分析,免得從一個坑里邊爬出來又進了另外一個。
Loadbalance模式說明
為了提供高性能,百萬並發,DDM自身是以無狀態的集群形式對外提供的。內部怎么做的我們不清楚,能看到的是,每個DDM提供了多個訪問地址,每個庫的訪問url類似於:jdbc:mysql:loadbalance://192.168.0.35:5066,192.168.0.192:5066,192.168.0.175:5066,192.168.0.139:5066/orderdb?loadBalanceAutoCommitStatementThreshold=5
從訪問的url看,內部應該是多台DDM節點的,實際上從我們測試的情況看,訪問任何一台的效果都是一樣的。猜測,內部的交互應該是類似如下圖的:
跟DDM的技術專家求證,的確是如此的,心里有點小得意~~
我們的代碼全部是java的代碼,連接池用的是druid,根據DDM的指導,將url配置好就能正常訪問了。感覺關健的就在loadbalance這個,應該是告訴了驅動,通過負載均衡方式訪問DDM。在網上查了下,這種方式是直接在驅動層面做的負載均衡,相比通過負載均衡器的方式,少了一次網絡轉發,怪不得效率會這么高。不過,APP到底是訪問哪個DDM,內部機制是什么樣子的?這些在網上查了下,都是語焉不詳,沒辦法只好從MySQL JDBC的源碼入手了。
驅動的源碼是托管在github上,我們當前用的是DDM推薦的5.1.44版本的:https://github.com/mysql/mysql-connector-j/tree/5.1.44
核心的就是幾個Loadbalance開頭的類:
代碼比較多,其他的就不多說了,最關鍵的就是下邊這塊代碼:
LoadBalancedConnectionProxy.java類的pickNewConnection()函數
這個函數在創建連接對象、一個事務提交或者回滾都會調用,作用就是輪換下一個DDM節點。這塊代碼的邏輯就是,根據一定的負載均衡策略挑選一個節點的連接,做個基本的連接有效性探測,然后將當前連接的狀態同步到新連接(見 Table 2 MultiHostConnectionProxy.syncSessionState())。同步完畢,就把當前使用的連接設置為新挑選的連接。如果所有的連接都不可用,就把當前狀態設置為了Closed狀態。看着快代碼,感覺MySQL的有些代碼也不嚴謹,比如如果在獲取新連接的時候,如果拋了SQLException出來,這個異常就直接被吃掉了,不會拋出去,也不會有任何信息記錄下來,這個對后續的問題定位還是很不方便的,不知道是出於什么考慮的。
Table 1 LoadBalancedConnectionProxy.pickNewConnection() synchronized void pickNewConnection() throws SQLException { if (this.isClosed && this.closedExplicitly) { return; } if (this.currentConnection == null) { // startup this.currentConnection = this.balancer.pickConnection(this, Collections.unmodifiableList(this.hostList), Collections.unmodifiableMap(this.liveConnections), this.responseTimes.clone(), this.retriesAllDown); return; } if (this.currentConnection.isClosed()) { invalidateCurrentConnection(); } int pingTimeout = this.currentConnection.getLoadBalancePingTimeout(); boolean pingBeforeReturn = this.currentConnection.getLoadBalanceValidateConnectionOnSwapServer(); for (int hostsTried = 0, hostsToTry = this.hostList.size(); hostsTried < hostsToTry; hostsTried++) { ConnectionImpl newConn = null; try { newConn = this.balancer.pickConnection(this, Collections.unmodifiableList(this.hostList), Collections.unmodifiableMap(this.liveConnections), this.responseTimes.clone(), this.retriesAllDown); if (this.currentConnection != null) { if (pingBeforeReturn) { if (pingTimeout == 0) { newConn.ping(); } else { newConn.pingInternal(true, pingTimeout); } } syncSessionState(this.currentConnection, newConn); } this.currentConnection = newConn; return; } catch (SQLException e) { if (shouldExceptionTriggerConnectionSwitch(e) && newConn != null) { // connection error, close up shop on current connection invalidateConnection(newConn); } } } // no hosts available to swap connection to, close up. this.isClosed = true; this.closedReason = "Connection closed after inability to pick valid new connection during load-balance."; } Table 2 MultiHostConnectionProxy.syncSessionState() static void syncSessionState(Connection source, Connection target, boolean readOnly) throws SQLException { if (target != null) { target.setReadOnly(readOnly); } if (source == null || target == null) { return; } target.setAutoCommit(source.getAutoCommit()); target.setCatalog(source.getCatalog()); target.setTransactionIsolation(source.getTransactionIsolation()); target.setSessionMaxRows(source.getSessionMaxRows()); }
MySQL-JDBC Loadbalance參數說明
明白了MySQL-JDBC的Loadbalance的相關機制,最重要的還是要對相關的參數有個詳細的了解,並且設置有效的值,Loadbalance相關一共有十幾個參數,幾個比較關鍵的如下表所示:
其他還有幾個參數,一般用不到,也就不羅列出來了。大家感興趣的話可以關注公眾號:中間件小哥(zhongjianjianxiaoge)了解更多喲~