• 实施MySQL ReplicationDriver支持读写分离


    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

  • 相关阅读:
    概率期望小记
    洛谷P5591 小猪佩奇学数学【单位根反演】
    EasyUI取消树节点选中
    EasyUI获取正在编辑状态行的索引
    js判断是否是大小写,数字等方法
    ElasticSearch部署问题
    全文检索ES 服务启动和关闭
    文件异步上传
    js控制时间显示格式
    SpringMVC 多视图解析器 跳转问题
  • 原文地址:https://www.cnblogs.com/flywang/p/7777792.html
Copyright © 2020-2023  润新知