为什么需要读写分离

当项目越来越大和并发越来大的情况下,单个数据库服务器的压力肯定也是越来越大,最终演变成数据库成为性能的瓶颈,而且当数据越来越多时,查询也更加耗费时间,当然数据库数据过大时,可以采用数据库分库分表,同时数据库压力过大时,也可以采用Redis等缓存技术来降低压力,但是任何一种技术都不是万金油,很多时候都是通过多种技术搭配使用,而本文主要就是介绍通过读写分离来加快数据库读取速度

实现方式

读写分离实现的方式有多种,但是多种都需要配置数据库的主从复制,当然也许是有不需要配置的,只是我不知道而已

方式一

数据库中间件实现,如Mycat等数据库中间件,对于项目本身来说,只有一个数据源,就是链接到Mycat,再由mycat根据规则去选择从哪个库获取数据

方式二

代码中配置多数据源,通过代码控制使用哪个数据源,本文也是主要介绍这种方式

读写分离优劣

优点

1.降低数据库读取压力,尤其是有些需要大量计算的实时报表类应用
2.增强数据安全性,读写分离有个好处就是数据近乎实时备份,一旦某台服务器硬盘发生了损坏,从库的数据可以无限接近主库
3.可以实现高可用,当然只是配置了读写分离并不能实现搞可用,最多就是在Master(主库)宕机了还能进行查询操作,具体高可用还需要其他操作

缺点

1.增大成本,一台数据库服务器和多台数据库的成本肯定是不一样的
2.增大代码复杂度,不过这点还比较轻微吧,但是也的确会一定程度上加重
3.增大写入成本,虽然降低了读取成本,但是写入成本却是一点也没有降低,毕竟还有从库一直在向主库请求数据

MySQL主从复制配置

MySQL主从配置是实现读写分离的基本条件,具体实现MySQL主从复制可以参考我之前的文章MySQL主从复制搭建,基于日志(binlog)

数据源配置

spring:application:name: separatemaster:url: jdbc:mysql://192.168.1.126:3307/test?useUnicode=true&characterEncoding=utf8&emptyStringsConvertToZero=trueusername: rootpassword: 123456driver_class_namel: com.mysql.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcemax-active: 20initial-size: 1min-idle: 3max-wait: 600time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000test-while-idle: truetest-on-borrow: falsetest-on-return: falsepoolPreparedStatements: trueslave:url: jdbc:mysql://192.168.1.126:3309/test?useUnicode=true&characterEncoding=utf8&emptyStringsConvertToZero=trueusername: testpassword: 123456driver_class_namel: com.mysql.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcemax-active: 20initial-size: 1min-idle: 3max-wait: 600time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000test-while-idle: truetest-on-borrow: falsetest-on-return: falsepoolPreparedStatements: true

文件中配置了2个数据源,master是写库,slave是读库,为了防止向slave写入,slave的用户只有读取权限
因为代码中需要动态的设置数据源,所以数据源需要通过继承AbstractRoutingDataSource


/*** 动态数据源* @author Raye* @since 2016年10月25日15:20:40*/
public class DynamicDataSource extends AbstractRoutingDataSource {private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<DatabaseType>();@Overrideprotected Object determineCurrentLookupKey() {return contextHolder.get();}public static enum DatabaseType {Master, Slave}public static void master(){contextHolder.set(DatabaseType.Master);}public static void slave(){contextHolder.set(DatabaseType.Slave);}public static void setDatabaseType(DatabaseType type) {contextHolder.set(type);}public static DatabaseType getType(){return contextHolder.get();}
}

contextHolder 是线程变量,因为每个请求是一个线程,所以通过这样来区分使用哪个库
determineCurrentLookupKey是重写的AbstractRoutingDataSource的方法,主要是确定当前应该使用哪个数据源的key,因为AbstractRoutingDataSource 中保存的多个数据源是通过Map的方式保存的

实例化数据源
/*** Druid的DataResource配置类* @author Raye* @since 2016年10月7日14:14:18*/
@Configuration
@EnableTransactionManagement
public class DataBaseConfiguration  implements EnvironmentAware {private RelaxedPropertyResolver propertyResolver1;private RelaxedPropertyResolver propertyResolver2;public DataBaseConfiguration(){System.out.println("####################  DataBaseConfiguration");}public void setEnvironment(Environment env) {this.propertyResolver1 = new RelaxedPropertyResolver(env, "spring.master.");this.propertyResolver2 = new RelaxedPropertyResolver(env, "spring.slave.");}public DataSource master() {System.out.println("注入Master druid!!!");DruidDataSource datasource = new DruidDataSource();datasource.setUrl(propertyResolver1.getProperty("url"));datasource.setDriverClassName(propertyResolver1.getProperty("driver-class-name"));datasource.setUsername(propertyResolver1.getProperty("username"));datasource.setPassword(propertyResolver1.getProperty("password"));datasource.setInitialSize(Integer.valueOf(propertyResolver1.getProperty("initial-size")));datasource.setMinIdle(Integer.valueOf(propertyResolver1.getProperty("min-idle")));datasource.setMaxWait(Long.valueOf(propertyResolver1.getProperty("max-wait")));datasource.setMaxActive(Integer.valueOf(propertyResolver1.getProperty("max-active")));datasource.setMinEvictableIdleTimeMillis(Long.valueOf(propertyResolver1.getProperty("min-evictable-idle-time-millis")));try {datasource.setFilters("stat,wall");} catch (SQLException e) {e.printStackTrace();}return datasource;}public DataSource slave() {System.out.println("Slave druid!!!");DruidDataSource datasource = new DruidDataSource();datasource.setUrl(propertyResolver2.getProperty("url"));datasource.setDriverClassName(propertyResolver2.getProperty("driver-class-name"));datasource.setUsername(propertyResolver2.getProperty("username"));datasource.setPassword(propertyResolver2.getProperty("password"));datasource.setInitialSize(Integer.valueOf(propertyResolver2.getProperty("initial-size")));datasource.setMinIdle(Integer.valueOf(propertyResolver2.getProperty("min-idle")));datasource.setMaxWait(Long.valueOf(propertyResolver2.getProperty("max-wait")));datasource.setMaxActive(Integer.valueOf(propertyResolver2.getProperty("max-active")));datasource.setMinEvictableIdleTimeMillis(Long.valueOf(propertyResolver2.getProperty("min-evictable-idle-time-millis")));try {datasource.setFilters("stat,wall");} catch (SQLException e) {e.printStackTrace();}return datasource;}@Beanpublic DynamicDataSource dynamicDataSource() {DataSource master = master();DataSource slave = slave();Map<Object, Object> targetDataSources = new HashMap<Object, Object>();targetDataSources.put(DynamicDataSource.DatabaseType.Master, master);targetDataSources.put(DynamicDataSource.DatabaseType.Slave, slave);DynamicDataSource dataSource = new DynamicDataSource();dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法dataSource.setDefaultTargetDataSource(master);return dataSource;}}

一共有3个数据源,一个master,一个slave,一个是动态数据源,保存在master和slave,为了防止spring注入异常,所以master和slave都是主动实例化的,并不是交给spring管理

dataSource.setDefaultTargetDataSource(master);

是配置的如果没有配置当前使用哪个数据源的默认数据源,本来是打算配置slave,但是因为事物问题,所以配置的master

Mybatis配置
/*** MyBatis的配置类* * @author Raye* @since 2016年10月7日14:13:39*/
@Configuration
@AutoConfigureAfter({ DataBaseConfiguration.class })
@Slf4j
public class MybatisConfiguration {@Bean(name = "sqlSessionFactory")@Autowiredpublic SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dynamicDataSource);try {SqlSessionFactory session = bean.getObject();MapperHelper mapperHelper = new MapperHelper();//特殊配置Config config = new Config();//具体支持的参数看后面的文档config.setNotEmpty(true);//设置配置mapperHelper.setConfig(config);// 注册自己项目中使用的通用Mapper接口,这里没有默认值,必须手动注册mapperHelper.registerMapper(Mapper.class);//配置完成后,执行下面的操作mapperHelper.processConfiguration(session.getConfiguration());return session;} catch (Exception e) {e.printStackTrace();}return null;}@Bean(name = "sqlSessionTemplate")@Autowiredpublic SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}@Beanpublic MapperScannerConfigurer scannerConfigurer(){MapperScannerConfigurer configurer = new MapperScannerConfigurer();configurer.setSqlSessionFactoryBeanName("sqlSessionFactory");configurer.setSqlSessionTemplateBeanName("sqlSessionTemplate");configurer.setBasePackage("wang.raye.**.mapper");configurer.setMarkerInterface(Mapper.class);return configurer;}
}

MybatisConfiguration 主要是配置的sqlSessionFactory和sqlSessionTemplate,以及Mybatis的扩展框架Mapper的配置,如果不需要Mapper,可以不用配置scannerConfigurer

事物配置

@Configuration
@EnableTransactionManagement
@Slf4j
@AutoConfigureAfter({ MybatisConfiguration.class })
public class TransactionConfiguration extends DataSourceTransactionManagerAutoConfiguration {@Bean@Autowiredpublic DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {log.info("事物配置");return new DataSourceTransactionManager(dynamicDataSource);}
}

事物配置这里有一个坑就是,一旦开启了事物,好像就会切换线程执行,所以并不会使用当前配置的数据源,而会取到默认的数据源,所以只能通过将默认数据源设置为master

AOP切入设置数据源

/*** 数据源的切入面**/
@Aspect
@Component
@Slf4j
public class DataSourceAOP {@Before("execution(* wang.raye.separate.service..*.select*(..)) || execution(* wang.raye.separate.service..*.get*(..))")public void setReadDataSourceType() {DynamicDataSource.slave();log.info("dataSource切换到:slave");}@Before("execution(* wang.raye.separate.service..*.insert*(..)) || execution(* wang.raye.separate.service..*.update*(..)) || execution(* wang.raye.separate.service..*.delete*(..)) || execution(* wang.raye.separate.service..*.add*(..))")public void setWriteDataSourceType() {DynamicDataSource.master();log.info("dataSource切换到:master");}}

这样的配置是根据方法名来的,可以根据自己的情况配置

也可以使用注解来主动切换,创建两个注解类,一个Master,一个Slave
Master.class

/*** 使用主库的注解*/
public @interface Master {}

Slave.class

/*** 使用读库的注解*/
public @interface Slave {}

AOP切入修改

/*** 数据源的切入面**/
@Aspect
@Component
@Slf4j
public class DataSourceAOP {@Before("(@annotation(wang.raye.separate.annotation.Master) || execution(* wang.raye.separate.service..*.insert*(..)) || " +"execution(* wang.raye.separate.service..*.update*(..)) || execution(* wang.raye.separate.service..*.delete*(..)) || " +"execution(* wang.raye.separate.service..*.add*(..))) && !@annotation(wang.raye.separate.annotation.Slave) -")public void setWriteDataSourceType() {DynamicDataSource.master();log.info("dataSource切换到:master");}@Before("(@annotation(wang.raye.separate.annotation.Slave) || execution(* wang.raye.separate.service..*.select*(..)) || execution(* wang.raye.separate.service..*.get*(..))) && !@annotation(wang.raye.separate.annotation.Master)")public void setReadDataSourceType() {DynamicDataSource.slave();log.info("dataSource切换到:slave");}}

注:这个AOP切入规则只是包含基本的规格,如果要正常使用,需要扩展规则
简单的service层代码

/*** 用户相关业务接口实现类*/
@Service
@Slf4j
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper mapper;@Master@Overridepublic List<User> selectAll() {return mapper.selectAll();}@Overridepublic boolean addUser(User user) {return mapper.insertSelective(user) > 0;}@Overridepublic boolean updateUser(User user) {return mapper.updateByPrimaryKey(user) > 0;}@Overridepublic boolean deleteByid(int id) {return mapper.deleteByPrimaryKey(id) > 0;}@Transactional(rollbackFor = Exception.class )@Overridepublic boolean insertAndUpdate(User user){log.info("当前key:"+ DynamicDataSource.getType().name());int count = 0;count += mapper.insertSelective(user);user = null;user.getId();count += mapper.updateByPrimaryKey(user);return count > 1;}
}

这里所有方法会使用master源,如果去掉selectAll的Master注解,那么selectAll就会使用slave数据源,insertAndUpdate方法主要是测试使用事物的情况下是否是向Master数据源写入以及是否正常回滚

源码

具体代码可以直接看我的demo项目读写分离demo

Springboot Mybatis MySQL读写分离及事物配置相关推荐

  1. SpringBoot + MyBatis + MySQL 读写分离实战

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:http://t.cn/AiKuJEB9 1. 引言 读写分 ...

  2. MySQL的主从配置+SpringBoot的MySQL读写分离配置

    MySQL的主从复制 点击前往查看MySQL的安装 1.主库操作 vim /etc/my.cnf 添加如下配置 log-bin=mysql-bin #[必须]启用二进制日志 server-id=128 ...

  3. mysql读写分离,主从配置

    2019独角兽企业重金招聘Python工程师标准>>> 一个完整的mysql读写分离环境包括以下几个部分: 应用程序client database proxy database集群 ...

  4. mysql读写分离的完整配置

    参考文章: 文章一[仅供参考]: 构建高性能web之路------mysql读写分离实战[按照里面配置主从mysql同步失败,并且按照他的my.cnf配置,给我的虚拟机搞坏了,重新弄了一个] http ...

  5. springboot+mybatis+mybatis +mysql读写分离(AOP方式)

    引言 读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,无非两个,要么中间件帮我们做,要么程序自己做.因此,一般来讲,读写分离有两种实现方式. 第一种是依靠中间 ...

  6. Spring Boot + MyBatis + MySQL读写分离

    今日推荐 借助Redis锁,完美解决高并发秒杀问题还在直接用JWT做鉴权?JJWT真香Spring Boot 操作 Redis 的各种实现Fluent Mybatis 牛逼!Nginx 常用配置清单这 ...

  7. boot lib分离 spring_SpringBoot+MyBatis+MySQL读写分离(实例)A

    1. 引言 读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,无非两个,要么中间件帮我们做,要么程序自己做.因此,一般来讲,读写分离有两种实现方式.第一种是依靠 ...

  8. mysql读写分离实现_laravel 配置MySQL读写分离

    前言:说到应对大流量.高并发的解决方案的时候,总会有这样的回答,如:读写分离,主从复制...等,数据库层今天先不讨论,那么今天我们就来看看怎么在应用层实现读写分离. 框架:laravel5.7 说明: ...

  9. mysql读写分离实例_SpringBoot+MyBatis+MySQL读写分离(实例)

    https://mp.weixin.qq.com/s/1vTbllmZkHRnuk7_xR2DIg 1. 引言 读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿 ...

最新文章

  1. mysql druid 多数据源_SpringBoot使用阿里数据库连接池Druid以及多数据源配置
  2. 成功解决tensorflow\contrib\learn\python\learn\datasets\base._internal_retry.locals.wrap.locals.wrapp
  3. 成功解决pywintypes.error: (2, 'LoadLibraryEx', '系统找不到指定的文件。')
  4. [WorldWind学习]5.相机对象
  5. Linux debian安装Notepadqq,Linux系统下的Notepad++编辑器
  6. Nginx handler模块
  7. Spring @Autowired Annotation
  8. 计蒜客---函数规律
  9. Javascript函数调用的四种方法
  10. CCF认证-2014-12-2 Z字形扫描
  11. memcache连接是否有用户名和密码的设置
  12. Python: 生成器,yield
  13. oracle10g在win10上的安装
  14. 714 买卖股票的最佳时机含手续费(状态机dp)
  15. 一文带你认识HTML
  16. HNU 11722 The Gougu Theorem
  17. 讲座 | lidar目标检测------图森未来CTO王乃岩
  18. 徐家骏:华为十年感悟(转载)
  19. Effective C++条款39:明智而审慎地使用private继承(Use private inheritance judiciously)
  20. Ray----Tune(2):Tune的用户指南

热门文章

  1. 做报表到10点才下班,做的还是丑,怎样才能做出一张好看的报表?
  2. 解决虚拟机-虚拟网络配置没有桥接模式,本地没有虚拟网卡
  3. 全球及中国芯片产业研发方向与投资规模预测报告2022版
  4. openlayers3 ol3热力图 json
  5. 配置Hadoop格式化namenode时报错cannot create directory /usr/local/hadoop/tmp/dfs/name/current
  6. python 高级部分
  7. 止血、回血 苏宁易购正在复苏路上
  8. java md5 源码_MD5加密 Java源代码
  9. 休问情怀谁得似——冰雪小五台苦旅记(十完结篇)
  10. 人工智能阿发狗技术都包含哪些内容