关于读写分离强制走主库的实现逻辑优化

关于读写分离强制走主库的实现逻辑

背景:之前做过读写分离的,肯定都会遇到这么一种情况,写完立刻读,有可能导致因为数据还没有同步到从库,导致造成脏读问题。刚开始解决方案是对这些接口都指定强制读主库。但是因为系统大多数都是读多写少,接口总是强制读主库,从库就失去了它的意义,所以为此改进了强制读主库的逻辑,实现尽可能利用读写分离的优点。优化思路:例如当修改订单状态后,把对应单据号指定500毫秒过期时间存入redis中,下次查询该单据信息先看500毫秒是否被修改过,是就读主库否就读从库

下面是优化方案:

定义注解,实现统一切面:

package com.zhqc.cloud.wms.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 主库路由注解
 *
 * @author zdd
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MasterRouteOnly {


    String value() default "";


    /**
     * 前缀
     */
    String prefix() default "";


}

  1. 定义切面
package com.zhqc.cloud.wms.aop;

import cn.hutool.core.util.ReflectUtil;
import com.zhqc.cloud.wms.annotation.MasterRouteOnly;
import groovy.util.logging.Slf4j;
import io.shardingsphere.api.HintManager;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author zdd
 */
@Aspect
@Component
@Slf4j
public class MasterRouteAspect {

    @Resource
    private MasterRouteRedis masterRouteRedis;

    private static final Logger log = LoggerFactory.getLogger(MasterRouteAspect.class);

    @Around("@annotation(masterRouteOnly)")
    public Object setMasterRoute(ProceedingJoinPoint joinPoint, MasterRouteOnly masterRouteOnly) throws Throwable {
        try {
            //获取注解的value数据
            String value = masterRouteOnly.value();
            String prefix = masterRouteOnly.prefix();
            //判断value是否是EL表达式
            if (StringUtils.isNotBlank(value) && StringUtils.isNotBlank(prefix)) {
                if(isEl(value)) {
                    value = parseEl(value, joinPoint).toString();
                }
                Object obj = masterRouteRedis.get(prefix + value);
                if (obj != null) {
                    HintManager.getInstance().setMasterRouteOnly();
                }
            }else {
                HintManager.getInstance().setMasterRouteOnly();
            }
        } catch (Exception e) {
            log.error("主库路由注解解析错误: {}", ((MethodSignature) joinPoint.getSignature()).toString());
        }

        return joinPoint.proceed();
    }

    private boolean isEl(String value) {
        return value.startsWith("#{") && value.endsWith("}");
    }

    private Object parseEl(String value, ProceedingJoinPoint joinPoint) {
        //去掉头尾
        String el = value.substring(2, value.length() - 1);
        //按照.分割
        String[] split = el.split("\\.");


        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] parameterNames = signature.getParameterNames();
        int index = -1;
        for (int i = 0; i < parameterNames.length; i++) {
            if (split[0].equals(parameterNames[i])) {
                index = i;
                break;
            }
        }
        if (index == -1) {
            log.error("注解EL表达式定义错误: {}", ((MethodSignature) joinPoint.getSignature()).toString());
        }
        Object arg = joinPoint.getArgs()[index];


        if(split.length == 1){
            return arg;
        }else {
            for (int i = 1; i < split.length; i++) {
                arg = ReflectUtil.getFieldValue(arg, split[i]);
            }
        }

        return arg;

    }
}
  1. redis命令封装类
package com.zhqc.cloud.wms.aop;

import com.zhqc.cloud.common.constants.CommonConstants;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @author zdd
 */
@Component
public class MasterRouteRedis {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    public Object get(String key) {
        return redisTemplate.opsForValue().get(CommonConstants.READ_MASTER_FLAG + key);
    }

    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(CommonConstants.READ_MASTER_FLAG + key, CommonConstants.READ_MASTER_FLAG + value,
                CommonConstants.READ_MASTER_FLAG_EXPIRE, TimeUnit.MILLISECONDS);
    }

}
  1. CommonConstants
package com.zhqc.cloud.common.constants;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;


public class CommonConstants {

    /**
     * 读主库标识前缀(redis)
     */
    public static final String READ_MASTER_FLAG = "readMasterFlag:";

    /**
     * 读主库标识存活时间ms(redis)
     */
    public static final long READ_MASTER_FLAG_EXPIRE = 500;

    /**
     * pageInfo 接口
     */
    public static final String PAGE_INFO = "pageInfo";


}
  1. 使用

    有两种使用方式,一种直接加注解即可,标识这个方法里查询总会查主库;另外一种就是指定el表达式以及前缀,提取并组装key,从redis读取key,有就读主没有就读从。

总结:个人的一个小优化,如果有更好的解决方案,可以进行评论,互相学习。

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值