读写分离 的字面意思,就是将数据库的读和写分开。在整个结果体系上的意思,就是写使用数据库的master节点,读使用slave或者master节点。
这样,讲读的数据库压力从写库中分离,转移到slave节点上,减少写库的存储压力,并且也能够避免写库中数据锁导致查询的卡顿情况。
在网上的资料中有很多的方式实现读写分离,比如中间件。也有部分代码实现的情况。
在spring中,提供了实现读写分离的接口,然而在我的使用过程中发现,采用spring的方式进行读写分离,没有办法处理数据库事物的情况发生。
我设想了两个场景进行读写分离;
1 ,无事物状态, 使用读库查询数据。
2, 有事物状态,采用写库查询,更新数据。
在mysql的jdbctemplate的代码中,发现能够判断当前线程是否启用事物的状态,因此可以采用它在数据源上封装一层功能,获取数据库连接时,如果存在事物,返回写库的连接,如果不存在事物,返回读库的连接。
代码:
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.List;
import java.util.logging.Logger;
import javax.sql.DataSource;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* 读写分离数据源
*
* @date : 2015年11月23日
* @comment :
*/
public class ReadAndWriteDataSource implements DataSource {
private static org.slf4j.Logger log = LoggerFactory.getLogger(ReadAndWriteDataSource.class);
private DataSource master;
private List<DataSource> slaves;
private volatile int index;
private int slaveSize;
public DataSource getMaster() {
return master;
}
public void setMaster(DataSource master) {
this.master = master;
}
public List<DataSource> getSlaves() {
return slaves;
}
public void setSlaves(List<DataSource> slaves) {
this.slaves = slaves;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return master.getLogWriter();
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
master.setLogWriter(out);
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
master.setLoginTimeout(seconds);
}
@Override
public int getLoginTimeout() throws SQLException {
return master.getLoginTimeout();
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return master.getParentLogger();
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
return master.unwrap(iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return master.isWrapperFor(iface);
}
private Connection getSlaveConnection() throws SQLException {
if (slaveSize == 0) {
return master.getConnection();
} else {
return slaves.get((index % slaveSize)).getConnection();
}
}
private Connection getSlaveConnection(String username, String password) throws SQLException {
if (slaveSize == 0) {
return master.getConnection(username, password);
} else {
return slaves.get((index % slaveSize)).getConnection(username, password);
}
}
private boolean isWrite() {
boolean read = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
if (read) {
if (log.isDebugEnabled()) {
log.debug("*************** return slave dataSouce*****************");
}
return false;
}
if (log.isDebugEnabled()) {
log.debug("**************** return master dataSouce******************");
}
return true;
}
@Override
public Connection getConnection() throws SQLException {
if (isWrite()) {
return master.getConnection();
} else {
return getSlaveConnection();
}
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
if (isWrite()) {
return master.getConnection(username, password);
} else {
return getSlaveConnection(username, password);
}
}
public void init() throws SQLException {
if (null == master) {
throw new SQLException("master can't be null");
}
if (null != slaves) {
slaveSize = slaves.size();
}
}
}
修改spring的配置为:
<bean id="baseDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName">
<value>${jdbc.connection.driver_class}</value>
</property>
<property name="maxWait">
<value>60000</value>
</property>
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="removeAbandoned">
<value>true</value>
</property>
<property name="removeAbandonedTimeout">
<value>1800</value>
</property>
<property name="filters" value="stat,counter,wall,config,mergeStat" />
<property name="useGlobalDataSourceStat" value="true" />
</bean>
<bean id="writer" parent="baseDataSource" init-method="init"
destroy-method="close">
<property name="url">
<value>${jdbc.connection.url01} </value>
</property>
<property name="username">
<value>${jdbc.connection.username01}</value>
</property>
<property name="password">
<value>${jdbc.connection.password01}</value>
</property>
<property name="maxActive">
<value>${jdbc.max.pool.size01}</value>
</property>
<property name="initialSize">
<value>${jdbc.min.pool.size01}</value>
</property>
<property name="minIdle">
<value>${jdbc.min.pool.size01}</value>
</property>
</bean>
<bean id="reader" parent="baseDataSource" init-method="init"
destroy-method="close">
<property name="url">
<value>${jdbc.connection.url02} </value>
</property>
<property name="username">
<value>${jdbc.connection.username02}</value>
</property>
<property name="password">
<value>${jdbc.connection.password02}</value>
</property>
<property name="maxActive">
<value>${jdbc.max.pool.size02}</value>
</property>
<property name="initialSize">
<value>${jdbc.min.pool.size02}</value>
</property>
<property name="minIdle">
<value>${jdbc.min.pool.size02}</value>
</property>
</bean>
<bean id="dataSource1" class="com.knet.datasource.ReadAndWriteDataSource"
init-method="init">
<property name="master" ref="writer" />
<property name="slaves">
<list>
<ref bean="reader" />
</list>
</property>
</bean>
---- 事物配置略
运行代码,发现在spring事物管理中,开启事物时就获取了数据库连接,然后才会在无事物将 TransactionSynchronizationManager.isCurrentTransactionReadOnly()=true。采用LazyConnectionDataSourceProxy 来处理这种情况。
加上配置 <bean id="dataSource"
class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
<constructor-arg ref="dataSource1" />
</bean>
使用代码,发现已经读写分离完成。
工程代码:
http://git.oschina.net/lkclkc88/RWS