springboot环境下实现读写分离

来源:互联网 发布:菜鸟网络工资 编辑:程序博客网 时间:2024/06/05 09:38

本文讲述springboot环境下构造读写分离的框架;

读写分离 有哪些好处呢,相信不用多讲,大家能够花时间看这篇文章,那么就很清楚它的应用场景了,下面我们开始直接进入正题;

读写分离其实就是在底层替换数据源即可,针对一主一从的情况下数据源切换比较简单,那么在一主多从的情况下如何有效的切换数据源则是一件很头疼的事情,简单的算法可以用随机,稍微改进则可以用轮寻,再改进的是不是可以用minCollectsUsed?针对这些情况还是看业务场景决定吧;

目前讲述的是一主一从的情况下

下面是根据一主一从的场景下讲述:

首先要了解什么时候切换,如何切换

在所有的读的时候进行切换,在update,delete,savfe的时候进行切换;如何判断这些呢?若是在集群的环境下我们可以通过路由控制服务的走向,在服务段配置下即可;但是这样移植性比较差;在不污染代码的同时我们可以采用AOP定义切入点来实现;实现方式如下:

@Pointcut("execution(* com.xx.wdcloud.mybatis.dao.impl.BaseDaoImpl.list*(..)) ||execution(* com.xx.wdcloud.mybatis.dao.impl.BaseDaoImpl.count*(..))||execution(* com.xx.wdcloud.mybatis.dao.impl.BaseDaoImpl.pager*(..))")   public void slavePointCut(){}  @Pointcut("execution(* com.xx.wdcloud.mybatis.dao.impl.BaseDaoImpl.save*(..)) || execution(* com.xx.wdcloud.mybatis.dao.impl.BaseDaoImpl.delete*(..))||execution(* com.xx.wdcloud.mybatis.dao.impl.BaseDaoImpl.update*(..))||execution(* com.xx.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的类,里面定一个了一个线程副本,来存放读写的信息,代码如下:
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代码化,看起来更方便简介,以前在xml中寻找各种配置,那是一个头大,现在能够清晰明了的对各种进行定义,比如RedisConfig,DruidConfig等。

MyAbstractRoutingDataSource类重写了AbstractRoutingDataSource中路由数据源;若是在多从的环境中,数据库调用负载均衡的算法则在此方法实现;有兴趣的同学可以看下AbstractRoutingDataSource类,看是如何调度这个方法的;

实现代码如下:

protected Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);// @Scope("prototype")/* * @Bean *  * @Primary public SqlSession SqlSession() throws Exception{ SqlSession * sqlSession=new SqlSessionTemplate(sqlSessionFactory()); return * sqlSession; } */@Beanpublic 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/xx/wdcloud/mapper/*.xml"));return sqlSessionFactoryBean.getObject();}@Beanpublic 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;}@Beanpublic 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();}
数据源的配置信息如下,配置了master和slave

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

大概的实现方式如上,其中在核心配置部分,可以通过实现mybatis提供的mybatisautoconfig这个类,这里可以通过集成,然后简化用户的操作,针对其中的一些方法可以根据业务需求重写来满足自己项目的需要,这里我没有集成这个类。