MySQL 提供支持读写分离的驱动类:
com.mysql.jdbc.ReplicationDriver
替代
com.mysql.jdbc.Driver
注意,所有参数主从统一:
jdbc:mysql:replication://<master>,<slave>.../...?...=...
当然,用户名和密码也必须相同
触发Slave的情况
-
设置 auto_commit = false
-
设置 readOnly 为 true
综上特点,读写分离依赖于事务
常用使用场景:
第一种, 事务管理使用【注解】支持
通常,事务管理在Service层,只需要简单的操作即可支持读写分离:
1
2
|
@Transactional (propagation=Propagation.REQUIRED, readOnly = true )
public List<OrderBase> findOrderList(String orderCode);
|
事务开启后,查询自动切换到从库。
注意:@Transactional 默认的readOnly参数是false,更新操作不需要特别的改动。propagation是指的事务传播方式,默认设置是Require,指的是“本次操作需要事务支持,如果没有事务开启一个事务,如果有事务,加入到该事务中”
考虑复杂一点的情况,当Service中出现自我方法的调用时:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Transactional (propagation=Propagation.REQUIRED, readOnly = true )
public OrderBase getOrder(String orderCode) {
findSubOrderList(orderCode);
} @Transactional (propagation=Propagation.REQUIRED, readOnly = true )
public List<OrderSub> findSubOrderList(String orderCode) {
} @Transactional (propagation=Propagation.REQUIRED, readOnly = false )
public void updateOrder(OrderBase orderBase) {
findSubOrderList(orderBase.getCode());
...
} |
当外部调用getOrder时,getOrder方法的@Transaction注解生效,设置从库查询。
当外部调用updateOrder时,updateOrder方法的@Transaction注解生效,设置操作主库。
注意,这两个方法都调用了findSubOrderList方法,而调用的对象是this,不是被spring事务管理器替换过的service对象,所以findSubOrderList方法上的@Transaction注解无效,会根据上文环境来查主库和从库
这种特性对于业务来说是恰当好处的,生效的事务是在最外层的方法上,可以避免在一个事务内部出现读写库不统一的情况。
更复杂一点的情况,当service中调用了其它类的service:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// OrderSerivceImpl: @Transactional (propagation=Propagation.REQUIRED, readOnly = true )
public OrderBase getOrder(String orderCode) {
orderCouponService.getById(couponId);
} @Transactional (propagation=Propagation.REQUIRED, readOnly = false )
public OrderBase createOrder(OrderGeneratorDto dto) {
orderCouponService.saveCoupon(coupon);
} @Transactional (propagation=Propagation.REQUIRED, readOnly = false )
public OrderBase updateOrder(OrderBase orderBase) {
orderCouponService.getById(couponId);
} // OrderCouponServiceImpl: @Transactional (propagation=Propagation.REQUIRED, readOnly = true )
public OrderCoupon getById(Integer couponId) {
} @Transactional (propagation=Propagation.REQUIRED, readOnly = false )
public OrderCoupon saveCoupon(OrderCoupon coupon) {
} |
1, 当外部调用OrderSerivce的getOrder时,getOrder方法的@Transaction注解生效,设置从库查询。
getOrder内部调用了OrderCouponService的getById方法,由于orderCouponService是spring提供的对象,经过了事务管理,所以getById方法上的@Transaction注解生效,
我们知道Require这个事务传播的特性,getById不会创建新的事务,所以依旧是由从库读取数据。
2, 当外部调用OrderSerivce的saveOrder时,saveOrder方法的@Transaction注解生效,设置操作主库。
saveOrder内部调用了OrderCouponService的saveCoupon方法,同样由于Require的特性,没有创建新事务,操作主库。
3, 当外部调用OrderSerivce的updateOrder时,updateOrder方法的@Transaction注解生效,设置操作主库。
updateOrder内部调用了OrderCouponService的getById方法,同样由于Require的特性,没有创建新事务,从主库读出数据。
这些特性也是很好的,我们只需要关心最外部调用的方法的注解内容,就可以确定走的哪个库。
更复杂点的情况是新开事务的情况,建议谨慎对待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// OrderSerivceImpl: @Transactional (propagation=Propagation.REQUIRED, readOnly = true )
public Price getOrderPrice(String orderCode) {
// 不恰当的业务逻辑!此处只是演示
otherService.updateById(id, xxx);
foo = otherService.getById(id);
} // OtherServiceImpl: @Transactional (propagation=Propagation.REQUIRED, readOnly = true )
public ... getById(Integer id) {
} @Transactional (propagation=Propagation.REQUIRED_NEW, readOnly = false )
public void updateById(id, ...) {
} |
该想法是构想把OrderSerivce的getOrderPrice查询走从库,其中一个小逻辑更新库设置操作主库。在没有设置主从的情况下,这种方式是支持的,并不会出现问题。
但在设置了主从的情况下,这种业务逻辑操作就“不安全”了,因为,updateById走的是主库,它的更新操作是依赖于主从同步的,很有可能getById取到了“过期”的数据。
这种情况在业务上来说是应该要避免的,如果不能避免,最好的办法是让外部都走主库,保证数据来源的一致性。
综上,事务管理配置用注解的方式还是蛮方便的。
第二种, 事务管理使用【XML配置】支持
XML配置的事务是以判断指定名称开头的方法来实现的,跟注解配置事务是类似的。可以把select和get判定为readOnly,传播机制设定为Require。
第三种,使用支持读事务的入口类
鉴于现有代码Service层被融合到web和admin中,在Service层的注入会影响多个系统,而单独写方法,不免繁琐,使用代理方法支持事务的读比较灵活。
流程:
这个模式好处在于可以根据业务的需要,合理安排开发和测试的工作,影响范围可控。
实现代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
public class ReadOnlyTransFactoryBean<T> implements MethodInterceptor, FactoryBean<T>, ApplicationContextAware {
private Logger logger = Logger.getLogger(ReadOnlyTransFactoryBean. class );
/**
* 代理的Service类
*/
Class<T> serviceInterface;
/**
* 代理的Service名
*/
String delegateBeanName;
ApplicationContext applicationContext;
public Class<T> getServiceInterface() {
return serviceInterface;
}
public void setServiceInterface(Class<T> serviceInterface) {
this .serviceInterface = serviceInterface;
}
public String getDelegateBeanName() {
return delegateBeanName;
}
public void setDelegateBeanName(String delegateBeanName) {
this .delegateBeanName = delegateBeanName;
}
Enhancer enhancer = new Enhancer();
@Override
public T getObject() throws Exception {
// 使用CGlib增强,提供代理功能
enhancer.setSuperclass(serviceInterface);
enhancer.setCallback( this );
return (T) enhancer.create();
}
@Override
public Class<?> getObjectType() {
return this .serviceInterface;
}
@Override
public boolean isSingleton() {
return true ;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this .applicationContext = applicationContext;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
T service = applicationContext.getBean(delegateBeanName, serviceInterface);
DataSourceTransactionManager txManager = applicationContext.getBean(DataSourceTransactionManager. class );
DefaultTransactionDefinition definition = new DefaultTransactionDefinition(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
definition.setReadOnly( true );
logger.info( "Start ReadOnly Transactional!" );
TransactionStatus transaction = txManager.getTransaction(definition);
Object result = method.invoke(service, args);
if (!transaction.isCompleted()) {
txManager.commit(transaction);
}
logger.info( "End ReadOnly Transactional!" );
return result;
}
} |
按需配置:
1
2
3
4
5
|
<!-- ReadOnly Transaction Service --> < bean id = "readOnlyOrderPlatformService" class = "com.qding.order.service.util.ReadOnlyTransFactoryBean" >
< property name = "serviceInterface" value = "com.qding.order.service.IOrderService" />
< property name = "delegateBeanName" value = "orderPlatformService" />
</ bean >
|
使用时请注意:原有@Autowired方式注入请改成@Resource指定名称的方式,以区别不同入口
1
2
3
4
5
|
@Resource (name= "orderPlatformService" )
protected IOrderService orderService;
@Resource (name= "readOnlyOrderPlatformService" )
protected IOrderService readOnlyOrderService;
|
相关推荐
springboot结合mysql主从来实现读写分离 一、实现的功能 1、基于springboot框架,application.yml配置多个数据源,使用AOP以及AbstractRootingDataSource、ThreadLocal来实现多数据源切换,以实现读写分离。mysql...
文档中包括mysql的读写分离,与mycat的读写分离,因为mycat读写是基于mysql的,所以首先需要部署mysql读写
【27】基于MyCat的MySQL高可用读写分离集群实战课程下载【No215】基于MyCat的MySQL高可用读写分离集群实战课程下载 .txt
MYSQL+MYCAT 读写分离
读写分离就是对于一条SQL该选择哪一个数据库去执行,至于谁来做选择数据库这件事,有两个,要么使用中间件帮我们做,要么程序自己做。一般来说,读写分离有两种实现方式。第一种是依靠中间件MyCat,也就是说应用程序...
基于Mycat的Mysql主从复制读写分离配置详解与示例,DBA必看的
基于mycat的mysql高可用读写分离,适合mysql进阶。比较实用的技术。
使用mysql-proxy实现mysql读写分离
springboot+mybatis+mysql实现读写分离 先在建好mysql主从数据库的配置,然后在代码中根据读写分离或强制读取master数据库中的数据 mysql数据库设置主从,参考: ...
Mybatis读写分离,支持n多的从库,简单的负载均衡。数据库是mysql,采用druid连接池。 读写分离采用插件的形式实现的,优点是不需要写源注解,不需要写分开的Mapper.xml。 如果只有主库的话,那么会创建两个地址相同...
MYSQL读写分离最佳实战,面对越来越大的访问压力,单台的服务器的性能成为瓶颈,需要分担负载
在linux下用于部署读写分离的中间件maxscale。它是实现mysql数据库的读写分离的
MySQL主从同步与读写分离配置图文详解
Mycat+MySQL主从复制读写分离验证安装手册
基于SpringBoot,来实现MySQL读写分离技术.zip基于SpringBoot,来实现MySQL读写分离技术.zip基于SpringBoot,来实现MySQL读写分离技术.zip基于SpringBoot,来实现MySQL读写分离技术.zip基于SpringBoot,来实现MySQL...
MySQL主从复制与读写分离MySQL主从复制与读写分离
.基于Mycat的MySQL主从复制读写分离docker实现.
1. [root@bogon ~]# yum -y install gcc* gcc-c++* autoconf* automake* zlib* libxml
基于MyCat的MySQL高可用读写分离集群
mysql 读写分离软件