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. spring整合mybatis的事物管理配置

    一.基本配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http:/ ...

  2. python学习笔记(1)python下载及运行

    进入https://www.python.org/官网下载python,根据需要选择2.*或3.*版本 安装完将安装目录添加到环境变量path中 运行cmd,输入python出现版本号即配置成功 下载 ...

  3. JavaScript Array.map

    Array.prototype.map() History Edit This article is in need of a technical review. Table of Contents ...

  4. cfree使用cygwin编译程序出现计算机丢失cygwin1.dll解决办法

    这种情况多是环境没配好,我的是64位cygwin C:\cygwin64\bin 加入到环境变量中,重打开cfree就可以解决.

  5. AngularJS分层开发

    为了AngularJS的代码利于维护和复用,利用MVC的模式将代码分离,提高程序的灵活性及可维护性. 1,前端基础层 var app=angular.module('appName',['pagina ...

  6. 第2章 构建springboot工程 2-1 构建SpringBoot第一个demo

    以后的趋势肯定是以一个微服务为主导的, Spring-Boot的指导 Maven整个环境构建之前的整个项目其实是一个很普通的J2SE项目,它构建完之后会进行重构,重构为Maven的一个项目路径.可以看 ...

  7. newcoder中的基础题

    1. mysql_num_fields()  函数返回结果集中字段的数 2. <?php class A{ ; } $a = new A(); $b = $a; $a; echo $b-> ...

  8. go语言-工作区和gopath

    工作区是放置Go源码文件的目录;一般情况下,Go源码文件都需要存放到工作区中;但是对于命令源码文件来说,这不是必须的. 每一个工作区的结构都类似下图所示:/home/hypermind/golib: ...

  9. gearman client的doBackground 与doNormal方法的区别

    doNormal方法是阻塞的,需要等到worker处理完之后才返回,否则一直阻塞住; doBackground 方法是非阻塞的,只要将数据发送到gearmand之后,就立马返回,不等待worker的处 ...

  10. Gstreamer编程

    一.简介 GStreamer是一个开源的多媒体框架库.利用它,可以构建一系列的媒体处理模块,包括从简单的ogg播放功能到复杂的音频(混音)和视频(非线性编辑)的处理.应用程序可以透明的利用解码和过滤技 ...