今天我們來說一說主從庫的讀寫分離的問題
主從結(jié)構(gòu)
一般情況下主從的結(jié)構(gòu)是由一個Master和多個Slave來組成的, Slave的個數(shù)和數(shù)據(jù)庫的業(yè)務(wù)訪問量有密切的關(guān)系
MySQL讀寫分離配置的主要流程
- 在進(jìn)行數(shù)據(jù)的更改之前Master會通知存儲引擎開啟事務(wù),Master會把數(shù)據(jù)庫的操作記錄在一個Binary Log中,在寫入完成之后雕沉,Master通知存儲引擎提交事務(wù)菱魔,這樣的一次操作稱為一次二進(jìn)制日志事件;
- Slave開啟一個IO線程梳玫,把Binary Log中的日志拷貝到自己的Relay log中,如果讀取完成之后,Slave的這個線程就會睡眠候生,等待Master的Binary產(chǎn)生新的數(shù)據(jù);
- Slave會把Delay Log中的數(shù)據(jù)重新執(zhí)行绽昼,使得日志中的數(shù)據(jù)重現(xiàn)在Slave的數(shù)據(jù)庫中唯鸭;
一、主從讀寫分離的配置
- 準(zhǔn)備兩臺服務(wù)器并分別安裝MySQL軟件
- 配置主服務(wù)器硅确,編輯主服務(wù)器的配置文件
- 在配置文件的mysqld節(jié)點下面添加mysql-id和log-bin以及l(fā)og-bin-index目溉,如下
server-id=1 log-bin=master-bin log-bin-index=master-bin.index
- 配置完成后重啟:
service mysqld restart
- 查看Master的狀態(tài):
show master status
- 創(chuàng)建用戶并賦予從服務(wù)器的訪問權(quán)限:
create user '<user-name>'; grant replication slave on *.* to '<user-name>'@'<host>' identified by '<access-pwd>'; flush privileges;
- 在配置文件的mysqld節(jié)點下面添加mysql-id和log-bin以及l(fā)og-bin-index目溉,如下
- 配置從服務(wù)器,編輯配置文件
- 在配置文件的mysqld節(jié)點下面添加如下的內(nèi)容,需要注意的是干掉其中server-id=1菱农,在mysqld中重新添加如下內(nèi)容:
server-id=2 relay-log=slave-relay-bin relay-log-index=slave-relay-bin.index
- 配置完成后重啟:
service mysqld restart
- 把從服務(wù)器掛到主服務(wù)器之下缭付,連接到從服務(wù)器上,執(zhí)行如下指令
change master to master_host='<master-ip>',master_port=<master-ip>,master_user='<user-name>',master_password='<access-pwd>',master_log_file='<master_status-file-name>',master-log-pos=0;
- 開始監(jiān)聽主服務(wù)器:
start slave
- 查看從庫的狀態(tài):
show slave status \G
- 在配置文件的mysqld節(jié)點下面添加如下的內(nèi)容,需要注意的是干掉其中server-id=1菱农,在mysqld中重新添加如下內(nèi)容:
- 測試主從:在主庫上進(jìn)行操作循未,后查看從庫的數(shù)據(jù)變化
二陷猫、MySQL讀寫分離代碼編寫
-
編寫一個讀寫分離的類
DynamicDataSource
,這個類繼承自AbstractRoutingDataSource
,實現(xiàn)其中的方法绣檬,如:import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceHolder.getDataSourceType(); } }
-
編寫一個用來維護(hù)連接類型的類
DynamicDataSourceHolder
舅巷,為了線程安全可以使用ThreadLocal
來存放兩個類型:Master
和Slave
:import org.springframework.util.Assert; public class DynamicDataSourceHolder { public static final String DATASOURCE_TYPE_MASTER = "master"; public static final String DATASOURCE_TYPE_SLAVE = "slave"; private static ThreadLocal<String> typeHolder = new ThreadLocal<>(); public static String getDataSourceType() { String type = typeHolder.get(); if (type == null) { type = DATASOURCE_TYPE_MASTER; } return type; } public static void setDataSourceType(String type) { Assert.notNull(type, "type not allowed to be null"); typeHolder.set(type); } public static void clearDataSourceType() { typeHolder.remove(); } }
-
編寫一個用來根據(jù)請求自動返回數(shù)據(jù)庫連接類型的攔截器的類
DynamicDataSourceInteceptor
,該類實現(xiàn)MyBatis的攔截器接口:org.apache.ibatis.plugin.Interceptor
:import java.util.Locale; import java.util.Properties; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.keygen.SelectKeyGenerator; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.springframework.transaction.support.TransactionSynchronizationManager; @Intercepts({ @Signature(type=Executor.class, method="update", args={MappedStatement.class, Object.class}), @Signature(type=Executor.class, method="query", args={MappedStatement.class, RowBounds.class, ResultHandler.class, Object.class}) }) public class DynamicDataSourceInteceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); String type = DynamicDataSourceHolder.DATASOURCE_TYPE_MASTER; if (txActive == false) { Object[] args = invocation.getArgs(); MappedStatement statement = (MappedStatement) args[0]; if (statement.getSqlCommandType().equals(SqlCommandType.SELECT)) { if (statement.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) { type = DynamicDataSourceHolder.DATASOURCE_TYPE_MASTER; } else { BoundSql boundSql = statement.getSqlSource().getBoundSql(args[1]); String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\n\\r\\t]", " "); if (sql.matches(".*insert\\u0020.*|.*update\\u0020.*|.*delete\\u0020.*")) { type = DynamicDataSourceHolder.DATASOURCE_TYPE_MASTER; } else { type = DynamicDataSourceHolder.DATASOURCE_TYPE_SLAVE; } } } } DynamicDataSourceHolder.setDataSourceType(type); return invocation.proceed(); } @Override public Object plugin(Object target) { if (target instanceof Executor) { return Plugin.wrap(target, this); } return target; } @Override public void setProperties(Properties properties) { } }
-
在Mybatis配置中配置攔截器
<plugins> <plugin interceptor="org.sherekr.dfo.core.dao.datasource.DynamicDataSourceInteceptor" /> </plugins>
-
Spring配置文件中配置兩個數(shù)據(jù)源河咽,一個用來執(zhí)行DML的數(shù)據(jù)源钠右,一個用來執(zhí)行DQL的數(shù)據(jù)源,同時配置動態(tài)數(shù)據(jù)源和懶連接數(shù)據(jù)源代理
<bean id="abstractDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" abstract="true" destroy-method="close"> <property name="driverClass" value="${jdbc.driver}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxPoolSize" value="30" /> <property name="minPoolSize" value="10" /> <property name="autoCommitOnClose" value="false" /> <property name="checkoutTimeout" value="10000" /> <property name="acquireRetryAttempts" value="2" /> </bean> <bean id="masterDataSource" parent="abstractDataSource"> <property name="jdbcUrl" value="${jdbc.master.url}" /> </bean> <bean id="slaveDataSource" parent="abstractDataSource"> <property name="jdbcUrl" value="${jdbc.slave.url}" /> </bean> <bean id="dynamicDataSource" class="org.sherekr.dfo.core.dao.datasource.DynamicDataSource"> <property name="targetDataSources"> <map> <entry key="master" value-ref="masterDataSource" /> <entry key="slave" value-ref="slaveDataSource" /> </map> </property> </bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy"> <property name="targetDataSource" ref="dynamicDataSource" /> </bean>
這個代碼已經(jīng)是幾年前寫的代碼了忘蟹,運(yùn)行時可能會遇到一些問題飒房,大家就見招拆招吧,如果遇到問題歡迎吐我