方案一

使用spring抽象路由数据源 AbstractRoutingDataSource + 自定义注解ReadOnly ,切面实现指定从库,由开发判断在service层是否只读从库来使用注解@ReadOnly

主要类

spring 提供AbstractRoutingDataSource 动态数据源 class DynamicDataSource extends AbstractRoutingDataSource

class DataSourceHolder 设置指定数据源

class ReadOnlyInterceptor 切面实现指定使用从库

代码展示

properties文件配置

spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.master.url=@master.jdbc.url@
spring.datasource.master.username=@master.jdbc.username@
spring.datasource.master.password=@master.jdbc.password@
spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave.url=@slave.jdbc.url@
spring.datasource.slave.username=@slave.jdbc.username@
spring.datasource.slave.password=@slave.jdbc.password@

Dbconfig

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@ConfigurationProperties(prefix = "spring.datasource")
@Component
public class DbConfig {/*** 数据加密解密秘钥*/private String secretKey;/*** 最大连接数*/private int maxActive;/*** 最小连接数*/private int minIdle;/*** 获取连接时最大等待时间*/private int maxWait;/*** 查询超时时间*/private int queryTimeout;private Source staff;private Source master;private Source slave;private Source insurance;public static class Source {private String driverClassName;private String url;private String username;private String password;public Source() {}public String getDriverClassName() {return driverClassName;}public void setDriverClassName(String driverClassName) {this.driverClassName = driverClassName;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}}public int getMaxActive() {return maxActive;}public void setMaxActive(int maxActive) {this.maxActive = maxActive;}public int getMinIdle() {return minIdle;}public void setMinIdle(int minIdle) {this.minIdle = minIdle;}public int getMaxWait() {return maxWait;}public void setMaxWait(int maxWait) {this.maxWait = maxWait;}public int getQueryTimeout() {return queryTimeout;}public void setQueryTimeout(int queryTimeout) {this.queryTimeout = queryTimeout;}public String getSecretKey() {return secretKey;}public void setSecretKey(String secretKey) {this.secretKey = secretKey;}public Source getStaff() {return staff;}public void setStaff(Source staff) {this.staff = staff;}public Source getMaster() {return master;}public void setMaster(Source master) {this.master = master;}public Source getSlave() {return slave;}public void setSlave(Source slave) {this.slave = slave;}public Source getInsurance() {return insurance;}public void setInsurance(Source insurance) {this.insurance = insurance;}
}

数据库配置 DbWorklistConfig

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** @author chenjun 450425075@qq.com* @descript:* @date 2019/12/27 10:16*/@Configuration
@MapperScan(basePackages = {"com.hyhs.hschefu.oa.manager.dao"}, sqlSessionFactoryRef = "worklistSqlSessionFactory")
public class DbWorklistConfig {public static final String WORKLIST_TRANSACTION_MANAGER_NAME = "worklistTransactionManager";/*** 主数据源** @return*/@Primary@Bean(name = "worklistDSMaster")public DataSource worklistDSMaster(DbConfig dbConfig) {DruidDataSource ds = new DruidDataSource();ds.setDriverClassName(dbConfig.getMaster().getDriverClassName());ds.setUrl(dbConfig.getMaster().getUrl());ds.setUsername(dbConfig.getMaster().getUsername());ds.setPassword(dbConfig.getMaster().getPassword());ds.setMaxActive(dbConfig.getMaxActive());ds.setMinIdle(dbConfig.getMinIdle());ds.setMaxWait(dbConfig.getMaxWait());ds.setQueryTimeout(dbConfig.getQueryTimeout());ds.setTestWhileIdle(true);ds.setValidationQuery("select 1");ds.setTimeBetweenEvictionRunsMillis(30000);return ds;}/*** 从库配置** @return*/@Bean(name = "worklistDSSlave")public DataSource worklistDSSlave(DbConfig dbConfig) {DruidDataSource ds = new DruidDataSource();ds.setDriverClassName(dbConfig.getSlave().getDriverClassName());ds.setUrl(dbConfig.getSlave().getUrl());ds.setUsername(dbConfig.getSlave().getUsername());ds.setPassword(dbConfig.getSlave().getPassword());ds.setMaxActive(dbConfig.getMaxActive());ds.setMinIdle(dbConfig.getMinIdle());ds.setMaxWait(dbConfig.getMaxWait());ds.setQueryTimeout(dbConfig.getQueryTimeout());ds.setTestWhileIdle(true);ds.setValidationQuery("select 1");ds.setTimeBetweenEvictionRunsMillis(30000);return ds;}/*** 动态数据源** @return*/@Beanpublic DynamicDataSource dynamicDataSource(DbConfig dbConfig) {DynamicDataSource dynamicDataSource = new DynamicDataSource();Map<Object, Object> map = new HashMap<>();map.put(DataSourceHolder.MASTER_DB, worklistDSMaster(dbConfig));map.put(DataSourceHolder.SLAVE_DB, worklistDSSlave(dbConfig));dynamicDataSource.setDefaultTargetDataSource(worklistDSMaster(dbConfig));dynamicDataSource.setTargetDataSources(map);return dynamicDataSource;}@Bean("worklistSqlSessionFactory")@Primarypublic SqlSessionFactory worklistSqlSessionFactory(DynamicDataSource dataSource) throws Exception {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(dataSource);factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/dao/**/*.xml"));factoryBean.setTypeAliasesPackage("com.hyhs.hschefu.oa.manager.model");
// factoryBean.setPlugins(new CatMybatisPlugin());
//暂不考虑用拦截器实现
//DataSourceInterceptor dataSourceInterceptor = new DataSourceInterceptor();
// factory.setPlugins(new Interceptor[]{dataSourceInterceptor});SqlSessionFactory sqlSessionFactory = factoryBean.getObject();sqlSessionFactory.getConfiguration().setMapUnderscoreToCamelCase(true);return sqlSessionFactory;}@Bean(name = "worklistSqlSessionTemplate")@Primarypublic SqlSessionTemplate worklistSqlSessionTemplate(@Qualifier("worklistSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory);return template;}@Bean(name = WORKLIST_TRANSACTION_MANAGER_NAME)@Primarypublic DataSourceTransactionManager worklistTransactionManager(DynamicDataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}

注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;//自定义注解 @ ReadOnly
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {
}

拦截器:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;//实现切面 并设置切面优先级高于事务切面
@Aspect
@Component
public class ReadOnlyInterceptor implements Ordered {private static final Logger log = LoggerFactory.getLogger(ReadOnlyInterceptor.class);@Around("@annotation(readOnly)")public Object setRead(ProceedingJoinPoint joinPoint, ReadOnly readOnly) throws Throwable {try {DataSourceHolder.setDataSource(DataSourceHolder.SLAVE_DB);log.info("使用数据库为:{}", DataSourceHolder.SLAVE_DB);return joinPoint.proceed();} finally {
//清除一方面为了避免内存泄漏,更重要的是避免对后续在本线程上执行的操作产生影响DataSourceHolder.remove();log.info("清除threadLocal");}}@Overridepublic int getOrder() {return 0;}
}

数据源:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** @author chenjun 450425075@qq.com* @descript:* @date 2019/12/27 10:29*///动态数据源 继承spring 的 AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {private static Logger log = LoggerFactory.getLogger(DynamicDataSource.class);@Overrideprotected Object determineCurrentLookupKey() {String type = DataSourceHolder.getDataSource();if (DataSourceHolder.SLAVE_DB == type) {log.info("使用从库 {}", type);}return type;}
}

DataSourceHolder:

public class DataSourceHolder {public static String MASTER_DB = "master";public static String SLAVE_DB = "slave";static ThreadLocal<String> dataSourceMap = new ThreadLocal<>();public static void setDataSource(String dataSourceKey) {if (MASTER_DB.equals(dataSourceKey) || SLAVE_DB.equals(dataSourceKey)) {dataSourceMap.set(dataSourceKey);} else {dataSourceMap.set(MASTER_DB);}}public static String getDataSource() {return dataSourceMap.get() == null ? MASTER_DB : dataSourceMap.get();}public static void remove() {dataSourceMap.remove();}
}

方案二 利用spring抽象路由数据源+MyBatis拦截器来实现自动的读写分离

主要类和接口

1.Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源

2.spring事务管理器 DataSourceTransactionManager

3.实现mybatis的拦截器接口 Interceptior

@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) })

方案三 基于mysql中间件的读写分离

mycat 中间件

1. 在主从同步正常时,主从读写分离

2. 当主从复制出现延迟且延迟时间大于10秒(可自定义延迟时间)后,读操作会发到主库上,从库不再接受读操作,目的是防止延迟过大导致读到以前的旧数据。

3. 当从库追上主库后,或延迟时间小于10秒时,从库自动接受读操作

4. 当主库宕掉后,读写操作都会发到从库上。(切换时间5s-30s)

5. 从库宕掉后,读写操作都会发到主库上。(切换时间5s-30s)

ProxySQL 中间件

。。。

Atlas 中间件

。。。

总结:基于目前当前项目的现状态,方案1根据开发人员人为去判断service层方法哪些是只读 决定使用从库更加稳妥,逐步实现读请求分流到从库,个人觉得目前简单实现可按方案1去实现读写分离。

读写分离方案_项目读写分离方案相关推荐

  1. mysql 常用优化方案_项目中常用的 19 条 MySQL 优化方案

    声明一下:下面的优化方案都是基于 " Mysql-索引-BTree类型 " 的 一.EXPLAIN 做MySQL优化,我们要善用 EXPLAIN 查看SQL执行计划. 下面来个简单 ...

  2. 万兆局域网方案_家庭万兆方案性价比之选,10G网速不是梦!

    Hello,大家好!我是Liuspy. 一.前言 去年年底的时候楼主完成了家庭万兆网络的部署,体验到了10G网速的魅力,确实给了楼主不小的惊喜.自然第一时间晒单分享了组建的经验给大家.上次的组建方案虽 ...

  3. 服务器项目技术方案,直播项目技术实现方案(工作室)

    一.常规直播app功能 1.聊天 私聊.聊天室.点亮.推送.黑名单等; 2.礼物 普通礼物.豪华礼物.红包.排行榜.第三方充值.内购.礼物动态更新.提现等: 3.直播列表 关注.热门.最新.分类直播用 ...

  4. 教室录播系统方案_录播教室装修方案

    录播教室装修要求 本次案例要装修的录播教室总面积为158平米,录播教室长度为12.93米,宽为8.94.观摩室长度为6.35米,宽度6.7米,高度2.8米.结合对该企业实地现场考察,出具详细录播教室装 ...

  5. 使用vassitx配色方案_选择网站配色方案的实用方法

    想象一个没有色彩的世界. 那世界将是一个无聊的地方,对吧? 这适用于没有颜色的网站. 但是,确定网站的颜色并非易事. 我个人经常被问到如何选择合适的颜色 . 如果您也想知道同一件事,那么这篇文章可能适 ...

  6. 微信抢红包的方案_微信关注抢红包方案

    微信关注抢红包方案 随着经济多元化的发展, 微信走进千家万户, 大多数人都开始关 注微信, 微信红包更是新一代的吸睛必备神器. 为了提升公司的知名 度,扩大公司的外界影响力,公司首次推出了微信抢红包活 ...

  7. linux中权限分离,linux多项目资源分离权限问题

    在一个linux服务器上部署着多个项目,许多项目的资源文件软连接到其他文件夹下,那么就会出现 open_basedir=/vagrant/vbee/:/tmp/:/proc/ 上传资源的时候会出现这样 ...

  8. mysql注入漏洞修复方案_注入漏洞修复方案

    近看到网上曝出的dedecms版本的一个注入漏洞利用,漏洞PoC和分析文章也已在网上公开.但是在我实际测试过程当中,发现无法复现.南昌办公应用培训南京电脑维护必备原因是此漏洞的利用需要一定的前提条件, ...

  9. htpc电脑方案_完美高清方案!网友实战超迷你HTPC

    随着高清应用的普及,客厅里组配一台专用的高清平台的人们也越来越多,我们都知道高清平台在硬件上看,大体差别不会不大,但高清平台的硬件均有一定的偏向性及属性,如一般高清平台主机均需要有节能.低功耗.体积小 ...

最新文章

  1. 我的第一个Github项目上线了
  2. 目标检测迁移学习_使用迁移学习检测疟疾
  3. Linux vim 的编码格式,linux下的文件编码,vim编码
  4. Redis安装+启动报错
  5. android ——Toolbar
  6. MFC采用定时器绘制简单动画
  7. 转为html5播放器插件,15个HTML5播放器插件
  8. 【位置推iMessage苹果推送】 软件安装AVPlayerItem(URL: movieUrl) player
  9. 苹果CMS自动定时采集教程
  10. 软件测试显卡最高清晰度,硬件碾压机再临? GTA5显卡性能全测试
  11. Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data-----阅读阶段
  12. c语言之bbs管理系统,编写c语言的软件 纯C语言编写图书管理系统WORD文档bbszp.doc...
  13. 苹果开发者账号网页版续费失败支付报错解决办法
  14. 阿里云设置密钥对登录服务器
  15. android 获取经纬度(百度地图)
  16. RSSI-RSRP-RSRQ
  17. 如何将夜晚图片转化为白天图片 matlab,教你简单几步将白天图片转换成夜景图_资源库...
  18. Android文本输入框EditText属性和方法说明
  19. scrapy导出数据
  20. 唐诗三百首加密软件如何使用_如何对PDF文件加密?原来PDF加密用这个软件就可以!...

热门文章

  1. 【电脑帮助】解决Wind10系统spacedesk程序开机自启动的问题
  2. springboot响应结果超长(7.8M)浏览器无法接收
  3. Collections.sort的两种用法
  4. Java操作某方法时报错:java.lang.NoSuchMethodError
  5. IDEA下查看Java字节码(插件ByteCode Viewer)
  6. 查看Ubuntu系统的版本
  7. 查询目标服务器系统,查看目标服务器的操作系统
  8. mybatis mysql 存储过程传入对象_mybatis 调用mysql存储过程 带输出输入参数
  9. 数据结构和算法系列13 五大查找之哈希查找
  10. 【架构设计的艺术】Kafka如何通过精妙的架构设计优化JVM GC问题?