实施MySQL ReplicationDriver支持读写分离
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; |
转自:http://leitelyaya.iteye.com/blog/2335195
实施MySQL ReplicationDriver支持读写分离的更多相关文章
- Linux下MySQL主从复制(GTID)+读写分离(ProxySQL)-实施笔记
GTID概念: GTID( Global Transaction Identifier)全局事务标识.GTID 是 5.6 版本引入的一个有关于主从复制的重大改进,相对于之前版本基于 Binlog 文 ...
- Mysql主从复制,读写分离
一个简单完整的 Mysql 主从复制,读写分离的示意图. 1. 首先搭建 Mysql 主从架构,实现 将 mater 数据自动复制到 slave MySQL 复制的工作方式很简单,一台服务器作为主机, ...
- 利用MySQL Router构建读写分离MGR集群
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 目录 1. 部署MySQL Router 2. 启动mysqlrouter服务 3. 确认读写分离效果 4. 确认只读负载 ...
- Amoeba+Mysql实现数据库读写分离
一.Amoeba 是什么 Amoeba(变形虫)项目,专注 分布式数据库 proxy 开发.座落与Client.DB Server(s)之间.对客户端透明.具有负载均衡.高可用性.sql过滤.读写分离 ...
- Mysql 主从复制,读写分离设置
一个简单完整的 Mysql 主从复制,读写分离的示意图. 1. 首先搭建 Mysql 主从架构,实现 将 mater 数据自动复制到 slave MySQL 复制的工作方式很简单,一台服务器作为主机, ...
- mysql主从复制以及读写分离
之前我们已经对LNMP平台的Nginx做过了负载均衡以及高可用的部署,今天我们就通过一些技术来提升数据的高可用以及数据库性能的提升. 一.mysql主从复制 首先我们先来看一下主从复制能够解决什么问题 ...
- MySQL 主从复制与读写分离 (超详细图文并茂小白闭着眼睛都会做)
MySQL 主从复制与读写分离 1.什么是读写分离 2.为什么要读写分离 3.什么时候要读写分离 4.主从复制与读写分离 5.mysql支持的复制类型 6.主从复制的工作过程 7.MySQL主从复制延 ...
- 30.Mysql主从复制、读写分离
Mysql主从复制.读写分离 目录 Mysql主从复制.读写分离 读写分离 读写分离概述 为什么要读写分离 什么时候要读写分离 主从复制与读写分离 mysql支持的复制类型 主从复制的工作过程 初始环 ...
- Mysql 实现数据库读写分离
Amoeba+Mysql实现数据库读写分离 一.Amoeba 是什么 Amoeba(变形虫)项目,专注 分布式数据库 proxy 开发.座落与Client.DB Server(s)之间.对客户端透明. ...
随机推荐
- 利用XmlDocument操作XML文件
利用XmlDocument可以方便的操作XML文件. .操作XML文件基本方法 ()添加对System.Xml的引用,并使用using语句添加引用: ()假设要读取的XML文件如下: <?xml ...
- ping 127.0.0.1请求超时的解决办法?
转自:http://blog.51cto.com/dengyong/1429699 打开网络连接,你很有可能启用了虚拟wifi.若有无线网卡就把无线网卡关掉,然后本地连接那里(就是有线网卡的那个连接) ...
- JS中,根据div数值判断弹出窗口
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- MySQL在cmd和python下的常用操作
环境配置1:安装mysql,环境变量添加mysql的bin目录 环境配置2:python安装MySQL-Python 请根据自身操作系统下载安装,否则会报c ++ compile 9.0,import ...
- 获取百度搜索结果的真实url以及摘要和时间
利用requests库和bs4实现,demo如下: #coding:utf- import requests from bs4 import BeautifulSoup import bs4 impo ...
- 【总结整理】AMAP学习AMAP.PlaceSearch()
http://lbs.amap.com/api/javascript-api/reference/search#m_AMap.PlaceSearch http://lbs.amap.com/api/j ...
- ueditor 1.2.6使用方法
本文以php版本为例: 文件下载:http://ueditor.baidu.com/website/download.html 还可以自己先定义内容,然后下载,这样可以帮助我们精简不少东西. 以本地p ...
- SpringBoot05 数据操作01 -> JPA的基本使用、基本使用02
前提: 创建一个springboot项目 创建一个名为springboottest的MySQL数据库 1 jar包准备 jpa的jar包 mysql驱动的jar包 druid数据库连接池的jar包 l ...
- IDEA java 代码格式化统一
Intellij idea 安装格式化插件 ECLIPSE CODE FORMATTER:1,安装插件:网络安装:选择Setting =>Plugins=>Browse repositor ...
- K-D TREE算法原理及实现
博客转载自:https://leileiluoluo.com/posts/kdtree-algorithm-and-implementation.html k-d tree即k-dimensional ...