Mysql使用ReplicationDriver驅動實現讀寫分離

  • 2019 年 10 月 7 日
  • 筆記

資料庫的主從複製環境已經配好,該要解決系統如何實現讀寫分離功能了。Mysql的jdbc驅動提供了一種實現ReplicationDriver。

1 資料庫地址的兩種寫法

參考:https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-url-format.html

因為後續配置可能會用到,先介紹一下mysql url中主機地址的兩種寫法。

最簡單的寫法就是 host:port ,如 192.168.5.128:3306,如果埠是預設的3306,也可以不寫,直接使用 192.168.5.128。

更複雜的寫法是指定屬性,格式為 address=(host=host_or_ip)(port=port)(key1=value1)(key2=value2)…(keyN=valueN),如 address=(host=192.168.5.128)(port=3306)(type=master)。

2 數據池配置

參考:https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-master-slave-replication-connection.html

driverClassName: com.mysql.jdbc.ReplicationDriver

url字元串格式:

jdbc:mysql:replication://[master host][:port],[slave host 1][:port][,[slave host 2][:port]]…[/[database]][?propertyName1=propertyValue1[&propertyName2=propertyValue2]…]

資料庫地址的寫法有兩種,上面是第一種簡單的寫法,ReplicationDriver默認將第一個資料庫伺服器作為Master庫,後續其他資料庫伺服器作為從資料庫。

如果資料庫主從設置了多台資料庫作為Master,則url格式為:

jdbc:mysql:replication://address=(type=master)(host=master1host),address=(type=master)(host=master2host),address=(type=slave)(host=slave1host)/database[?propertyName1=propertyValue1[&propertyName2=propertyValue2]…]

3 ReplicationDriver的調用方法

Mysql驅動使用究竟使用 master還是 slave資料庫,取決於 Connection.getReadOnly()的值。如果值為false,則將命令發送到master資料庫,如果值為true,則將命令發送到slave資料庫。當有多台slave資料庫時,使用輪詢調度(round-robin)演算法選擇某一台slave資料庫。

所以,為了讓mysql驅動能夠準確的將命令發送到master或slave,程式碼需要在獲取到數據連接後,執行命令 Connection.setReadOnly(true) 或 Connection.setReadOnly(false)。

以下是mysql官方提供的示例程式碼

import java.sql.Connection;import java.sql.ResultSet;import java.util.Properties;import com.mysql.jdbc.ReplicationDriver;public class ReplicationDriverDemo { public static void main(String[] args) throws Exception { ReplicationDriver driver = new ReplicationDriver(); Properties props = new Properties(); // 從庫上允許自動重連 props.put("autoReconnect", "true"); // 多台從庫使用負載均衡 props.put("roundRobinLoadBalance", "true"); props.put("user", "foo"); props.put("password", "password"); Connection conn = driver.connect("jdbc:mysql:replication://master,slave1,slave2 /test", props); // 在主庫上讀寫,設置 read-only 為 "false" conn.setReadOnly(false); conn.setAutoCommit(false); conn.createStatement().executeUpdate("UPDATE some_table …."); conn.commit(); // 現在在從庫上執行查詢,驅動會自動選擇一台從庫執行查詢 conn.setReadOnly(true); ResultSet rs = conn.createStatement().executeQuery("SELECT a,b FROM alt_table"); ……. }}

4 Spring TX實現讀寫分離

項目使用Spring Transaction管理事務,註解Transactional已經幫我們做了封裝,註解屬性 readOnly,預設為 false。

/** * {@code true} if the transaction is read-only. * Defaults to {@code false}. * <p>This just serves as a hint for the actual transaction subsystem; * it will <i>not necessarily</i> cause failure of write access attempts. * A transaction manager which cannot interpret the read-only hint will * <i>not</i> throw an exception when asked for a read-only transaction. * @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly() */boolean readOnly() default false;

簡單用法

@Transactional(readOnly = true)public List<Enterprise> queryList(EnterpriseCriteria criteria) { return enterpriseDao.queryList(criteria);}

在現有項目上拉了一個分支,使用VMWare上搭建的MariaDB主從庫進行測試,確實能實現讀寫分離。

5 還需要了解的擴展知識

有空時應該進一步讀一下程式碼,看看以下一些內容:

  1. Transactional的註解,是怎樣逐漸調用到Connection.setReadOnly()的?
  2. 多個Transactional註解的函數調用時,readOnly參數是否會每次都調用?