假如你日後的工作,需要快速實現MySQL的讀寫分離功能,你一定會想起這篇文章。如果你再次回到這裡,證明你已經迫切需要一個簡單快捷的解決方案了--那就是MySQL官方驅動
層實現的讀寫分離,偏小眾,但很有效。
JDBC驅動
我們經常使用的MySQL驅動jar包,其實預設有非常棒的功能,那就是主從分離和HA。如果你只是需要一個主從分離、failover的功能,不要sharding。一個驅動就夠了,不需要引入什麼中間層。
這個東西就是Replication協議。Mysql JDBC Connector在5.1.X版本之後增加了這些功能,以支援“multi-host”叢集拓撲的訪問正規化。這個功能是在驅動層實現的,而既然是驅動層,那就不可避免有一些驅動層的問題(詳見《“分庫分表" ?選型和流程要慎重,否則會失控》。
我們平常的jdbc連線是這樣
jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8
複製程式碼
而經過協議改造後的jdbc連線,長得要長一些、大一些!
jdbc:mysql:replication://127.0.0.1:3306,127.0.0.1:3307,127.0.0.1:3308/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=false&loadBalanceStrategy=random
複製程式碼
當然,也可以有ipv6的寫法,更加直觀
jdbc:mysql://address=(type=master)(host=master1host),address=(type=master)(host=master2host),address=(type=slave)(host=slave1host)/db
複製程式碼
8.0的文件看這裡: dev.mysql.com/doc/connect… 說明:
協議的第一個連線
,表示主庫Master
後面的一堆連線
,表示從庫Slave
,當然可以有多個
當你把Master
的連線
也放在後面的一堆裡,那麼它也擁有了“讀庫“的屬性了
後面有一堆引數,來控制這所有連線
,到底要如何相處
這樣,所謂的主從分離功能,只要配置好這個連線串,就枯木逢春了。
程式碼層
首先你要改驅動類,再也不是Driver了,而是這個
com.mysql.jdbc.ReplicationDriver
複製程式碼
這種情況下 DataSource.getConnection() 獲取的連線,實際上是ReplicationConnection,這個連線是虛擬的,和真實的資料庫連線是個1對多的關係,所以記得給每一個MySQL都做上相應的機器授權。
那麼如何來區別本次請求是讀是寫呢?靠的其實是Connection
中的readonly
屬性,這個屬性是通過請求header
中的“readonly”傳遞的。所以如果請求中有寫入操作,這整個的事務就一定是readonly=false的。
對於Spring來說,就可以使用@Transactional
註解來控制這個屬性了。一個事務不可能跨兩個連線,所以是讀是寫,有最高層決定。
以下程式碼片段展示了你應該配置的一些元素,沒錯,就是註解。
public interface UserManager {
public UserDO get(int id);
public void insert(UserDO user);
public void update(int id);
}
複製程式碼
@Component
public class UserManagerImpl implements UserManager{
@Autowired
private UserDao userDao;
...
@Override
@Transactional(readOnly = false,propagation = Propagation.REQUIRED)
public void insert(UserDO user) {
this.userDao.insert(user);
}
}
複製程式碼
是不是很簡單?等等,別高興太早。
引數
不要覺得是官方驅動,就可以任性的用。這套jdbc驅動的引數還是非常豐富的,學習的代價也就高了些。在一些小流量下執行的很好,但在高併發環境下會頻繁發生問題。這裡只挑最重要的說下。
一個虛擬連線,對應著一個真正的主庫連線和多個從庫連線。對於主機的存活和重新上線,我們要考慮三種情況:
-
Master都死掉了
-
Slave都死掉了
-
Master、Slave全都死掉了
無論哪種情況,MySQL驅動都表現出一些奇怪的行為,預設引數是不好使的。
你可能以為驅動也就是管理一下幾個連線而已,但情況比這複雜的多。首先給張圖看下這個複雜的關係。
1、readFromMasterWhenNoSlaves 當所有的salve死掉後,此引數用來控制主庫是否參與讀。如果從庫的流量很大,配置此引數對主庫有很大風險;但如果你關掉,請求則會快速失敗。 2、loadBalanceStrategy 策略用來指定從庫的輪詢規則。有輪詢,也有權重,也可以指定具體的策略實現。當你維護或者遷移某個例項時,先置空流量,這會非常有用。或許,你會給某DB一個預熱的可能。 3、allowMasterDownConnections 如果主機當機,當連線池獲取新的連線時,會失敗。但如果開啟此引數,則虛擬連線只會建立Slave連線組,整個連線會降級為只讀,不論你設定了什麼註解。 4、allowSlavesDownConnections 如果沒有隻讀庫了,是否允許建立新的連線。在這種情況下,此引數開啟,讀操作有很大可能會失敗。 5、retriesAllDown 當所有的hosts都無法連線時重試的最大次數(依次迴圈重試),預設為120。重試次數達到閾值仍然無法獲取有效連結,將會丟擲SQLException。 6、autoReconnect 例項既然有下線、就有上線。上線以後要能夠繼續服務,此引數用來控制斷線情況下自動重連而不丟擲異常。這會破壞事務的完整性,但還是預設開啟。然而MySQL驅動提供了更加豐富的引數來控制這個過程,如果你的業務要求比較苛刻,這些引數可能要測個遍才會放心。 但大多數情況下,它執行的很好。
管理
僅有配置引數,此協議就算一個半成品而已。所幸,此驅動提供了JMX管理的方式,可以基於其做一些配置變更之類的功能。市面上並沒有這種配置管理工具,可能還是因為它太小眾了。
public abstract void addSlaveHost(String groupFilter, String host) throws SQLException;
public abstract void removeSlaveHost(String groupFilter, String host) throws SQLException;
public abstract void promoteSlaveToMaster(String groupFilter, String host) throws SQLException;
public abstract void removeMasterHost(String groupFilter, String host) throws SQLException;
public abstract String getMasterHostsList(String group);
public abstract String getSlaveHostsList(String group);
public abstract String getRegisteredConnectionGroups();
public abstract int getActiveMasterHostCount(String group);
public abstract int getActiveSlaveHostCount(String group);
public abstract int getSlavePromotionCount(String group);
public abstract long getTotalLogicalConnectionCount(String group);
public abstract long getActiveLogicalConnectionCount(String group);
複製程式碼
我們順便提一下阿里的德魯伊資料庫連線池。如圖,某些功能,只支援預設的單連線,對multi-host支援還是有限。
結尾
MySQL 5.1.x官方驅動出了這麼個東西以後,其實宣告了很多小公司自研的某些小中介軟體的死亡。翻來服務,改寫JDBC,不過就是為了管理個連線集合。
本文物件為專注基礎設施研發的同學。有人看到的,不過是一堆引數而已;而真正去深入使用的人,會感到背脊通徹的寒冷。
當它小眾時,你對它不屑一顧。然而當你一旦採用了某種方案,你卻希望全世界都是關於它的描寫。人生從來就沒那麼幸運,很多坑,還需要自己來踩。 0.