4、spring cloud hystrix和线程上下文

上篇文章介绍了hystrix的使用,当一个@HystrixCommand被执行时,可以有两种不同的隔离策略:THREAD(线程)和SEMAPHORE(信号量)。在使用THREAD(线程)隔离策略的时候,由于每个hystrix命令都在一个单独的线程池中执行,这样的话就获取不到父线程的上线文。

示例

这里我简单写了一个ThreadLocal的例子:

/**
 * threadLocal问题
 *
 * @author hui.wang
 * @since 18 November 2018
 */
public class ThreadLocalTest {

    static ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(() -> {
            THREAD_LOCAL.set("测试");
            new A().run();
            new C().run();
        }).start();
    }
}

class A {
    public void run() {
        System.out.println("=========方式1==============");
        System.out.println("thread name :" + Thread.currentThread().getName());
        System.out.println("A get ThreadLocal :" + ThreadLocalTest.THREAD_LOCAL.get());
        new B().run();
    }
}

class B {
    public void run() {
        System.out.println("************");
        System.out.println("thread name :" + Thread.currentThread().getName());
        System.out.println("B get ThreadLocal :" + ThreadLocalTest.THREAD_LOCAL.get());
    }
}

class C {
    public void run() {
        System.out.println("=========方式2==============");
        System.out.println("thread name :" + Thread.currentThread().getName());
        System.out.println("C get ThreadLocal :" + ThreadLocalTest.THREAD_LOCAL.get());
        new Thread(() -> new D().run()).start();
    }
}

class D {
    public void run() {
        System.out.println("************");
        System.out.println("thread name :" + Thread.currentThread().getName());
        System.out.println("D get ThreadLocal :" + ThreadLocalTest.THREAD_LOCAL.get());
    }
}

打印结果为:

=========方式1==============
thread name :Thread-0
A get ThreadLocal :测试
************
thread name :Thread-0
B get ThreadLocal :测试
=========方式2==============
thread name :Thread-0
C get ThreadLocal :测试
************
thread name :Thread-1
D get ThreadLocal :null

可以看到,在C.class里面开启了一个线程执行D.class方法,这时候D.class是获取不到父线程的上线文的。

在我们生产环境经常使用的是,用filter拦截状态信息(例如登录态信息),然后存放在ThreadLocal中,这个时候被@HystrixCommand(使用线程策略)修饰的方法时获取不到这个ThreadLocal的。

解决方案

  1. 准备开发环境
    定义一个UserInfo的类用来存放用户传递的上下文信息
public class UserInfo implements Serializable{

    public static final String USER_ID = "user_id";
    public static final String USER_TOKEN = "user_token";

    private String userId;
    private String userToken;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserToken() {
        return userToken;
    }

    public void setUserToken(String userToken) {
        this.userToken = userToken;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "userId='" + userId + '\'' +
                ", userToken='" + userToken + '\'' +
                '}';
    }
}

接着创建ThreadLocal保存用户上下文信息

/**
 * 保存用户信息上线文
 *
 * @author hui.wang
 * @since 18 November 2018
 */
public class UserContext {

    private static final ThreadLocal<UserInfo> USER_CONTEXT = new ThreadLocal<>();

    /**
     * 获取用户信息
     */
    public static UserInfo getUserInfo() {
        UserInfo userInfo = USER_CONTEXT.get();

        if (Objects.isNull(userInfo)) {
            userInfo = create();
            USER_CONTEXT.set(userInfo);
        }
        return USER_CONTEXT.get();
    }

    private static UserInfo create() {
        return new UserInfo();
    }

    /**
     * 保存用户信息
     */
    public static void setContext(UserInfo userInfo) {
        Assert.notNull(userInfo, "Only non-null UserContext instances are permitted");
        USER_CONTEXT.set(userInfo);
    }

    /**
     * 销毁
     */
    public static void clean() {
        USER_CONTEXT.remove();
    }
}

然后配置filter拦截每次的用户信息保存到ThreadLocal中

/**
 * 模拟用户信息filter
 *
 * @author hui.wang
 * @since 18 November 2018
 */
@Component
public class UserContextFilter implements Filter{

    private static final Logger LOGGER = LoggerFactory.getLogger(UserContextFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // not do
    }

    /**
     * 实现doFilter方法
     * 这里将用户信息保存到上下文中,并在请求结束后clear
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            try {
                HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
                if (StringUtils.isNotEmpty(httpServletRequest.getHeader(UserInfo.USER_ID)) &&
                        StringUtils.isNotEmpty(httpServletRequest.getHeader(UserInfo.USER_TOKEN))) {
                    UserContext.getUserInfo().setUserId(httpServletRequest.getHeader(UserInfo.USER_ID));
                    UserContext.getUserInfo().setUserToken(httpServletRequest.getHeader(UserInfo.USER_TOKEN));
                    LOGGER.info("filter set userInfo success");
                }
            } catch (Exception e) {
                LOGGER.error("filter set userInfo error", e);
            }
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            UserContext.clean();
        }
    }

    @Override
    public void destroy() {

    }
}

搭建完成后,只要在请求的header里面添加上user_iduser_token字段后,filter就可以将这两个信息保存到上下文中
写一个测试controller:

/**
 * 测试filter
 * @see com.hui.wang.spring.cloud.filter.UserContextFilter
 *
 * @author hui.wang
 * @since 18 November 2018
 */
@RestController
public class FilterController {

    private final Logger LOGGER = Logger.getLogger(FilterController.class);

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/userFilter/v1")
    public UserInfo userFilter() {
        UserInfo userInfo = UserContext.getUserInfo();
        return userInfo;
    }
}

访问:

 curl -H'user_id:hui.wang' -H'user_token:123'  http://localhost:8111/userFilter/v1

返回:
{ 
    "userId":"hui.wang",
    "userToken":"123"
}

这样filter部门和上线文部分完成,这个使用如果直接使用@HystrixCommand命令,在执行过程使用UserContext.getUserInfo()是获取不到上下文的

2.自定义Hystrix并发策略
编写ThreadLocalAwareStrategy自定义的并发策略类

/**
 * 自定义并发Hystrix策略
 *
 * @author hui.wang
 * @since 18 November 2018
 */
public class ThreadLocalAwareStrategy extends HystrixConcurrencyStrategy{

    private HystrixConcurrencyStrategy existingConcurrencyStrategy;

    public ThreadLocalAwareStrategy(HystrixConcurrencyStrategy existingConcurrencyStrategy) {
        this.existingConcurrencyStrategy = existingConcurrencyStrategy;
    }

    public ThreadLocalAwareStrategy() {
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        return existingConcurrencyStrategy != null
                ? existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue) : super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
        return existingConcurrencyStrategy != null
                ? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize) : super.getBlockingQueue(maxQueueSize);
    }

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        return existingConcurrencyStrategy != null
                ? existingConcurrencyStrategy.wrapCallable(new DelegatingUserContextCallable<T>(callable, UserContext.getUserInfo())) : super.wrapCallable(new DelegatingUserContextCallable<T>(callable, UserContext.getUserInfo()));
    }

    @Override
    public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
        return existingConcurrencyStrategy != null ? existingConcurrencyStrategy.getRequestVariable(rv) : super.getRequestVariable(rv);
    }
}

wrapCallable方法里面需要提供一个Java Callcable类将用户信息上线文注入到Hystrix中

/**
 * 定义一个Java callable 类,将UserContext注入Hystrix命令中
 *
 * @author hui.wang
 * @since 18 November 2018
 */
public class DelegatingUserContextCallable<V> implements Callable<V>{

    private final Logger LOGGER = Logger.getLogger(DelegatingUserContextCallable.class);

    private final Callable<V> delegate;

    private UserInfo originalUserInfo;

    public DelegatingUserContextCallable(Callable<V> delegate, UserInfo userInfo) {
        Assert.notNull(delegate, "delegate cannot be null");
        Assert.notNull(userInfo, "userContext cannot be null");

        this.delegate = delegate;
        this.originalUserInfo = userInfo;
    }

    public DelegatingUserContextCallable(Callable<V> delegate) {
        this(delegate, UserContext.getUserInfo());
    }

    @Override
    public V call() throws Exception {
        UserContext.setContext(originalUserInfo);

        try {
            return delegate.call();
        } finally {
            this.originalUserInfo = null;
        }
    }

    public static <V> Callable<V> create(Callable<V> delegate, UserInfo userContext) {
        return new DelegatingUserContextCallable<V>(delegate, userContext);
    }
}

接着配置我们自定义的Hystrix并发策略


/**
 * 配置自定义Hystrix并发策略
 *
 * @author hui.wang
 * @since 18 November 2018
 */
@Configuration
public class ThreadLocalConfiguration {

    @Autowired(required = false)
    private HystrixConcurrencyStrategy existingConcurrencyStrategy;

    @PostConstruct
    public void init() {

        HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
        HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
        HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy();
        HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook();

        HystrixPlugins.reset();

        //注册自定义的Hystrix并发策略
        HystrixPlugins.getInstance().registerConcurrencyStrategy(new ThreadLocalAwareStrategy(existingConcurrencyStrategy));
        HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
        HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
        HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
        HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
    }
}

这样就在Hystrix中访问上下文信息了

/**
 *
 * @author hui.wang
 * @since 18 November 2018
 */
@RestController
public class HystrixController {

    private final Logger LOGGER = Logger.getLogger(HystrixController.class);

    @Autowired
    private RestTemplate restTemplate;


    @HystrixCommand(
            threadPoolKey = "hystrix-v1",
            threadPoolProperties = {
                    @HystrixProperty(name = "coreSize", value = "30"),
                    @HystrixProperty(name = "maxQueueSize", value = "10")
            }
    )
    @RequestMapping("/hystrix/v1")
    public String hystrixV1() {
        UserInfo userInfo = UserContext.getUserInfo();
        LOGGER.info("=========================");
        LOGGER.info("userInfo = " + userInfo.toString());
        LOGGER.info("=========================");
        return restTemplate.getForEntity("http://provider-server/provider?request={1}", String.class, "test").getBody();
    }
}

可以看到我们日志打印出的上下文信息

源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • (git上的源码:https://gitee.com/rain7564/spring_microservices_...
    sprainkle阅读 9,314评论 13 33
  • 流程图 下图展示了当你使用 Hystrix 来包装你请求依赖服务时的流程: 接下来将详细介绍如下问题: 1.构建H...
    KingsChan阅读 5,958评论 0 21
  • 一、推荐阅读 可能有部分同学对 Hystrix 的特性了解的不是很清晰,推荐如下文章,写的真的好; 《【翻译】Hy...
    JiMingQiang阅读 1,176评论 0 7
  • 2018年5月29日 星期二 晴 今天中午起床,我就换上演出的衣服。因为今天我们学校有一个古...
    婷Amy阅读 302评论 0 0