SpringCloud Hystrix 请求缓存的使用

本文转自:SpringCloud (八) Hystrix 请求缓存的使用

前言

最近忙着微服务项目的开发,脱更了半个月多,今天项目的初版已经完成,所以打算继续我们的微服务学习,由于Hystrix这一块东西好多,只好多拆分几篇文章写,对于一般对性能要求不是很高的项目中,可以使用其基础上开发的Feign进行容错保护。Hystrix学到现在我认为它的好处在于可以更灵活的调整熔断时间和自定义的线程隔离策略,设置请求缓存与请求合并,还可以降低被调用服务的负载,配合仪表盘和Turbine进行服务状态监控等,更加深入的还请阅读书籍,理解浅薄,还望看官莫笑。 由于篇幅有限,请求合并的使用放在下一篇博客中

本文主要浅析Hystrix的请求缓存的使用

前情提要:

之前我们学习了自定义HystrixCommand,包括继承和注解两种方式实现了同步请求和异步请求,也正是这里我们开始使用了整个的项目管理我们的代码,防止了项目过于分散丢笔记的情况。

如果是和我一样在搭建并测试的,请来Github clone我的项目,地址是:https://github.com/HellxZ/SpringCloudLearn 欢迎大家对这个项目提建议。

正文:

在高并发的场景中,消费者A调用提供者B,如果请求是相同的,那么重复请求势必会加重服务提供者B的负载,一般我们做高并发场景会理所应当的想到用缓存,那么针对这种情况Hystrix有什么方式来应对么?

Hystrix有两种方式来应对高并发场景,分别是请求缓存与请求合并

回顾一下我们前几篇文章中搭建的服务提供者项目,它会在有请求过来的时候打印此方法被调用。为了这次的测试,我们先在服务提供者项目中提供一个返回随机数的接口,作为测试请求缓存的调用的接口,方便验证我们的看法。

在EurekaServiceProvider项目中的GetRequestController中添加如下方法

    /**
     * 为了请求测试Hystrix请求缓存提供的返回随机数的接口
     */
    @GetMapping("/hystrix/cache")
    public Integer getRandomInteger(){
        Random random = new Random();
        int randomInt = random.nextInt(99999);
        return randomInt;
    }

接下来我们先介绍请求缓存

请求缓存

在高并发的场景下,Hystrix 提供了请求缓存的功能,我们可以方便的开启和使用请求缓存来优化系统,达到减轻高并发时的请求线程消耗、降低请求响应时间的效果

Hystrix的缓存,这个功能是有点鸡肋的,因为这个缓存是基于request的,为什么这么说呢?因为每次请求来之前都必须HystrixRequestContext.initializeContext();进行初始化,每请求一次controller就会走一次filter,上下文又会初始化一次,前面缓存的就失效了,又得重新来。

所以你要是想测试缓存,你得在一次controller请求中多次调用那个加了缓存的service或HystrixCommand命令。Hystrix的书上写的是:在同一用户请求的上下文中,相同依赖服务的返回数据始终保持一致。在当次请求内对同一个依赖进行重复调用,只会真实调用一次。在当次请求内数据可以保证一致性。

因此。希望大家在这里不要理解错了。

请求缓存图,如下:

Hystrix 请求缓存图
假设两个线程发起相同的HTTP请求,Hystrix会把请求参数初始化到ThreadLocal中,两个Command异步执行,每个Command会把请求参数从ThreadLocal中拷贝到Command所在自身的线程中,Command在执行的时候会通过CacheKey优先从缓存中尝试获取是否已有缓存结果,

如果命中,直接从HystrixRequestCache返回,如果没有命中,那么需要进行一次真实调用,然后把结果回写到缓存中,在请求范围内共享响应结果。

RequestCache主要有三个优点:

  • 在当次请求内对同一个依赖进行重复调用,只会真实调用一次。
  • 在当次请求内数据可以保证一致性。
  • 可以减少不必要的线程开销。

这里分别介绍使用继承和使用注解两种方式,这里使用前文用到的RibbonConsumHystix项目

1.1继承方式

1.1.1 开启请求缓存 与 清除请求缓存

在com.cnblogs.hellxz.hystrix包下,因为和UserCommand类中的返回值不同,为了不破坏已有代码,我们在hystrix新建一个CacheCommand类,如下:

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixRequestCache;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault;
import org.springframework.web.client.RestTemplate;

public class CacheCommand extends HystrixCommand<Integer> {


    private RestTemplate restTemplate;
    private static Long id;
    private static final HystrixCommandKey GETTER_KEY = HystrixCommandKey.Factory.asKey("test");

    public CacheCommand(String commandGroupKey, RestTemplate restTemplate, Long id){
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(commandGroupKey)).andCommandKey(GETTER_KEY));
        this.restTemplate = restTemplate;
        this.id = id;
    }

    /**
     * 这里我们调用产生随机数的接口
     */
    @Override
    protected Integer run() throws Exception {
        return restTemplate.getForObject("http://eureka-service/hystrix/cache",Integer.class);
    }

    /**
     * 开启请求缓存,只需重载getCacheKey方法
     * 因为我们这里使用的是id,不同的请求来请求的时候会有不同cacheKey所以,同一请求第一次访问会调用,之后都会走缓存
     * 好处:    1.减少请求数、降低并发
     *          2.同一用户上下文数据一致
     *          3.这个方法会在run()和contruct()方法之前执行,减少线程开支
     */
    @Override
    public String getCacheKey() {
        return String.valueOf(id); //这不是唯一的方法,可自定义,保证同一请求返回同一值即可
    }

    /**
     * 清理缓存
     * 开启请求缓存之后,我们在读的过程中没有问题,但是我们如果是写,那么我们继续读之前的缓存了
     * 我们需要把之前的cache清掉
     * 说明 :   1.其中getInstance方法中的第一个参数的key名称要与实际相同
     *          2.clear方法中的cacheKey要与getCacheKey方法生成的key方法相同
     *          3.注意我们用了commandKey是test,大家要注意之后new这个Command的时候要指定相同的commandKey,否则会清除不成功
     */
    public static void flushRequestCache(Long id){
        HystrixRequestCache.getInstance(GETTER_KEY, HystrixConcurrencyStrategyDefault.getInstance())
                .clear(String.valueOf(id));
    }

    public static Long getId() {
        return id;
    }

}

说明一下,使用继承的方式,只需要重写getCacheKey(),有了开启缓存自然有清除缓存的方法,用以确保我们在同一请求中进行写操作后,让后续的读操作获取最新的结果,而不是过时的结果。

需要注意的地方:

  • CacheCommand 构造函数里需要使用 andCommandKey 设置 commandKey,而且这个 commandKey 要和 flushRequestCache 里的一致。否则,无法成功清除缓存。
  • flushRequestCache(Long id),其中.clear()中的cacheKey的生成方法相同,只有把正确需要清除的key清掉才会连同value一同清掉,从而达到清除缓存的作用。
  • 清除缓存时机:我们应该在同一个Controller中进行写操作之后,如果这个操作之后还有访问同一资源的请求,那么必须加清除缓存,从而保证数据同步,如果后面没有读操作,无须清除缓存,因为在下一次请求到来的时候HystrixRequestContext会重置,缓存自然也没有了1

使用 controller 进行调用

package com.johnfnash.learn.springcloud.hystrix.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.johnfnash.learn.springcloud.hystrix.demo.command.CacheCommand;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

@RestController
public class CacheController {

    @Autowired
    private RestTemplate restTemplate;
    
    @RequestMapping("/cache")
    public String cache() {
        // Hystrix的缓存实现,这功能有点鸡肋
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        
        CacheCommand command = new CacheCommand("test", restTemplate, 1L);
        Integer result1 = command.execute();
        
        // 清空缓存
        //CacheCommand.flushRequestCache(1L);
        
        CacheCommand command1 = new CacheCommand("test", restTemplate, 1L);
        Integer result2 = command1.execute();
        
        System.out.println("first request result is: " + result1 + " ,and secend request result is: " + result2);
        
        context.close();
        
        return null;
    }
    
}

注意:之前说过,每次Controller被访问的时候,Hystrix请求的上下文都需要被初始化,这里可以用这种方式作测试,但是生产环境是用filter的方式初始化的,这种方式放到请求缓存的结尾讲

分别启动注册中心、服务提供者、当前项目,然后访问 /cache。

我们会看到

first request result is: 67380 ,and secend request result is: 67380

去掉上面清空缓存的注解,重新访问,输出如下内容:

first request result is: 43919 ,and secend request result is: 51052

1.2 注解方式

继承方式自然没有注解开发快而且省力,想必大家期待已久了,在此之前我们需要了解三个注解:

注解描述属性
@CacheResult该注解用来标记请求命令返回的结果应该被缓存,它必须与@HystrixCommand注解结合使用cacheKeyMethod
@CacheRemove该注解用来让请求命令的缓存失效,失效的缓存根据commandKey进行查找commandKey,cacheKeyMethod
@CacheKey该注解用来在请求命令的参数上标记,使其作为cacheKey,如果没有使用此注解则会使用所有参数列表中的参数作为cacheKeyvalue

本人实测总结三种注解方式均可用,实现大同小异,与大家分享之

1.2.1 方式1 :使用getCacheKey方法获取cacheKey

扩充RibbonService

    /**
     * 使用注解请求缓存 方式1
     * @CacheResult  标记这是一个缓存方法,结果会被缓存
     */
    @CacheResult(cacheKeyMethod = "getCacheKey")
    @HystrixCommand(commandKey = "commandKey1")
    public Integer openCacheByAnnotation1(Long id){
        //此次结果会被缓存
        return restTemplate.getForObject("http://eureka-service/hystrix/cache", Integer.class);
    }

    /**
     * 使用注解清除缓存 方式1
     * @CacheRemove 必须指定commandKey才能进行清除指定缓存
     */
    @CacheRemove(commandKey = "commandKey1", cacheKeyMethod = "getCacheKey")
    @HystrixCommand
    public void flushCacheByAnnotation1(Long id){
        LOGGER.info("请求缓存已清空!");
        //这个@CacheRemove注解直接用在更新方法上效果更好
    }

    /**
     * 第一种方法没有使用@CacheKey注解,而是使用这个方法进行生成cacheKey的替换办法
     * 这里有两点要特别注意:
     * 1、这个方法的入参的类型必须与缓存方法的入参类型相同,如果不同被调用会报这个方法找不到的异常
     * 2、这个方法的返回值一定是String类型
     */
    public String getCacheKey(Long id){
        return String.valueOf(id);
    }

扩充 CacheController

    /**
     * 注解方式请求缓存,第一种
     */
    @GetMapping("/cacheAnnotation1")
    public void openCacheByAnnotation1(){
        //初始化Hystrix请求上下文
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        //访问并开启缓存
        Integer result1 = service.openCacheByAnnotation1(1L);
        Integer result2 = service.openCacheByAnnotation1(1L);
        System.out.println("first request result is: " + result1 + " ,and secend request result is: " + result2);

        //清空缓存
        service.flushCacheByAnnotation1(1L);
        
        Integer result3 = service.openCacheByAnnotation1(1L);
        Integer result4 = service.openCacheByAnnotation1(1L);
        System.out.println("first request result is: " + result3 + " ,and secend request result is: " + result4);
        
        context.close();
    }

访问该接口,输出如下:

first request result is: 2524 ,and secend request result is: 2524
请求缓存已清空!
first request result is: 41515 ,and secend request result is: 41515
1.2.2 方式2 :使用@CacheKey指定cacheKey

扩充RibbonService

    /**
     * 使用注解请求缓存 方式2
     * @CacheResult  标记这是一个缓存方法,结果会被缓存
     * @CacheKey 使用这个注解会把最近的参数作为cacheKey
     *
     * 注意:有些教程中说使用这个可以指定参数,比如:@CacheKey("id") , 但是我这么用会报错,网上只找到一个也出这个错误的贴子没解决
     *          而且我发现有一个问题是有些文章中有提到 “不使用@CacheResult,只使用@CacheKey也能实现缓存” ,经本人实测无用
     */
    @CacheResult
    @HystrixCommand(commandKey = "commandKey2")
    public Integer openCacheByAnnotation2(@CacheKey Long id){
        //此次结果会被缓存
        return restTemplate.getForObject("http://eureka-service/hystrix/cache", Integer.class);
    }

    /**
     * 使用注解清除缓存 方式2
     * @CacheRemove 必须指定commandKey才能进行清除指定缓存
     */
    @CacheRemove(commandKey = "commandKey2")
    @HystrixCommand
    public void flushCacheByAnnotation2(@CacheKey Long id){
        LOGGER.info("请求缓存已清空!");
        //这个@CacheRemove注解直接用在更新方法上效果更好
    }

扩充 RibbonController,使用和 openCacheByAnnotation1 差不多的代码,只是调用的方法变成了方式2中定义的。进行测试,结果和方式一类似。

1.2.3 方式3 :使用默认所有参数作为cacheKey

去掉方式2中的 @CacheKey 注解,其它代码和方式2完成一样。进行测试,结果和上面的类似。这里就不再贴代码和测试结果了。

1.4 出现的问题

1.4.1 HystrixRequestContext 未初始化

2018-05-17 16:57:22.759 ERROR 5984 --- [nio-8088-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: UserCommand failed while executing.] with root cause

java.lang.IllegalStateException: Request caching is not available. Maybe you need to initialize the HystrixRequestContext?
    at com.netflix.hystrix.HystrixRequestCache.get(HystrixRequestCache.java:104) ~[hystrix-core-1.5.12.jar:1.5.12]
……省略多余输出……

初始化HystrixRequestContext方法:

两种方法

1、在每个用到请求缓存的Controller方法的第一行加上如下代码:

        //初始化Hystrix请求上下文
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        //省略中间代码上下文环境用完需要关闭
        context.close();

2、使用Filter方式:

在启动类加入@ServletComponentScan注解

在con.cnblogs.hellxz.filter包下创建HystrixRequestContextServletFilter.java,实现Filter接口,在doFilter方法中添加方法1中的那一行代码,并在一次请求结束后关掉这个上下文

import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(filterName = "hystrixRequestContextServletFilter",urlPatterns = "/*",asyncSupported = true)
public class HystrixRequestContextServletFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //初始化Hystrix请求上下文
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            //请求正常通过
            chain.doFilter(request, response);
        } finally {
            //关闭Hystrix请求上下文
            context.shutdown();
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }
}

此时注释掉RibbonController中每个Controller方法中的HystrixRequestContext.initializeContext();(不注掉也没事)

执行上面的测试,会发现效果一致。

结语

通过这篇文章我们学习了Hystrix的请求缓存的使用,在写本文过程中纠正了很多只看书学到的错误知识,并不是说书中写错了,可能是spring cloud的不同版本所造成的问题,所以,学习还是推荐大家动手实践。受限于篇幅的限制,本来是想把请求合并一并写出来的,想了下暂时请求合并的代码我还没有测试通过,所以,综上所述,请求合并部分,我会在下篇文章中写。可能不会快,但一定会有。

本文引用文章出处:

  1. Request caching is not available. Maybe you need to initialize the HystrixRequestContext
  2. Spring Cloud中Hystrix的请求缓存
  3. Spring Cloud @HystrixCommand和@CacheResult注解使用,参数配置
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Cloud Hystrix是一种用于构建容错和弹性系统的开源库,它允许开发者在分布式系统中处理延迟和故障,并在这些系统之间提供后备选项。Hystrix的原理可以概括为以下几点。 首先,Hystrix通过使用线程池隔离和信号量隔离的方式,为每个依赖服务创建了一个独立的执行环境。这意味着每个服务的执行都是在独立的线程或信号量中完成的,这样可以防止由于依赖服务的故障而导致整个系统崩溃。 其次,Hystrix使用了断路器模式来防止故障的扩散。当一个依赖服务的失败率超过预定义的阈值时,断路器会打开,进而停止请求该服务,而是直接返回预定义的fallback值。这个过程可以有效地保护系统免受失败服务的影响,并且在服务恢复正常后,断路器会逐渐闭合,重新允许对该服务的请求。 另外,Hystrix还提供了实时监控和报告功能。通过使用Hystrix Dashboard,开发者可以实时监控每个依赖服务的运行状态,包括请求的成功率、失败率、响应时间等指标,以及断路器的状态。这对于故障诊断和性能优化非常有帮助。 最后,Hystrix还支持请求缓存请求合并等功能,以进一步提升系统的性能和容错能力。通过对重复的请求进行合并和缓存,可以减少网络开销和服务的负载,同时也减少了对依赖服务的请求次数,降低了故障的风险。 总的来说,Spring Cloud Hystrix通过使用断路器、隔离和容错等机制,能够保护系统免受依赖服务的故障影响,并提供实时监控和报告,以及其他增强性能的功能,从而提高了分布式系统的可靠性和弹性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值