AOP实现mysql的主从数据库:读写分离

1.问题

首先,为何会碰到这样的问题?java

昨天写的一个业务上线了,可是在dev环境和test环境都能跑,可是到了线上环境发生数据不能插入的问题。
问了老大以后发现线上数据库是读写分离的,而后经过过滤器的才能进入写数据库卡,个人函数命名规范问题不符合过滤器的要求,致使从controller不能进入逻辑函数。面试

主要缘由spring

  • 线上数据库是主从分离(即读写分离,写数据的状况下链接主库,读数据的时候链接从库)
  • 而为何跟函数命名规范相关?
  • 代码中实现数据库读写分离是经过AOP的拦截器在方法开始执行先后插入咱们想要的代码来实现动态切换数据源的功能
  • 为何要用AOP来实现数据源的切换?为何要读写分离?AOP是如何实现读写分离的?

2.什么是AOP

2.1 AOP定义

AOP目标是把业务的共性问题提取出来集中放到一个统一的地方管理和控制。sql

2.2 应用场景

  • 参数验证或者判空等公共方法
  • 异常处理
  • 事务管理
  • 缓存
  • 热修复:好比代码上线以后,有小改动,能够发起一个热修。即把有bug的方法替换成咱们修复以后的方法

2.3 AOP织入方法

不一样方法在java的完整周期(类加载期间,编译期,运行期间等)中不一样的时间段插入切面的方式不一样(即咱们想要他在这个时间完成的方法,这里建议先了解AOP的5种加强类型)。数据库

  • 动态织入Hook方式:比静态织入方式灵活,在运行期间,目标类加载以后,为接口动态生成代理类,将切面植入到代理类中。 常见的有:Dexposed,Xposed,epic(在native层修改java method对应的native指针)
  • 动态字节码生成:cglib+DexMaker;
Cglib 是一个强大的,高性能的 Code 生成类库。
原理是在运行期间目标字节码加载后,经过字节码技术为一个类建立子类,
并在子类中采用方法拦截的技术拦截全部父类方法的调用,顺势织入横切逻辑。
因为是经过子类来代理父类,所以不能代理被 final 字段修饰的方法。
复制代码
  • 静态织入方式 :编译期间 APT AspectJ Javassist

在java编译期间有不一样的织入方法 编程

2.3 AspectJ

自动代理 基于注解的方式 或 xml方式 本项目是使用Spring+AspectJ:基于xml:aop:config的方式实现AOP织入。缓存

2.4 Spring aop

基于代理(jdk动态代理、cglib动态代理)实现的aop Spring aop使用了两种代理机制。一种是jdk动态代理,另外一种是cglib动态代理。 Jdk动态代理只支持接口代理,cglib支持类的代理。
下面这张退很好的总结了AOP的知识点。本人可能总结的不对,有问题请评论提醒。 安全

参考: juejin.im/post/5c0153…

3.读写分离

3.1 为何要master-slave

1.将读操做和写操做分离到不一样的数据库上,避免主服务器出现性能瓶颈;
2.主服务器进行写操做时,不影响查询应用服务器的查询性能,下降阻塞,提升并发;
3.数据拥有多个容灾副本,提升数据安全性。
4.同时当主服务器故障时,可当即切换到其余服务器,提升系统可用性;
复制代码

3.2读写分离能解决什么问题

  • 1.高可用:master宕机,当即切换到slave机器上;
  • 2.负载均衡:读写分离也算是负载均衡的一种,主要指多台从数据库(slave)与主数据库(master)之间的数据均衡;
  • 3.数据备份:通常会写定时任务备份到不一样的slave,保证数据安全;
  • 4.业务模块化:一个业务模块读取一个slave,这个能够针对不一样的业务模块对不一样的数据库进行处理(好比索引的建立以及存储引擎的选择);
  • 5.扩展性:scale-up:主要指服务器性能扩展;scale-out:主要指增长服务器的数量;
一、冷备份(定时全量/增量备份)
二、热备份(在主从架构上实现)
三、主从架构(N主M从)
四、读写分离(在主从架构上实现)
五、数据分片(分库分表(垂直分库 水平分表))
复制代码

参考:www.jianshu.com/p/b7834c990…springboot

3.3 读写分离的缺点

  • 成本增长
  • 数据延迟
  • 写数据库压力较大

3.4 主从复制方式

  • 基于日志 binlog
  • 基于GTID 全局事务标识符
一、Master将数据改变记录到二进制日志(binary log)中,也就是配置文件log-bin指定的文件,这些记录叫作二进制日志事件(binary log events) 
二、Slave经过I/O线程读取Master中的binary log events并写入到它的中继日志(relay log) 
三、Slave重作中继日志中的事件,把中继日志中的事件信息一条一条的在本地执行一次,完成数据在本地的存储,从而实现将改变反映到它本身的数据(数据重放)
复制代码

4.项目中AOP是如何实现主从读写分离的?

1.数据源配置spring_mybatis.xml

data数据源列表:master 写库 slave 读库 
数据源DynamicDataSource类须要经过继承AbstractRoutingDataSource,class位于com.A.B.DynamicDataSource
  <bean id="dynamicDataSource" class="com.A.B.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="master" value-ref="masterDataSource" />
                <entry key="slave" value-ref="slaveDataSource" />
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="masterDataSource" />
    </bean>


配置dataAOP 动态设置数据源,class位于com.T.A.dsadvice.DataSourceAdvice
    <bean id="dataSourceAdvice" class="com.T.A.dsadvice.DataSourceAdvice" />
配置事务
<!-- spring aop manager transaction -->
    <tx:advice id="txTransactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- add transaction -->
            <tx:method name="add*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="create*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="insert*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="change*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="modify*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="edit*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="remove*" propagation="REQUIRED" rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>
经过配置aop:config实现AOP:AspectJ
    <aop:config>
        <aop:advisor advice-ref="dataSourceAdvice" pointcut="execution(* com.T.A.service..*Service.*(..))" order="1"/>
        <aop:advisor advice-ref="txTransactionAdvice" pointcut="execution(* com.T.A.service..*Service.*(..)))" order="2" />
    </aop:config>
复制代码

2.数据源配置DynamicDataSource

//determineCurrentLookupKey是重写的AbstractRoutingDataSource的方法
//主要是肯定当前应该使用哪一个数据源的key,由于AbstractRoutingDataSource 中保存的多个数据源是经过Map的方式保存的
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceSwitcher.getDataSource();
    }
}
复制代码

3.动态数据源配置

public class DataSourceAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice
复制代码

4.实例化数据源

一共三个数据源:master slave 动态数据源 保存在master和slave,为了防止spring注入异常,因此master和slave都是主动实例化的,并非交给spring管理bash

5.mybatis配置及事务配置

MybatisConfiguration 主要是配置的sqlSessionFactory和sqlSessionTemplate,以及Mybatis的扩展框架Mapper的配置,若是不须要Mapper,能够不用配置scannerConfigurer

6.AOP过滤

@Before是在方法执行前执行
@After在方法执行后执行
@Around环绕执行,能够再方法执行先后操做
@Aspect放在类名上面,把当前类标识为一个切面供容器读取
@Pointcut切入点,此注解放在方法上面,指向须要使用的切面编程的方法。此注解下面的方法并不会执行
复制代码

总结本项目中使用AOP实现主从分离的方式:

  • 使用@Before加强模式,在方法执行执行插入切面:Spring+AspectJ:aop:config
  • before方法中经过动态获取数据源的配置即写数据库的dbname
  • 设定过滤方法(设置方法名条件),若是知足写操做(函数名知足过滤方法),则把DataSource设置为master(调用这个函数后的dao层对主库进行处理),不然设置为slave。

参考资料:raye.wang/springboot-…

5.总结

  • 其实读写分离和AOP之前为了面试看过,没有实际应用过或者没有一个应用场景,怎么看也只能理解表面。

对于AOP来讲,最重要的点在于:

  • 理解应用场景:好比这个项目中是用了AOP解决了读写数据库分离;
  • 考虑在什么期间插入代码,选用合适的AOP方法:本项目选择.java到.class之间的编译期间,静态织入的方法。使用的插入方法是AspectJxml的方式配置 aop:config
  • 考虑怎么过滤方法,找到注入点的描述,好比公司的项目(是否有update insert 等关键词判断是否为写操做)经过方法名来过滤
  • 考虑以怎样的方式处理代码,是在代码执行以前?执行以后?(5种加强类型):Before advice
相关文章
相关标签/搜索