Spring Cloud中Hystrix的请求缓存

高并发环境下如果能处理好缓存就可以有效的减小服务器的压力,Java中有许多非常好用的缓存工具,比如Redis、EHCache等,当然在Spring Cloud的Hystrix中也提供了请求缓存的功能,我们可以通过一个注解或者一个方法来开启缓存,进而减轻高并发环境下系统的压力。OK,本文我们就来看看Hystrix中请求缓存的使用。


准备工作

本文的案例依然在前文所搭建环境的基础之上来进行,所以如果尚不明白如何搭建服务注册中心、服务提供者和服务消费者的话,请先阅读前文。

通过方法重载开启缓存

如果我们使用了自定义Hystrix请求命令的方式来使用Hystrix,那么我们只需要重写getCacheKey方法即可实现请求缓存,如下:

public class BookCommand extends HystrixCommand<Book> {

    private RestTemplate restTemplate;
    private Long id;

    @Override
    protected Book getFallback() {
        Throwable executionException = getExecutionException();
        System.out.println(executionException.getMessage());
        return new Book("宋诗选注", 88, "钱钟书", "三联书店");
    }

    @Override
    protected Book run() throws Exception {
        return restTemplate.getForObject("http://HELLO-SERVICE/getbook5/{1}", Book.class,id);
    }

    public BookCommand(Setter setter, RestTemplate restTemplate,Long id) {
        super(setter);
        this.restTemplate = restTemplate;
        this.id = id;
    }

    @Override
    protected String getCacheKey() {
        return String.valueOf(id);
    }
}

系统在运行时会根据getCacheKey方法的返回值来判断这个请求是否和之前执行过的请求一样,即被缓存,如果被缓存,则直接使用缓存数据而不去请求服务提供者,那么很明显,getCacheKey方法将在run方法之前执行。我现在在服务提供者中打印一个日志,如下:

@RequestMapping(value = "/getbook5/{id}", method = RequestMethod.GET)
public Book book5(@PathVariable("id") Integer id) {
    System.out.println(">>>>>>>>/getbook5/{id}");
    if (id == 1) {
        return new Book("《李自成》", 55, "姚雪垠", "人民文学出版社");
    } else if (id == 2) {
        return new Book("中国文学简史", 33, "林庚", "清华大学出版社");
    }
    return new Book("文学改良刍议", 33, "胡适", "无");
}

然后我们服务消费者的Controller中来执行这个请求,如下:

@RequestMapping("/test5")
public Book test5() {
    HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("commandKey");
    HystrixRequestContext.initializeContext();
    BookCommand bc1 = new BookCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")).andCommandKey(commandKey), restTemplate, 1l);
    Book e1 = bc1.execute();
    BookCommand bc2 = new BookCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")).andCommandKey(commandKey), restTemplate, 1l);
    Book e2 = bc2.execute();
    BookCommand bc3 = new BookCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")).andCommandKey(commandKey), restTemplate, 1l);
    Book e3 = bc3.execute();
    System.out.println("e1:" + e1);
    System.out.println("e2:" + e2);
    System.out.println("e3:" + e3);
    return e1;
}

我连着发起三个相同的请求,我们来看看服务提供者的日志打印情况,注意,在服务请求发起之前,需要先初始化HystrixRequestContext。执行效果如下:

小伙伴们看到,上面是服务提供者打印出来的日志,下面是服务消费者打印出来的日志,发起了三个请求,但是服务提供者实际上只执行了一次,其他两次都使用了缓存数据。

有一种特殊的情况:如果我将服务提供者的数据修改了,那么缓存的数据就应该被清除,否则用户在读取的时候就有可能获取到一个错误的数据,缓存数据的清除也很容易,也是根据id来清除,方式如下:

@RequestMapping("/test5")
public Book test5() {
    HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("commandKey");
    HystrixRequestContext.initializeContext();
    BookCommand bc1 = new BookCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")).andCommandKey(commandKey), restTemplate, 1l);
    Book e1 = bc1.execute();
    HystrixRequestCache.getInstance(commandKey, HystrixConcurrencyStrategyDefault.getInstance()).clear(String.valueOf(1l));
    BookCommand bc2 = new BookCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")).andCommandKey(commandKey), restTemplate, 1l);
    Book e2 = bc2.execute();
    BookCommand bc3 = new BookCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")).andCommandKey(commandKey), restTemplate, 1l);
    Book e3 = bc3.execute();
    System.out.println("e1:" + e1);
    System.out.println("e2:" + e2);
    System.out.println("e3:" + e3);
    return e1;
}

小伙伴们注意,这里我们执行完第一次请求之后,id为1的数据就已经被缓存下来了,然后我通过HystrixRequestCache中的clear方法将缓存的数据清除掉,这个时候如果我再发起请求,则又会调用服务提供者的方法,我们来看一下执行结果,如下:

小伙伴们看到,此时服务提供者的方法执行了两次,因为我在第一次请求结束后将id为1的缓存清除了。

通过注解开启缓存

当然,我们也可以通过注解来开启缓存,和缓存相关的注解一共有三个,分别是@CacheResult、@CacheKey和@CacheRemove,我们分别来看。

@CacheResult

@CacheResult方法可以用在我们之前的Service方法上,表示给该方法开启缓存,默认情况下方法的所有参数都将作为缓存的key,如下:

@CacheResult
@HystrixCommand
public Book test6(Integer id,String aa) {
    return restTemplate.getForObject("http://HELLO-SERVICE/getbook5/{1}", Book.class, id);
}

此时test6方法会自动开启缓存,默认所有的参数都将作为缓存的key,如果在某次调用中传入的两个参数和之前传入的两个参数都一致的话,则直接使用缓存,否则就发起请求,如下:

@RequestMapping("/test6")
public Book test6() {
    HystrixRequestContext.initializeContext();
    //第一次发起请求
    Book b1 = bookService.test6(2, "");
    //参数和上次一致,使用缓存数据
    Book b2 = bookService.test6(2, "");
    //参数不一致,发起新请求
    Book b3 = bookService.test6(2, "aa");
    return b1;
}

当然这里我们也可以在@CacheResult中添加cacheKeyMethod属性来指定返回缓存key的方法,注意返回的key要是String类型的,如下:

@CacheResult(cacheKeyMethod = "getCacheKey2")
@HystrixCommand
public Book test6(Integer id) {
    return restTemplate.getForObject("http://HELLO-SERVICE/getbook5/{1}", Book.class, id);
}
public String getCacheKey2(Integer id) {
    return String.valueOf(id);
}

此时默认的规则失效。

@CacheKey

当然除了使用默认数据之外,我们也可以使用@CacheKey来指定缓存的key,如下:

@CacheResult
@HystrixCommand
public Book test6(@CacheKey Integer id,String aa) {
    return restTemplate.getForObject("http://HELLO-SERVICE/getbook5/{1}", Book.class, id);
}

这里我们使用@CacheKey注解指明了缓存的key为id,和aa这个参数无关,此时只要id相同就认为是同一个请求,而aa参数的值则不会作为判断缓存的依据(这里只是举例子,实际开发中我们的调用条件可能都要作为key,否则可能会获取到错误的数据)。如果我们即使用了@CacheResult中cacheKeyMethod属性来指定key,又使用了@CacheKey注解来指定key,则后者失效。

@CacheRemove

这个当然是用来让缓存失效的注解,用法也很简单,如下:

@CacheRemove(commandKey = "test6")
@HystrixCommand
public Book test7(@CacheKey Integer id) {
    return null;
}

注意这里必须指定commandKey,commandKey的值就为缓存的位置,配置了commandKey属性的值,Hystrix才能找到请求命令缓存的位置。举个简单的例子,如下:

@RequestMapping("/test6")
public Book test6() {
    HystrixRequestContext.initializeContext();
    //第一次发起请求
    Book b1 = bookService.test6(2);
    //清除缓存
    bookService.test7(2);
    //缓存被清除,重新发起请求
    Book b2 = bookService.test6(2);
    //参数一致,使用缓存数据
    Book b3 = bookService.test6(2);
    return b1;
}

OK,这就是我们关于Hystrix请求缓存的介绍,有问题欢迎留言讨论。

原文发布于微信公众号 - 玩转JavaEE(gh_d1ca11234a30)

原文发表时间:2017-09-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Linyb极客之路

Spring Cloud开发注意事项

如果provider中需要引入其他feign client的接口,需在 provider的启动类添加注解 @EnableFeignClients(basePac...

42930
来自专栏A周立SpringCloud

如何使用Feign构造多参数的请求

最近经常有人在Spring Cloud中国社区(http://springcloud.cn)QQ群(157525002)里问到该问题。索性整理一下。 本节我们来...

57350
来自专栏Hongten

HIbernate的“1+N”问题

import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.T...

10730
来自专栏猿天地

知识点-Spring Boot 统一异常处理汇总

上面讲的是做页面开发的时候遇到的问题,还有一种情况就是用来开发Rest接口,当错误的时候我们希望返回给用户的是我们接口的标准格式,不是返回一段html代码。

15620
来自专栏Java 源码分析

SpringBoot 笔记 ( 四 ):web 开发

SpringBoot 笔记 (四): web 开发 1、SpringBoot对静态资源的映射规则 @ConfigurationProperties(prefix...

70660
来自专栏王二麻子IT技术交流园地

《SpringMVC从入门到放肆》六、SpringMVC开发Controller的方法总结

到目前为止我们已经大概学习了StringMVC的执行流程,以及详细的处理器映射器和处理器适配器的执行流程,并可以自己写一个配置方式开发的小Demo了。今天我们来...

18720
来自专栏Java技术栈

@Resource,@Autowired,@Inject3种注入方式详解

概况 @Resource,@Autowired,@Inject 这3种都是用来注入bean的,它们属于不同的程序中。 ANNOTATIONPACKAGESOUR...

40390
来自专栏Java3y

SpringMVC入门就这么简单

什么是SpringMVC? SpringMVC是Spring家族的一员,Spring是将现在开发中流行的组件进行组合而成的一个框架!它用在基于MVC的表现层开发...

65160
来自专栏编程坑太多

『高级篇』docker之课程管理dubbo入门操练(14)

PS:dubbo的入门也就到这里,从spring 和springboot 对dubbo的整合。 流程基本之前也说,api 建立接口,provider 实现接口,...

11620
来自专栏JAVA技术站

JFinal整合Spring开发 原

思路大概是这样子的,首先需要初始化Spring的容器,把所有注解类加入到容器中,Spring里的AnnotationConfigApplicationConte...

10620

扫码关注云+社区

领取腾讯云代金券