MySQL 提供支持读写分离的驱动类:

com.mysql.jdbc.ReplicationDriver

替代

com.mysql.jdbc.Driver

注意,所有参数主从统一:

jdbc:mysql:replication://<master>,<slave>.../...?...=... 

当然,用户名和密码也必须相同

触发Slave的情况

  1. 设置 auto_commit = false

  2. 设置 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支持读写分离的更多相关文章

  1. Linux下MySQL主从复制(GTID)+读写分离(ProxySQL)-实施笔记

    GTID概念: GTID( Global Transaction Identifier)全局事务标识.GTID 是 5.6 版本引入的一个有关于主从复制的重大改进,相对于之前版本基于 Binlog 文 ...

  2. Mysql主从复制,读写分离

    一个简单完整的 Mysql 主从复制,读写分离的示意图. 1. 首先搭建 Mysql 主从架构,实现 将 mater 数据自动复制到 slave MySQL 复制的工作方式很简单,一台服务器作为主机, ...

  3. 利用MySQL Router构建读写分离MGR集群

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 目录 1. 部署MySQL Router 2. 启动mysqlrouter服务 3. 确认读写分离效果 4. 确认只读负载 ...

  4. Amoeba+Mysql实现数据库读写分离

    一.Amoeba 是什么 Amoeba(变形虫)项目,专注 分布式数据库 proxy 开发.座落与Client.DB Server(s)之间.对客户端透明.具有负载均衡.高可用性.sql过滤.读写分离 ...

  5. Mysql 主从复制,读写分离设置

    一个简单完整的 Mysql 主从复制,读写分离的示意图. 1. 首先搭建 Mysql 主从架构,实现 将 mater 数据自动复制到 slave MySQL 复制的工作方式很简单,一台服务器作为主机, ...

  6. mysql主从复制以及读写分离

    之前我们已经对LNMP平台的Nginx做过了负载均衡以及高可用的部署,今天我们就通过一些技术来提升数据的高可用以及数据库性能的提升. 一.mysql主从复制 首先我们先来看一下主从复制能够解决什么问题 ...

  7. MySQL 主从复制与读写分离 (超详细图文并茂小白闭着眼睛都会做)

    MySQL 主从复制与读写分离 1.什么是读写分离 2.为什么要读写分离 3.什么时候要读写分离 4.主从复制与读写分离 5.mysql支持的复制类型 6.主从复制的工作过程 7.MySQL主从复制延 ...

  8. 30.Mysql主从复制、读写分离

    Mysql主从复制.读写分离 目录 Mysql主从复制.读写分离 读写分离 读写分离概述 为什么要读写分离 什么时候要读写分离 主从复制与读写分离 mysql支持的复制类型 主从复制的工作过程 初始环 ...

  9. Mysql 实现数据库读写分离

    Amoeba+Mysql实现数据库读写分离 一.Amoeba 是什么 Amoeba(变形虫)项目,专注 分布式数据库 proxy 开发.座落与Client.DB Server(s)之间.对客户端透明. ...

随机推荐

  1. Velocity加载模版文件

    一.类路径 加载classpath目录下的vm文件,或者maven项目的resources路径下 Properties p = new Properties(); p.put("file.r ...

  2. java之链表

    链表是一种物理存储单元上非连续.非顺序的存储结构. 链表是由那几个部分组成的呢? 是由N个节点组成的       每一个节点分为两部分:                         1.数据域  ...

  3. python爬虫实战(3)--图片下载器

    本篇目标 1.输入关键字能够根据关键字爬取百度图片 2.能够将图片保存到本地文件夹 1.URL的格式 进入百度图片搜索apple,这时显示的是瀑布流版本,我们选择传统翻页版本进行爬取.可以看到网址为: ...

  4. java如何从cmd运行并使用text文件作为输入源的总结

    大家好,好几天没写东西了,又和大家见面了 首先,编译我们的.java文件,生成.class文件 使用命令 javac yourname.java 编译 然后使用java youname < yo ...

  5. VS2012新建网站出现(1)的解决方案

    1.用记事本打开以下文件: D:\Users\lyn\Documents\IISExpress\config\applicationhost.config 2.删除sites结点下的所有site结点:

  6. p3634 [APIO2012]守卫

    传送门 分析 1.先预处理出不被0覆盖的点,然后对每个点处理出在它左边离他最近的点和在他右边理他最近的点. 2.对于每个至少存在一个忍者的区间,先将它左右边界处理为不被0所覆盖.排序后将包含其他区间的 ...

  7. loj10104 [POI 2008]Blockade

    传送门 分析 我们知道对于一个割点,我们如果去掉它就会使原来的图被分为若干块,则这是我们将所有块包含的点的个数两两相乘即可,而如果不是割点则对于图的连通性没有影响.注意在最后要加上2*(n-1)表示去 ...

  8. 第一篇:Django基础

    Django框架第一篇基础 一个小问题: 什么是根目录:就是没有路径,只有域名..url(r'^$') 补充一张关于wsgiref模块的图片 一.MTV模型 Django的MTV分别代表: Model ...

  9. 初识 Redis

    浏览目录 什么是redis redis的特点 redis的安装和基本使用 操作模式 连接池 操作 string操作 hash操作 list操作 什么是Redis? redis是一个key-value存 ...

  10. python部分运算符理解

    1.//取整除 5//3得到1 2.%取余 5%3得到2 3.<<左移 2<<2得到8 2用二进制表示为10,向左移两位得到1000,即十进制的8 4.>>右移 1 ...