1.问题
首先,为什么会碰到这样的问题?
昨天写的一个业务上线了,但是在dev环境和test环境都能跑,但是到了线上环境发生数据不能插入的问题。
问了老大之后发现线上数据库是读写分离的,然后通过过滤器的才能进入写数据库卡,我的函数命名规范问题不符合过滤器的要求,导致从controller不能进入逻辑函数。
主要原因:
- 线上数据库是主从分离(即读写分离,写数据的情况下连接主库,读数据的时候连接从库)
- 而为什么跟函数命名规范相关?
- 代码中实现数据库读写分离是通过AOP的拦截器在方法开始执行前后插入我们想要的代码来实现动态切换数据源的功能
- 为什么要用AOP来实现数据源的切换?为什么要读写分离?AOP是如何实现读写分离的?
2.什么是AOP
2.1 AOP定义
AOP目标是把业务的共性问题提取出来集中放到一个统一的地方管理和控制。
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的知识点。本人可能总结的不对,有问题请评论提醒。

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:主要指增加服务器的数量;
1、冷备份(定时全量/增量备份)
2、热备份(在主从架构上实现)
3、主从架构(N主M从)
4、读写分离(在主从架构上实现)
5、数据分片(分库分表(垂直分库 水平分表))
参考:www.jianshu.com/p/b7834c990…
3.3 读写分离的缺点
- 成本增加
- 数据延迟
- 写数据库压力较大
3.4 主从复制方式
- 基于日志 binlog
- 基于GTID 全局事务标识符
1、Master将数据改变记录到二进制日志(binary log)中,也就是配置文件log-bin指定的文件,这些记录叫做二进制日志事件(binary log events)
2、Slave通过I/O线程读取Master中的binary log events并写入到它的中继日志(relay log)
3、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管理
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。
5.总结
- 其实读写分离和AOP以前为了面试看过,没有实际应用过或者没有一个应用场景,怎么看也只能理解表面。
对于AOP来说,最重要的点在于:
- 理解应用场景:比如这个项目中是用了AOP解决了读写数据库分离;
- 考虑在什么期间插入代码,选用合适的AOP方法:本项目选择.java到.class之间的编译期间,静态织入的方法。使用的插入方法是AspectJxml的方式配置 aop:config
- 考虑怎么过滤方法,找到注入点的描述,比如公司的项目(是否有update insert 等关键词判断是否为写操作)通过方法名来过滤
- 考虑以怎样的方式处理代码,是在代码执行之前?执行之后?(5种增强类型):Before advice