服务容错保护断路器Hystrix之六:缓存功能的使用
高并发环境下如果能处理好缓存就可以有效的减小服务器的压力,Java中有许多非常好用的缓存工具,比如Redis、EHCache等,当然在Spring Cloud的Hystrix中也提供了请求缓存的功能,我们可以通过一个注解或者一个方法来开启缓存,进而减轻高并发环境下系统的压力。
请求缓存的整个生命周期
下图关于是请求缓存的整个生命周期
缓存优势
- 复用性:这里的复用性指的是代码复用性
- 一致性:也就是常说的幂等性,不管请求几次,得到的结果应该都是一样的
- 减少重复工作:由于请求缓存是在HystrixCommand的construct()或run()运行之前运行,所有可以有效减少线程的使用
适用场景
请求缓存的优势显而易见,但是也不是银弹。
在读少写多的场景就显得不太合适,对于读的请求,需要add缓存。对于增删改的请求,需要把缓存remove。在增加系统资源开销的同时,又很鸡肋。
所以一般适合读多写少的场景。似乎所有缓存机制都有这个局限性吧。
为了介绍Hystrix的缓存如何使用,先搭建一些服务做好准备工作:
准备工作
1、consul,window上通过consul agent -dev启动一个。详细见《服务注册发现consul之一:consul介绍、安装、及功能介绍》
2、服务提供者,通过gradle构建
gradle配置:
configurations {
//compile.exclude group:'ch.qos.logback'
compile.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
compile 'org.springframework.cloud:spring-cloud-starter-feign'
compile 'org.springframework.cloud:spring-cloud-starter-hystrix-dashboard'
compile 'org.springframework.cloud:spring-cloud-starter-hystrix'
compile 'org.springframework.boot:spring-boot-starter-actuator'
compile 'org.springframework.boot:spring-boot-starter-logging:1.5.10.RELEASE'
compile 'io.springfox:springfox-swagger2:2.6.1'
compile 'io.springfox:springfox-swagger-ui:2.6.1'
compile 'com.github.xiaoymin:swagger-bootstrap-ui:1.6'
compile 'org.springframework.boot:spring-boot-starter-undertow'
compile 'org.apache.commons:commons-lang3:3.6'
compile 'org.springframework.data:spring-data-redis:1.8.1.RELEASE'
compile 'com.google.auth:google-auth-library-appengine:0.10.0'
compile 'com.google.auth:google-auth-library-oauth2-http:0.10.0'
compile 'com.google.cloud:google-cloud-storage:1.40.0'
compile 'com.google.cloud:google-cloud-bigquery:1.35.0'
compile 'com.google.cloud.bigtable:bigtable-client-core:1.0.0'
compile 'com.google.guava:guava:23.6-jre'
compile 'org.apache.httpcomponents:httpcore:4.4.8'
compile 'junit:junit:4.12' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
服务提供类:
package com.dxz.producter.web; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate; import com.dxz.producter.model.Book; import io.swagger.annotations.ApiParam; @RestController
@RequestMapping("/book")
public class BookProducter { @Autowired
private RestTemplate restTemplate; @RequestMapping(value = "/getbook5/{id}", method = RequestMethod.GET)
public Book getbook5(@ApiParam("id编号") @PathVariable("id") Integer id) {
System.out.println(">>>>>>>>/getbook5/" + id);
if (id == 1) {
return new Book(id, "《李自成》", 55, "姚雪垠", "人民文学出版社");
} else if (id == 2) {
return new Book(id, "中国文学简史", 33, "林庚", "清华大学出版社");
}
return new Book(id, "文学改良刍议", 33, "胡适", "无");
}
}
启动consul和service-producter,启动时增加端口参数,如下,启动2个服务:
D:\workspace\service-producter\build\libs>java -Dserver.port=8888 -jar service-producter-201809191443.jar
查看consul列表,service-producter已经成功注册了2台。
实现方式:
原生模式--通过方法重载(HystrixCommand类实现)开启缓存
如果我们使用了自定义Hystrix请求命令的方式来使用Hystrix,继承HystrixCommand后,重写getCacheKey()方法,该方法默认返回的是null,也就是不使用请求缓存功能。相同key的请求会使用相同的缓存。
package com.dxz.consumer.command; import org.springframework.web.client.RestTemplate; import com.dxz.consumer.model.Book;
import com.netflix.hystrix.HystrixCommand; 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://service-producter/book/getbook5/{id}", 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方法之前执行。我现在在服务提供者中打印一个日志,如下:
@RestController
@RequestMapping("/book")
public class BookProducter { @Autowired
private RestTemplate restTemplate; @RequestMapping(value = "/getbook5/{id}", method = RequestMethod.GET)
public Book getbook5(@ApiParam("id编号") @PathVariable("id") Integer id) {
System.out.println(">>>>>>>>/getbook5/" + id);
if (id == 1) {
return new Book(id, "《李自成》", 55, "姚雪垠", "人民文学出版社");
} else if (id == 2) {
return new Book(id, "中国文学简史", 33, "林庚", "清华大学出版社");
}
return new Book(id, "文学改良刍议", 33, "胡适", "无");
}
}
然后我们服务消费者的Controller中来执行这个请求,如下:
package com.dxz.consumer.web; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate; import com.dxz.consumer.command.BookCommand;
import com.dxz.consumer.model.Book;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; import io.swagger.annotations.ApiParam; @RestController
@RequestMapping("/consumer")
public class BookConsumer { @Autowired
private RestTemplate restTemplate; @RequestMapping(value = "/showbook5/{id}", method = RequestMethod.GET)
public Book getbook5(@ApiParam("id编号") @PathVariable("id") Long id) {
HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("commandKey");
HystrixRequestContext.initializeContext();
BookCommand bc1 = new BookCommand(
HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")).andCommandKey(commandKey),
restTemplate, id);
Book e1 = bc1.execute();
BookCommand bc2 = new BookCommand(
HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")).andCommandKey(commandKey),
restTemplate, id);
Book e2 = bc2.execute();
BookCommand bc3 = new BookCommand(
HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")).andCommandKey(commandKey),
restTemplate, id);
Book e3 = bc3.execute();
System.out.println("e1:" + e1);
System.out.println("e2:" + e2);
System.out.println("e3:" + e3);
return e1;
}
}
我连着发起三个相同的请求,我们来看看服务提供者的日志打印情况,注意,在服务请求发起之前,需要先初始化HystrixRequestContext。执行效果如下:
小伙伴们看到,上面是服务提供者打印出来的日志,下面是服务消费者打印出来的日志,发起了三个请求,但是服务提供者实际上只执行了一次,其他两次都使用了缓存数据。
有一种特殊的情况:如果我将服务提供者的数据修改了,那么缓存的数据就应该被清除,否则用户在读取的时候就有可能获取到一个错误的数据,缓存数据的清除也很容易,也是根据id来清除,方式如下:
//...
Book e1 = bc1.execute();
HystrixRequestCache.getInstance(commandKey, HystrixConcurrencyStrategyDefault.getInstance()).clear(String.valueOf(id));
//...
小伙伴们注意,这里我们执行完第一次请求之后,id为1的数据就已经被缓存下来了,然后我通过HystrixRequestCache中的clear方法将缓存的数据清除掉,这个时候如果我再发起请求,则又会调用服务提供者的方法,我们来看一下执行结果,如下:
小伙伴们看到,此时服务提供者的方法执行了两次,因为我在第一次请求结束后将id为1的缓存清除了。
通过注解开启缓存
当然,我们也可以通过注解来开启缓存,和缓存相关的注解一共有三个,分别是@CacheResult、@CacheKey和@CacheRemove,我们分别来看。
@CacheResult
@CacheResult方法可以用在我们之前的Service方法上,表示给该方法开启缓存,默认情况下方法的所有参数都将作为缓存的key,如下:
package com.dxz.consumer.web; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate; import com.dxz.consumer.model.Book;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; import io.swagger.annotations.ApiParam; @RestController
@RequestMapping("/consumer2")
public class BookConsumer2 { @Autowired
private RestTemplate restTemplate; @RequestMapping("/showbook6/{id}")
public Book getbook6(@ApiParam("id编号") @PathVariable("id") Long id) {
HystrixRequestContext.initializeContext();
//第一次发起请求
Book b1 = test6(id, "");
//参数和上次一致,使用缓存数据
Book b2 = test6(id, "");
//参数不一致,发起新请求
Book b3 = test6(id, "aa");
return b1;
} @CacheResult
@HystrixCommand
public Book test6(Long id, String aa) {
return restTemplate.getForObject("http://service-producter/book/getbook5/{id}", Book.class, id);
}
}
此时test6方法会自动开启缓存,默认所有的参数都将作为缓存的key,如果在某次调用中传入的两个参数和之前传入的两个参数都一致的话,则直接使用缓存,否则就发起请求,如下:
当然这里我们也可以在@CacheResult中添加cacheKeyMethod属性来指定返回缓存key的方法,注意返回的key要是String类型的,如下:
@CacheResult(cacheKeyMethod = "getCacheKey2")
@HystrixCommand
public Book test6(Long id, String aa) {
return restTemplate.getForObject("http://service-producter/book/getbook5/{id}", Book.class, id);
} public String getCacheKey2(Integer id) {
return String.valueOf(id);
}
controller层增加一个入口:
@RestController
@RequestMapping("/consumer2")
public class BookConsumer2 { @Autowired
private BookService bookService; @RequestMapping("/showbook6/{id}")
public Book getbook6(@ApiParam("id编号") @PathVariable("id") Long id) {
HystrixRequestContext.initializeContext();
if(1 == 2) {
//第一次发起请求
Book b1 = bookService.test6(id, "");
//参数和上次一致,使用缓存数据
Book b2 = bookService.test6(id, "");
//参数不一致,发起新请求
Book b3 = bookService.test6(id, "aa");
} else {
//第一次发起请求
Book b1 = bookService.test6(id);
//参数和上次一致,使用缓存数据
Book b2 = bookService.test6(id);
//参数不一致,发起新请求
Book b3 = bookService.test6(id);
} return null;
}
}
此时默认的规则失效。
@CacheKey
当然除了使用默认数据之外,我们也可以使用@CacheKey来指定缓存的key,如下:
@CacheResult
@HystrixCommand
public Book test6(@CacheKey Long id, Long bb) {
return restTemplate.getForObject("http://service-producter/book/getbook5/{id}", Book.class, id);
}
controller中增加入口
} else {
//第一次发起请求
Book b1 = bookService.test6(id, 0L);
//参数和上次一致,使用缓存数据
Book b2 = bookService.test6(id, 1L);
//参数不一致,发起新请求
Book b3 = bookService.test6(id, 2L);
}
验证结果 ,只调用一次。
这里我们使用@CacheKey注解指明了缓存的key为id,和bb这个参数无关,此时只要id相同就认为是同一个请求,而bb参数的值则不会作为判断缓存的依据(这里只是举例子,实际开发中我们的调用条件可能都要作为key,否则可能会获取到错误的数据)。如果我们即使用了@CacheResult中cacheKeyMethod属性来指定key,又使用了@CacheKey注解来指定key,则后者失效。
@CacheRemove
这个当然是用来让缓存失效的注解,用法也很简单,如下:
@CacheRemove(commandKey = "test6")
@HystrixCommand
public Book test7(@CacheKey Long id) {
return null;
}
注意这里必须指定commandKey,commandKey的值就为缓存的位置,配置了commandKey属性的值,Hystrix才能找到请求命令缓存的位置。举个简单的例子,如下:
@RequestMapping("/showbook7/{id}")
public Book getbook7(@ApiParam("id编号") @PathVariable("id") Long id) {
HystrixRequestContext.initializeContext();
//第一次发起请求
Book b1 = bookService.test6(id, 0L);
//清除缓存
bookService.test7(id);
//参数和上次一致,缓存被清除,重新发起请求
Book b2 = bookService.test6(id, 1L);
//参数不一致,发起新请求
Book b3 = bookService.test6(id, 2L); return null;
}
结果:
1.2 配置HystrixRequestContextServletFilter
通过servlet的Filter配置hystrix的上下文。
package com.dxz.hystrixdemo.filter; 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 {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
chain.doFilter(request, response);
} finally {
context.shutdown();
}
} @Override
public void init(FilterConfig filterConfig) throws ServletException { } @Override
public void destroy() { }
}
在不同context中的缓存是不共享的,还有这个request内部一个ThreadLocal,所以request只能限于当前线程。
服务容错保护断路器Hystrix之六:缓存功能的使用的更多相关文章
- 服务容错保护断路器Hystrix之六:服务熔断和服务降级
伴随着微服务架构被宣传得如火如荼,一些概念也被推到了我们面前(管你接受不接受),其实大多数概念以前就有,但很少被提的这么频繁(现在好像不提及都不好意思交流了).想起有人总结的一句话,微服务架构的特点就 ...
- 服务容错保护断路器Hystrix之七:做到自动降级
从<高可用服务设计之二:Rate limiting 限流与降级>中的“自动降级”中,我们这边将系统遇到“危险”时采取的整套应急方案和措施统一称为降级或服务降级.想要帮助服务做到自动降级,需 ...
- 服务容错保护断路器Hystrix之二:Hystrix工作流程解析
一.总运行流程 当你发出请求后,hystrix是这么运行的 红圈 :Hystrix 命令执行失败,执行回退逻辑.也就是大家经常在文章中看到的“服务降级”. 绿圈 :四种情况会触发失败回退逻辑( fal ...
- 服务容错保护断路器Hystrix之五:配置
接着<服务容错保护断路器Hystrix之二:Hystrix工作流程解析>中的<2.8.关于配置>再列举重要的配置如下 一.hystrix在生产中的建议 1.保持timeout的 ...
- 服务容错保护断路器Hystrix之三:断路器监控(Hystrix Dashboard)-单体监控
turbine:英 [ˈtɜ:baɪn] 美 [ˈtɜ:rbaɪn] n.汽轮机;涡轮机;透平机 一.Hystrix Dashboard简介 在微服务架构中为了保证程序的可用性,防止程序出错导致网络阻 ...
- 服务容错保护断路器Hystrix之一:入门示例介绍(springcloud引入Hystrix的两种方式)
限流知识<高可用服务设计之二:Rate limiting 限流与降级> 在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元间通过服务注册与订阅的方式互相依赖.由于每个单元都在不同的 ...
- 服务容错保护断路器Hystrix之八:Hystrix资源隔离策略
在一个基于微服务的应用程序中,您通常需要调用多个微服务完成一个特定任务.不使用舱壁模式,这些调用默认是使用相同的线程来执行调用的,这些线程Java容器为处理所有请求预留的.在高服务器请求的情况下,一个 ...
- 服务容错保护断路器Hystrix之四:断路器监控(Hystrix Dashboard)-turbine集群监控
turbine 英[ˈtɜ:baɪn] n. 汽轮机; 涡轮机; 透平机; OK,上文我们看了一个监控单体应用的例子,在实际应用中,我们要监控的应用往往是一个集群,这个时候我们就得采取Turbine集 ...
- Spring Cloud(四):服务容错保护 Hystrix【Finchley 版】
Spring Cloud(四):服务容错保护 Hystrix[Finchley 版] 发表于 2018-04-15 | 更新于 2018-05-07 | 分布式系统中经常会出现某个基础服务不可用 ...
随机推荐
- vue elementui 写的一个后台管理页面模板
https://github.com/PanJiaChen/vue-element-admin
- loopback 代码解析
loopback-boot boot(app, __dirname);//server.js var instructions = compile(options); execute(app, ins ...
- 洛谷P1337 【[JSOI2004]平衡点 / 吊打XXX】(模拟退火)
洛谷题目传送门 很可惜,充满Mo力的Mo拟退火并不是正解.不过这是一道最适合开始入手Mo拟退火的好题. 对模拟退火还不是很清楚的可以看一下 这道题还真和能量有点关系.达到平衡稳态的时候,物体的总能量应 ...
- golang 内存占用测量
web服务中加入如下 import ( "runtime" "time" "fmt" ) go func() { for { var m r ...
- Tomcat三种项目部署方式
第一种:直接将项目放在webapps目录下 材料:项目文件名hello,文件hello.html如图: 访问地址: 注释:hello 项目文件名 hello.html 访问的文件名字 第二种:修改c ...
- Cassandra基础3
cassandra读性能优化:1.禁用read repair每次读操作,无论读请求设置读一个节点还是多个节点,cassandra返回给客户端最新的数据后,都会后台对比所有副本的数据并对差异数据进行修复 ...
- 使用terraform-provider-s3 操作minio
尽管默认官方提供了s3 的操作,但是对于开源minio 无法支持,更多的是aws 的s3,社区提供了一个通用 s3 操作的provider(基于minio 的sdk) 环境准备 docker-comp ...
- Singer 学习三 使用Singer进行mongodb 2 postgres 数据转换
Singer 可以方便的进行数据的etl 处理,我们可以处理的数据可以是api 接口,也可以是数据库数据,或者 是文件 备注: 测试使用docker-compose 运行&&提供数据库 ...
- Unity 代码 学习
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A: B: using System; using System.Runtime.Compile ...
- php7 php-fpm 重启
PHP7中php.ini.php-fpm和www.conf的配置 http://www.tuicool.com/articles/NjmQNj6 php-fpm 关闭: kill -SIGINT ` ...