前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >解决在同一个线程下数据源多次切换的回溯问题

解决在同一个线程下数据源多次切换的回溯问题

作者头像
吴就业
发布2020-07-10 11:27:46
7480
发布2020-07-10 11:27:46
举报
文章被收录于专栏:Java艺术Java艺术

版本号:1.0.6-RELEASE 日期:2020/04/24 更新内容:解决在同一个线程下数据源多次切换的回溯问题

作者开源的几个项目都有在项目中使用,并且已经发布到maven中央仓库,遇到问题会及时解决,欢迎大家使用,有问题可到githubissues

解决在同一个线程下数据源多次切换的回溯问题

在某些场景下,我们可能需要多次切换数据源才能处理完同一个请求,也就是在一个线程上多次切换数据源。

比如:ServiceA.a调用ServiceB.bServiceB.b调用ServiceC.cServiceA.a使用从库,ServiceB.b使用主库,ServiceC.c又使用从库,因此,这一调用链路一共需要动态切换三次数据源。

数据源的切换我们都是使用AOP完成,在方法执行之前切换,从注解上获取到数据源的key,将其保持到ThreadLocal

当方法执行完成或异常时,需要从ThreadLocal中移除切换记录,否则可能会影响别的不显示声明切换数据源的地方获取到错误的数据源,并且我们也需要保证ThreadLocalremove方法被调用,这在多次切换数据源的情况下就会出问题。

当调用ServiceA.a时,切换到从库,方法执行到一半时由于需要调用ServiceB.b方法,此时数据源又被切换到了主库,也就是说ServiceB.b方法切面将ServiceA.a方法切面的数据源切换记录覆盖了。

ServiceB.b方法执行完成后,ServiceB.b方法切面调用ThreadLocalremove方法,将ServiceB.b方法切面的数据源切换记录移除,此时回到ServiceA.a方法继续往下执行时,由于ThreadLocal存储null, 如果配置了默认使用的数据源为主库,那么ServiceA.a方法后面的数据库操作就都在主库上操作了。

这一现象我们可以称为方法调用回溯导致的动态数据源切换故障。

使用切面实现动态切换数据源的方法如下:

代码语言:javascript
复制
public class EasyMutiDataSourceAspect {
/**
     * 切换数据源
     *
     * @param point 切点
     * @return
     * @throws Throwable
     */
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        EasyMutiDataSource ds = method.getAnnotation(EasyMutiDataSource.class);
        if (ds == null) {
            DataSourceContextHolder.setDataSource(null);
        } else {
            DataSourceContextHolder.setDataSource(ds.value());
        }
        try {
            return point.proceed();
        } finally {
            DataSourceContextHolder.clearDataSource();
        }
    }
}

为解决这个问题,我想到的是使用栈这个数据结构存储动态数据源的切换记录。当调用ServiceA.a方法需要切换数据源时,将数据源的key push到栈顶,当在ServiceA.a方法中调用ServiceB.b方法时,切面切换数据源也将ServiceB.b方法需要切换的数据源的key push到栈顶。代码如下:

代码语言:javascript
复制
public final class DataSourceContextHolder {
   
   /**
     * 设置数据源
     *
     * @param multipleDataSource
     */
    public static void setDataSource(EasyMutiDataSource.MultipleDataSource multipleDataSource) {
        // 用于存储切换记录的栈
        DataSourceSwitchStack switchStack = multipleDataSourceThreadLocal.get();
        if (switchStack == null) {
            switchStack = new DataSourceSwitchStack();
            multipleDataSourceThreadLocal.set(switchStack);
        }
        // 将当前切换的数据源推送到栈顶,覆盖上次切换的数据源
        switchStack.push(multipleDataSource);
    }
}

ServiceB.b方法执行完成时,方法切面需要调用clearDataSource方法将切换的数据源的keyThreadLocal中移除,这时我们可以先从栈顶中移除一个元素,再判断栈是否为空,为空再将栈从ThreadLocal中移除。pop操作将ServiceB.b方法切面切换的数据源的key移除后,栈顶就是调用ServiceB.b方法之前使用的数据源。

代码语言:javascript
复制
public final class DataSourceContextHolder {
    
   /**
     * 清除数据源
     */
    public static void clearDataSource() {
        DataSourceSwitchStack switchStack = multipleDataSourceThreadLocal.get();
        if (switchStack == null) {
            return;
        }
        // 回退数据源切换
        switchStack.pop();
        // 栈空则表示所有切换都已经还原,可以remove了
        if (switchStack.size() == 0) {
            multipleDataSourceThreadLocal.remove();
        }
    }
}

只有所有切点都调用完clearDataSource方法之后,再将保持数据源切换记录的栈从ThreadLocal中移除。每个切点执行完成之后,调用clearDataSource方法将自身的切换记录从栈中移除,栈顶存储的就是前一个切点的切换记录,即回退数据源切换。这就可以解决同一个线程下数据源多次切换的回溯问题,使数据源切换正常。

存储切换记录的栈在easymulti-datasource的时候如下。

代码语言:javascript
复制
class DataSourceSwitchStack {

    private EasyMutiDataSource.MultipleDataSource[] stack;
    private int topIndex;
    private int leng = 2;

    public DataSourceSwitchStack() {
        stack = new EasyMutiDataSource.MultipleDataSource[leng];
        topIndex = -1;
    }

    public void push(EasyMutiDataSource.MultipleDataSource source) {
        if (topIndex + 1 == leng) {
            leng *= 2;
            stack = Arrays.copyOf(stack, leng);
        }
        this.stack[++topIndex] = source;
    }

    public EasyMutiDataSource.MultipleDataSource peek() {
        return stack[topIndex];
    }

    public EasyMutiDataSource.MultipleDataSource pop() {
        return stack[topIndex--];
    }

    public int size() {
        return topIndex + 1;
    }

}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java艺术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档