公司的项目使用了mysql的主从结构,本来slave只是做备份使用,后面希望读取能从slave取,而写还是到master里面这样以便提高效率。网上查了下,目前读写分离有两种方式:
一是使用mysql proxy
二就是程序端控制
其实我更喜欢mysql proxy这种方式,因为对应用完全透明。但程序端的分离也可以,mysql本身提供了ReplicationConnection,当数据库连接connection.setReadonly=true时会从slave数据库里面取数据。详细内容见:http://dev.mysql.com/doc/refman/5.1/en/connector-j-reference-replication-connection.html
这样的话一个方案就是利用spring的事务的readonly特性,对项目进行改造,这样对于应用的侵入还可以接受。如果使用的是jpa的话,可以利用事务配置里面的jpaDialect,进行配置
<!-- transactionManager -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="jpaDialect">
<bean class="xxx.support.hibernate.HibernateJpaReadonlySupportDialect" />
</property>
</bean>
HibernateJpaReadonlySupportDialect里面只要在begin里面:
@Override
public Object beginTransaction(final EntityManager entityManager,
final TransactionDefinition definition)
throws PersistenceException, SQLException, TransactionException {
...
ConnectionHandle connHandle = getJdbcConnection(entityManager, definition.isReadOnly());
Connection conn = connHandle.getConnection();
conn.setReadOnly(definition.isReadOnly());
...
}
加上这段就可以了,如果是使用hibernate或者jdbc,也可以直接继承spring提供的事务处理类,设置conn的readOnly属性。
jdbc事务的话:
public class DataSourceReadonlyTransactionManager extends DataSourceTransactionManager{
private static final long serialVersionUID = 6611124435285107181L;
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
super.doBegin(transaction, definition);
try {
Method getConnectionHolder = transaction.getClass().getMethod("getConnectionHolder", null);
ConnectionHolder connectionHolder = (ConnectionHolder)getConnectionHolder.invoke(transaction, null);
Connection con = null;
con = connectionHolder.getConnection();
if(null != con)
con.setReadOnly(definition.isReadOnly());
} catch (Exception ignore) {
}
}
}
hibernate的话和jdbc类似。
最后就是相应的所有service方法如果只是读取数据,就需要在事务上加上readonly属性,这点比较麻烦,因为我们都用的注解配置事务的,如果使用xml通配的话倒是很简单。
这个方案的缺点是如果事务里面有写的操作那么整个事务里面就都会操作master数据库,所以分离的效果差点。