parallelStream并发线程问题

问题描述:
http://cloud.anker-in.com/service/interface/message
{“interfaceCode”:“CSC2EPAAS_REVIEW_BL_QUERY”,“sourceSystem”:“CSC”,“messageBody”:{“email”:“dimension2000a@gmail.com”}}
报错提示:接口访问报找不到数据源
原因描述:生产调用到了服务化sit环境的服务,所以用当前租户和环境是肯定找不到地址的

重要问题:为什么生产会调用到服务化sit环境的服务?(tpcloud租户下的脚本调用的是sit环境,怀疑是不是串了)
调用链:
(默认租户)域名,网关,interface, mvel,order。
(anker租户)域名,网关,interface, mvel,order。
http://10.3.2.222:8290/service/interface/message->调用正常,证明默认租户里面的缓存是正常的。
http://cloud.anker-in.com/service/interface/message->anker租户下的缓存不正常,所以当我去页面保存脚本时候,整个调用链就正常了

重要问题:为什么安克租户下的缓存不对?
重要问题: 现在的调用好像都会去调用tpcloud的缓存?怎么证明
重要问题:为什么刷新安克租户下的缓存就成功了?难道没有缓存就会去调用tpcloud吗?
重要问题:如果真的是通过tpcloud来调用的order服务,那么脚本应该是写死的ip地址,那么应该是不会带有租户信息的

通过几个请求的返回结果和tpcloud的脚本的返回结果 对比后不难发现是租户之间的缓存信息串了,所以以上问题都能得到解释

如果是混乱,那么需要看脚本缓存加载的方法:ScriptEngine.java
@PostConstruct
private void initScript() {
for(String tenant : configCache.getCONFIG_CACHE().keySet()){
new Thread(new InitThread(tenant)).start();
}
if(DataSourceTenantContextHolder.DEFAULT_TENANT.equals(configCache.getDefaultTenant())){
new Thread(new InitThread(DataSourceTenantContextHolder.DEFAULT_TENANT)).start();
}
}

private class InitThread implements Runnable {
private String tenant;

public InitThread(String tenant) {
    this.tenant = tenant;
}

@Override
public void run() {
    initTenantScript(tenant);
}

}

private void initTenantScript(String tenant){
DataSourceTenantContextHolder.setCurrentTanent(tenant);
List

private String scriptKey(Long id){
String tenant = DataSourceTenantContextHolder.getCurrentTenant();
if(DataSourceTenantContextHolder.DEFAULT_TENANT.equals(tenant)){
tenant = configCache.getDefaultTenant();
}
return tenant+id;
}

抽离出来以后的问题代码:
public static void main(String[] args) {
new Thread(new InitThread2(“tenant1”)).start();
new Thread(new InitThread2(“tenant2”)).start();
}

private static class InitThread2 implements Runnable {
private String tenant;

public InitThread2(String tenant) {
    this.tenant = tenant;
}

@Override
public void run() {
    DataSourceTenantContextHolder.setCurrentTanent(tenant);
    List<String> items = new ArrayList<>();;
    for(int i = 0 ; i<1000; i++){
        items.add(tenant);
    }
    logger.info(tenant+":"+Thread.currentThread().getName());//Thread-0:tenant1当前租户标志tenant1
    //Thread-1:tenant2当前租户标志tenant2
    items.parallelStream().forEach(item -> {
        logger.info(Thread.currentThread().getName()+":"+DataSourceTenantContextHolder.getCurrentTenant()+"当前租户标志"+tenant);
    });
}

}

解决的办法:一个是将parallelStream改为普通的Stream,另外一个是并行处理中设置租户信息
改进后代码:
public static void main(String[] args) {
new Thread(new InitThread2(“tenant1”)).start();
new Thread(new InitThread2(“tenant2”)).start();
}

private static class InitThread2 implements Runnable {
private String tenant;

public InitThread2(String tenant) {
    this.tenant = tenant;
}

@Override
public void run() {
    DataSourceTenantContextHolder.setCurrentTanent(tenant);
    List<String> items = new ArrayList<>();;
    for(int i = 0 ; i<1000; i++){
        items.add(tenant);
    }
    logger.info(tenant+":"+Thread.currentThread().getName());//Thread-0:tenant1当前租户标志tenant1
    //Thread-1:tenant2当前租户标志tenant2
    items.parallelStream().forEach(item -> {
        DataSourceTenantContextHolder.setCurrentTanent(tenant);
        logger.info(Thread.currentThread().getName()+":"+DataSourceTenantContextHolder.getCurrentTenant()+"当前租户标志"+tenant);
    });
}

}

原因分析:
在这里插入图片描述
1.总数还是2000个,work数默认为cpu-1,然后让调用线程也参与其工作
2.thread-0,thread-1是隔离的,其对应关系是不会存在问题的
3.InheritableThreadLocal保存着租户信息,而底层是用thread为key,租户为value的map存着,保证着线程安全
4.那么work在获取InheritableThreadLocal里面的租户信息的时候到底会调用哪个父进程的租户信息呢。
第一次thread-0执行的时候forkjoin-work1,这里面执行获取的租户信息是明确的,是取的thread-0父线程的租户信息
第二次thread-1执行的时候,将一个任务丢到了forkjoin-work1里面,那么forkjoin-work1获取租户信息的时候取的是thread-1父线程的租户信息
因为这7个线程是公共的,而线程里面的任务是两个不同Thread派发过来的,里面的实现是要获取当前线程的租户信息,那么这样就会导致后面派发过来的任务里面的租户信息覆盖这个work线程里面的租户信息,从而导致任务执行时取到的租户信息发生错误。

修改分析:如果每个子任务在执行的时候都直接指定租户,可以避免取值问题.
如果不采用parallelStream,那么每个线程之间都是完全隔离的,所以也可以解决问题.
#如果使用TransmittableThreadLocal传递的话,可以将线程和work绑定.

相关知识点
parallelStream:https://www.jianshu.com/p/3d4e76467990
InheritableThreadLocal:https://www.jianshu.com/p/94ba4a918ff5
TransmittableThreadLocal:https://github.com/alibaba/transmittable-thread-local

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值