zl程序教程

您现在的位置是:首页 >  Java

当前栏目

高并发情况下使用动态多数据源dynamic-datasource 3.1.0并发问题复盘

2023-03-07 09:04:17 时间

最近公司内某个项目出现了因并发问题导致的多数据源串掉的问题,经研究找到了如下解决方案

复现流程

image.png

A线程切换指定数据源并挂起

image.png

B线程使用了默认数据源,A线程先于B线程,结果B线程使用了从库数据源(health_bi)

问题原因

image.png

DynamicDataSourceContextHolder中NamedInheritableThreadLocal导致两个数据源共用同一数据源队列Deque<String> 且NamedInheritableThreadLocal会出现将此子线程的值复制到主线程中

InheritableThreadLocal的类注释

This class extends ThreadLocal to provide inheritance of values from parent thread to child thread: when a child thread is created, the child receives initial values for all inheritable thread-local variables for which the parent has values. Normally the child's values will be identical to the parent's; however, the child's value can be made an arbitrary function of the parent's by overriding the childValue method in this class.

Inheritable thread-local variables are used in preference to ordinary thread-local variables when the per-thread-attribute being maintained in the variable (e.g., User ID, Transaction ID) must be automatically transmitted to any child threads that are created.

经翻译为

此类扩展ThreadLocal以提供从父线程到子线程的值继承:当创建子线程时,子线程接收父线程具有值的所有可继承线程本地变量的初始值。通常情况下,孩子的值与父母的值相同;但是,通过重写该类中的childValue方法,可以使子级的值成为父级的任意函数。

当变量中维护的每线程属性(例如,用户ID、事务ID)必须自动传输到创建的任何子线程时,可继承线程本地变量优先于普通线程本地变量。

image.png

子线程创建的时候会获取所有父线程的值。而子线程的值又会传递给父线程,所以就相当于所有子线程的值是共享的,这个问题才会出现。而跟代码的时候你会发现其实所有线程的ArrayDeque都是指向同一个地址

当用new Thread()创建线程时,会走下面的代码,那么inheritThreadLocals就会是true

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}

解决方案

升级dynamic-datasource最新版本,使用NamedThreadLocal,NamedThreadLocal并无其他操作仅仅是命名

参考资料

踩坑dynamic-datasource-spring-boot-starter v3.1.0 自动切换数据源失败

dynamic-datasource-spring-boot-starter v3.1.0 自动切换数据源失败源码分析