实施MySQL ReplicationDriver支持读写分离

本文深入探讨了MySQL读写分离的实现方法,通过使用ReplicationDriver类和注解配置,详细讲解了如何在服务层实现自动读写分离,包括事务管理、主从切换策略及常见应用场景。
import java.sql.Connection;
import java.sql.ResultSet;
import java.util.Properties;
 
import com.mysql.jdbc.ReplicationDriver;
 
public class ReplicationDriverDemo {
 
  public static void main(String[] args) throws Exception {
    ReplicationDriver driver = new ReplicationDriver();
 
    Properties props = new Properties();
 
    // We want this for failover on the slaves
    props.put("autoReconnect", "true");
 
    // We want to load balance between the slaves
    props.put("roundRobinLoadBalance", "true");
 
    props.put("user", "foo");
    props.put("password", "bar");
 
    //
    // Looks like a normal MySQL JDBC url, with a
    // comma-separated list of hosts, the first
    // being the 'master', the rest being any number
    // of slaves that the driver will load balance against
    //
 
    Connection conn =
        driver.connect("jdbc:mysql:replication://master,slave1,slave2,slave3/test",
            props);
 
    //
    // Perform read/write work on the master
    // by setting the read-only flag to "false"
    //
 
    conn.setReadOnly(false);
    conn.setAutoCommit(false);
    conn.createStatement().executeUpdate("UPDATE some_table ....");
    conn.commit();
 
    //
    // Now, do a query from a slave, the driver automatically picks one
    // from the list
    //
 
    conn.setReadOnly(true);
 
    ResultSet rs =
      conn.createStatement().executeQuery("SELECT a,b FROM alt_table");
 
     .......
  }
}

 

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;

 

 

 

转载于:https://my.oschina.net/xiaominmin/blog/2999248

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值