您好,欢迎来到好走旅游网。
搜索
您的当前位置:首页使用Hystrix时要处理的上下文在不同线程传输问题

使用Hystrix时要处理的上下文在不同线程传输问题

来源:好走旅游网

引言

springcloud微服务中, 有些参数信息是要进行传输到不同进程服务的,但是当使用Hystrix做流量控制时,并且使用的是线程策略,那么这时在调用服务或者服务类时,由于进入了子线程,在原来父线程的上下文就获取不到了,这时就无法正常把如session信息等通过Feign调用传递请求头给提供方。

问题重现

编写controller 在此写入上下文信息–> 调用服务层service 在此获取上下文 --> 结果是获取时报空指针

代码1-ThreadLocalController

package cn.springcloud.controller;

import cn.springcloud.config.HystrixThreadLocal;
import cn.springcloud.service.HystrixService;
import cn.springcloud.service.ThreadLocalService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

@Controller
public class ThreadLocalController {
    @Autowired
    private ThreadLocalService threadLocalService;
    @GetMapping("/getuser/{id}")
    @ResponseBody
    public Object getUser(@PathVariable Integer id){
        HystrixThreadLocal.threadLocal.set("userId="+id);
        StringBuffer sff = new StringBuffer();
        RequestContextHolder.currentRequestAttributes().setAttribute("userId",
                "userId="+id, RequestAttributes.SCOPE_REQUEST);
        sff.append("Current thread---->"+Thread.currentThread());
        sff.append("   ");
        sff.append("HystrixThreadLocal----> userId: "+ HystrixThreadLocal.threadLocal.get());
        sff.append("   ");
        sff.append("RequestContextHolder---->"+RequestContextHolder.currentRequestAttributes()
        .getAttribute("userId",RequestAttributes.SCOPE_REQUEST));
        String user = threadLocalService.getUser(id);
        return user+"   "+sff.toString();



    }
}``

代码2-ThreadLocalService

注意,这里添加了@HystrixCommand,如果不添加时是正常的。

package cn.springcloud.service;

import cn.springcloud.config.HystrixThreadLocal;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

@Service
public class ThreadLocalService {
    @HystrixCommand
    public String getUser(Integer id){
        System.out.println("Current thread:"+Thread.currentThread());
        System.out.println("HystrixLocal:"+ HystrixThreadLocal.threadLocal.get());
        Object userId = null;
        try{

            userId = RequestContextHolder.getRequestAttributes().getAttribute("userId", RequestAttributes.SCOPE_REQUEST);
        }catch (Exception e){
            System.out.println("error userid");
        }
        System.out.println("RequestContexHolder:"+ userId);

        return "user:"+id;
    }

}

测试结果:

原因分析

查看相关的策略类可以看出RequestContextHolder保存的是ThreadLocal,另外,

处理

在SecurityContextConcurrencyStrategy 下有一个它自己的方法org.springframework.cloud.netflix.hystrix.security.SecurityContextConcurrencyStrategy#wrapCallable,这个方法有创建一个DelegatingSecurityContextCallable,进入这个DelegatingSecurityContextCallable再看下它的org.springframework.security.concurrent.DelegatingSecurityContextCallable#call,从中可以看出有一个return delegate.call();动作。根据网上与相关书籍介绍,可以从这个delegate.call()前进行设置上下文,以同步到子线程。执行步骤如下:
创建SpringCloudHystrixConcurrencyStrategy 继承 HystrixConcurrencyStrategy --》 重写wrapCallable --》此方法返回一个新的类对象HystrixThreadCallable,实现了java.util.concurrent.Callable --》重写Callable的call方法 --》 在执行return delegate.call(); 前设置上下文。
注意事项:HystrixThreadLocal 是我另外增加的一个保存当前线程的信息类,这个可以根据自己情况,如果使用自己新增的线程保存类,则RequestContextHolder可以不使用都可以了。

代码3-HystrixThreadLocal

这个可有可无,主要是用来保存自定义的thredlocal

package cn.springcloud.config;

public class HystrixThreadLocal {
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

}

代码4-SpringCloudHystrixConcurrencyStrategy

这个是新增的策略类,用来控制增加上下文传输的逻辑,是必须的。
注意事项:这个类里面有一个内部类HystrixThreadCallable,用来执行call的

package cn.springcloud.config;

import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.concurrent.Callable;

public class SpringCloudHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy  {
    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        return new HystrixThreadCallable<>(callable, RequestContextHolder.getRequestAttributes());
    }
    static class HystrixThreadCallable<T> implements Callable<T>{
        private final Callable<T> delegate;
        private final RequestAttributes requestAttributes;
        public HystrixThreadCallable(Callable<T> callable, RequestAttributes requestAttributes) {
            this.delegate = callable;
            this.requestAttributes = requestAttributes;
        }
        @Override
        public T call() throws Exception {
            try{
                RequestContextHolder.setRequestAttributes(requestAttributes);
                HystrixThreadLocal.threadLocal.set(requestAttributes.getAttribute("userId",RequestAttributes.SCOPE_REQUEST).toString());
                return delegate.call();
            }finally {
                RequestContextHolder.getRequestAttributes();
            }
        }
    }
}

代码5-主类增加注册

把新增的策略类SpringCloudHystrixConcurrencyStrategy注册到HystrixPlugins

    @PostConstruct
    public void init(){
        HystrixPlugins.getInstance().registerConcurrencyStrategy(new SpringCloudHystrixConcurrencyStrategy());
    }

结果验证

从下图可以看出,前后的信息一致了。

问题还没有解决

问题如下图

问题2

这个问题是我在《重新定义SpringCloud实战》中看到的,但是文章上说只能被调用一次,但是测试多个客户端多次发起请求都没测试出这个问题,所以这里顺便贴出来。代大家验证。
同时也希望大家能提供验证的方式以供参考,谢谢!

感谢文章作者

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- haog.cn 版权所有

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务