干货丨springboot环境实现读写分离
Posted 中兴开发者社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了干货丨springboot环境实现读写分离相关的知识,希望对你有一定的参考价值。
作者简介
作者黄永猛热衷各种软件架构,喜欢研究源码以及各种架构设计方案。该文主要是针对高并发的情况下数据层的高负载的一种解决方案。
读写分离使用的好处这里不做过多的阐述,相信看这篇文章的人都已经很清楚业务使用场景;下面我们直接进入主题:
读写分离其实就是在底层替换数据源即可,针对一主一从的情况下数据源切换比较简单,那么在一主多从的情况下如何有效的切换数据源则是一件很头疼的事情,简单的算法可以用随机,稍微改进则可以用轮寻,再改进的是不是可以用minCollectsUsed?针对这些情况还是看业务场景决定吧;
下面是根据一主一从的场景下讲述:
首先要了解什么时候切换,如何切换
在所有的读的时候进行切换,在update,delete,savfe的时候进行切换;如何判断这些呢?若是在集群的环境下我们可以通过路由控制服务的走向,在服务段配置下即可;但是这样移植性比较差;在不污染代码的同时我们可以采用AOP定义切入点来实现;实现方式如下:
@Pointcut("execution(* com.zte.wdcloud.mybatis.dao.impl.BaseDaoImpl.list*(..)) ||execution(* com.zte.wdcloud.mybatis.dao.impl.BaseDaoImpl.count*(..))||execution(* com.zte.wdcloud.mybatis.dao.impl.BaseDaoImpl.pager*(..))")
public void slavePointCut(){}
@Pointcut("execution(* com.zte.wdcloud.mybatis.dao.impl.BaseDaoImpl.save*(..)) || execution(* com.zte.wdcloud.mybatis.dao.impl.BaseDaoImpl.delete*(..))||execution(* com.zte.wdcloud.mybatis.dao.impl.BaseDaoImpl.update*(..))||execution(* com.zte.wdcloud.mybatis.dao.impl.BaseDaoImpl.batchDelete*(..))")
public void masterPointCut(){}
/*@Before("slavePointCut()")
public void setSlaveDataSouce(){
DataSourceContextHolder.read();
}
@Before("masterPointCut()")
public void setMasterDataSource(){
DataSourceContextHolder.write();
}*/
@Around("slavePointCut()")
public Object processedSlave(ProceedingJoinPoint point) throws Throwable{
try{
System.out.println("--------------------------->set database source is slave");
DataSourceContextHolder.read();
Object result=point.proceed();
return result;
}finally {
DataSourceContextHolder.removeDataSource();
}
}
@Around("masterPointCut()")
public Object processedMaster(ProceedingJoinPoint point) throws Throwable{
try{
System.out.println("---------------------------->set database source is master");
DataSourceContextHolder.write();
Object result=point.proceed();
return result;
}finally {
DataSourceContextHolder.removeDataSource();
}
}
aop的两种方式都可以实现(before和around),注释的是一种,可跟根据自己需求选择,在上述代码中有一个DatasourceContextHolder的类,里面定一个了一个线程副本,来存放读写的信息,代码如下:
/**
* @author 黄永猛10221064
* 定义数据源之间的切换
*/
public class DataSourceContextHolder {
private static final Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
private static final ThreadLocal<String> local=new ThreadLocal<String>();
public static void read(){
log.info("------------》切换到读库");
local.set(DataSourceType.slaveDataSource.getDataSource());
}
public static void write(){
log.info("------------》切换到写库");
local.set(DataSourceType.masterDataSource.getDataSource());
}
public static String getCurrentDataSource(){
log.info("------------》getCurrentDataSource");
return local.get();
}
public static void removeDataSource(){
local.remove();
}
threadlocal的用法不多做解释,网上很多人的博客讲述的很详细;但是需要说明的一点,它不是本地线程这样来说的。。。
2.数据源的定义以及数据源路由的控制
数据源的定义如下,springboot中的config信息就是将传统的springmvc中的xml代码化,看起来更方便;
protected Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);
// @Scope("prototype")
/*
* @Bean
*
* @Primary public SqlSession SqlSession() throws Exception{ SqlSession
* sqlSession=new SqlSessionTemplate(sqlSessionFactory()); return
* sqlSession; }
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
// return super.sqlSessionFactory(slaveDataSource());
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSouceProxy());
// sqlSessionFactoryBean.setTypeAliasesPackage("com.*.*");
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("/config/mybatis-config.xml"));
PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean
.setMapperLocations(resourcePatternResolver.getResources("classpath*:com/zte/wdcloud/mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public AbstractRoutingDataSource dataSouceProxy() {
MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource();
Map<Object, Object> targetDataSource = new HashMap<Object, Object>();
targetDataSource.put(DataSourceType.slaveDataSource.getDataSource(), slaveDataSource());
targetDataSource.put(DataSourceType.masterDataSource.getDataSource(), masterDataSource());
proxy.setTargetDataSources(targetDataSource);
proxy.setDefaultTargetDataSource(slaveDataSource());
return proxy;
}
@Bean
public PlatformTransactionManager PlatformTransactionManager() {
return new DataSourceTransactionManager(dataSouceProxy());
}
@Value("${datasource.type}")
private Class<? extends DataSource> dataSourceType;
@Primary
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "datasource.master")
public DataSource masterDataSource() {
logger.info("-----初始化master数据库配置-----");
return DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "datasource.slave")
public DataSource slaveDataSource() {
logger.info("-----初始化slave数据库配置-----");
return DataSourceBuilder.create().type(dataSourceType).build();
}
MyAbstractRoutingDataSource类重写了AbstractRoutingDataSource中路由数据源;若是在多从的环境中,数据库调用负载均衡的算法则在此方法实现,下面看一下数据库的配置信息:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
master:
url: jdbc:mysql://10.67.18.17:3306/xxxxxx?useUnicode:true&characterEncoding:UTF-8
username: xxxxxxx
password: xxxxxxx
driver-class-name: com.mysql.jdbc.Driver
filters: stat
maxActive: 200
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQueryTimeout: 900000
validationQuery: SELECT SYSDATE() from dual
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
slave:
url: jdbc:mysql://10.67.18.17:3306/yyyyyyyy?useUnicode:true&characterEncoding:UTF-8
username: xxxxx
password: xxxxxx
driver-class-name: com.mysql.jdbc.Driver
filters: stat
maxActive: 200
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQueryTimeout: 900000
validationQuery: SELECT SYSDATE() from dual
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
哦,忘了说了,这里的数据源连接采用的是阿里的druid;
项目搭建没有采用以往的Spingjpa,而是用mybatis替换,个人认为是更灵活的~
以上是关于干货丨springboot环境实现读写分离的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot+ShardingSphereJDBC实现读写分离!
SpringBoot+ShardingSphereJDBC实现读写分离!
SpringBoot+ShardingSphereJDBC实现读写分离!