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请求缓存的介绍,有问题欢迎留言讨论。
- 小程序实现原理解析
- EF 通过DataAnnotations配置属性和类型
- IntelliJ IDEA 中的版本控制介绍(上)
- EF基础知识小记七(拆分实体到多个表以及拆分表到多个实体)
- Android 进程回收之LowMemoryKiller原理篇
- 邮件发送功能开发
- Vuejs和其他前端框架的对比
- 详述 IntelliJ IDEA 中恢复代码的方法
- C# 通过IEnumberable接口和IEnumerator接口实现自定义集合类型foreach功能
- 微信小程序之picker组件
- 详述 IntelliJ IDEA 中恢复代码的方法「进阶篇」
- mac环境下mongodb的安装和使用
- C# 终极基类Object介绍
- EF基础知识小记五(一对多、多对多处理)
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Windows 技术篇-设置计划任务,每天自动关机
- Mycat 快速入门
- Python 技术篇-连接qq邮箱服务器,调用qq邮箱发送邮件实战演示,qq邮箱授权码开通方法
- 浅谈数据库集群方案
- SkyWalking - 实现微服务监控告警
- Actuator + Prometheus + Grafana搭建微服务监控平台
- Python 用smtplib库发邮件报错:[WinError 10061] 由于目标计算机积极拒绝,无法连接。解决办法
- python运算符
- Windows 技术篇-禁用windows更新服务,解决windows无法关闭更新问题,解决windows自己启用更新问题。
- 搭建 SkyWalking 服务(For ElasticSearch 7)
- 基于 SkyWalking 实现服务链路追踪
- Python 技巧篇-字符串灵活处理:字符串过滤、字符串拼接,字符串切片,特殊、超长字符串的处理实例演示
- Python 技巧篇-开头注释怎么写最好,开头注释需要包含什么,开头注释的重要性
- 在CentOS8下安装Python3和ansible
- 开发一个属于自己的Spring Boot Starter