數據庫的主從復制環境已經配好,該要解決系統如何實現讀寫分離功能了。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 還需要了解的擴展知識
有空時應該進一步讀一下代碼,看看以下一些內容:
- Transactional的注解,是怎樣逐漸調用到Connection.setReadOnly()的?
- 多個Transactional注解的函數調用時,readOnly參數是否會每次都調用?