高并发环境下如果能处理好缓存就可以有效的减小服务器的压力,Java中有许多非常好用的缓存工具,比如Redis、EHCache等,当然在Spring Cloud的Hystrix中也提供了请求缓存的功能,我们可以通过一个注解或者一个方法来开启缓存,进而减轻高并发环境下系统的压力。OK,本文我们就来看看Hystrix中请求缓存的使用。
本文是Spring Cloud系列的第十三篇文章,了解前十二篇文章内容有助于更好的理解本文:
1.使用Spring Cloud搭建服务注册中心
2.使用Spring Cloud搭建高可用服务注册中心
3.Spring Cloud中服务的发现与消费
4.Eureka中的核心概念
5.什么是客户端负载均衡
6.Spring RestTemplate中几种常见的请求方式
7.RestTemplate的逆袭之路,从发送请求到负载均衡
8.Spring Cloud中负载均衡器概览
9.Spring Cloud中的负载均衡策略
10.Spring Cloud中的断路器Hystrix
11.Spring Cloud自定义Hystrix请求命令
12.Spring Cloud中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请求缓存的介绍,有问题欢迎留言讨论。