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. HDLM命令dlnkmgr详解之二__help/clear

    1.help操作 主要显示命令的帮助信息. 显示所有操作的帮助信息 -bash-3.2# dlnkmgr help dlnkmgr: Format dlnkmgr { clear | help | o ...

  2. opencv相关

    http://opencv.org/ ================== 不错的博客: 图像处理(小魏的修行路):http://blog.csdn.net/xiaowei_cqu/article/c ...

  3. DAY10-MYSQL表操作

    一 存储引擎介绍 存储引擎即表类型,mysql根据不同的表类型会有不同的处理机制 http://www.cnblogs.com/guoyunlong666/p/8491702.html 二 表介绍 表 ...

  4. MySQL中的多表插入更新与MS-SQL的对比

    MySQL多表插入: INSERT INTO tdb_goods_cates (cate_name) SELECT goods_cate FROM tdb_goods GROUP BY goods_c ...

  5. FTP 命令 上传下载

    ftp   ftp [-v] [-n] [-i] [-d] [-g] [-s:filename] [-a] [-w:windowsize] [computer] 参数-v 禁止显示远程服务器响应.-n ...

  6. javascript变量声明提升(hoisting)

    javascript的变量声明具有hoisting机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的最前面. 先看一段代码 1 2 3 4 5 var v = &quo ...

  7. Codeforces 58E Expression (搜索)

    题意:给你一个可能不正确的算式a + b = c, 你可以在a,b,c中随意添加数字.输出一个添加数字最少的新等式x + y  = z; 题目链接 思路:来源于这片博客:https://www.cnb ...

  8. Ros学习——创建程序包

      1.程序包 一个程序包要想称为catkin程序包必须符合以下要求: 该程序包必须包含catkin compliant package.xml文件 这个package.xml文件提供有关程序包的元信 ...

  9. 前端学习笔记2017.6.21-引入JS文件的方法

    通过网络引入JS文件 <script src="https://www.xxx.com/aaa.js"></script> 如果是引入本地JS文件 < ...

  10. 关于IE6下绝对定位元素莫名消失的问题

    一般来说,让绝对定位的元素不挨着浮动元素就OK了: 1.当绝对定位层的邻近浮动层的宽度不等于父层宽度时,以及没有清除浮动时,IE6/7,FF中显示一致: 2.当绝对定位层的邻近浮动层的宽度不等于父层宽 ...