高并发环境下如果能处理好缓存就可以有效的减小服务器的压力,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之六:缓存功能的使用的更多相关文章

  1. 服务容错保护断路器Hystrix之六:服务熔断和服务降级

    伴随着微服务架构被宣传得如火如荼,一些概念也被推到了我们面前(管你接受不接受),其实大多数概念以前就有,但很少被提的这么频繁(现在好像不提及都不好意思交流了).想起有人总结的一句话,微服务架构的特点就 ...

  2. 服务容错保护断路器Hystrix之七:做到自动降级

    从<高可用服务设计之二:Rate limiting 限流与降级>中的“自动降级”中,我们这边将系统遇到“危险”时采取的整套应急方案和措施统一称为降级或服务降级.想要帮助服务做到自动降级,需 ...

  3. 服务容错保护断路器Hystrix之二:Hystrix工作流程解析

    一.总运行流程 当你发出请求后,hystrix是这么运行的 红圈 :Hystrix 命令执行失败,执行回退逻辑.也就是大家经常在文章中看到的“服务降级”. 绿圈 :四种情况会触发失败回退逻辑( fal ...

  4. 服务容错保护断路器Hystrix之五:配置

    接着<服务容错保护断路器Hystrix之二:Hystrix工作流程解析>中的<2.8.关于配置>再列举重要的配置如下 一.hystrix在生产中的建议 1.保持timeout的 ...

  5. 服务容错保护断路器Hystrix之三:断路器监控(Hystrix Dashboard)-单体监控

    turbine:英 [ˈtɜ:baɪn] 美 [ˈtɜ:rbaɪn] n.汽轮机;涡轮机;透平机 一.Hystrix Dashboard简介 在微服务架构中为了保证程序的可用性,防止程序出错导致网络阻 ...

  6. 服务容错保护断路器Hystrix之一:入门示例介绍(springcloud引入Hystrix的两种方式)

    限流知识<高可用服务设计之二:Rate limiting 限流与降级> 在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元间通过服务注册与订阅的方式互相依赖.由于每个单元都在不同的 ...

  7. 服务容错保护断路器Hystrix之八:Hystrix资源隔离策略

    在一个基于微服务的应用程序中,您通常需要调用多个微服务完成一个特定任务.不使用舱壁模式,这些调用默认是使用相同的线程来执行调用的,这些线程Java容器为处理所有请求预留的.在高服务器请求的情况下,一个 ...

  8. 服务容错保护断路器Hystrix之四:断路器监控(Hystrix Dashboard)-turbine集群监控

    turbine 英[ˈtɜ:baɪn] n. 汽轮机; 涡轮机; 透平机; OK,上文我们看了一个监控单体应用的例子,在实际应用中,我们要监控的应用往往是一个集群,这个时候我们就得采取Turbine集 ...

  9. Spring Cloud(四):服务容错保护 Hystrix【Finchley 版】

    Spring Cloud(四):服务容错保护 Hystrix[Finchley 版]  发表于 2018-04-15 |  更新于 2018-05-07 |  分布式系统中经常会出现某个基础服务不可用 ...

随机推荐

  1. hdu2174 kiki's game 博弈

    Recently kiki has nothing to do. While she is bored, an idea appears in his mind, she just playes th ...

  2. 【BZOJ3992】【SDOI2015】序列统计

    数论劲啊 原题: 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S. 小C用这个生成器生成了许多这样的数列.但是小 ...

  3. 【shell编程】之基础知识-函数

    linux shell 可以用户定义函数,然后在shell脚本中可以随便调用. shell中函数的定义格式如下: [ function ] funname [()] { action; [return ...

  4. 一个天气的微服务springcloud

    1.开发环境 jdk8  和 gradle 4. ---------------搭建一个天气预报系统 1.bootstrap就是一堆样式文件,首先有html标签,然后加上各种样式以后就变得好看了2.j ...

  5. access-control-allow-origin

    when use vastinspector to check our  vast response ,it  tiped : "no 'access-control-allow-origi ...

  6. mysql之commit,transaction事物控制

    简单来说,transaction就是用来恢复为以前的数据. 举个例子,我想把今天输入到数据库里的数据在晚上的时候全部删除,那么我们就可以在今天早上的时候开始transaction事物,令autocom ...

  7. chrome自带调试工具介绍

    Chrome浏览器不仅可以调试页面.JS.请求.资源.cookie,还可以模拟手机进行调试等等,为开发者提供了很多方便,下面就介绍一下我常用到的调试技巧. 1.chrome浏览页面常用快捷键 Ctrl ...

  8. How To Use the AWK language to Manipulate Text in Linux

    https://www.digitalocean.com/community/tutorials/how-to-use-the-awk-language-to-manipulate-text-in-l ...

  9. C#更新程序设计

    更新程序设计 大致设想了两种更新方式,如下所示: 一种简单暴力,直接请求静态资源服务器上的文件 第二种考虑了网络传输不稳定时,中断的情况.再次启动更新时会检测本地文件下载多少了,然后接着上次中断的位置 ...

  10. DevExpress 控件使用菜单栏之BarManager

    DevExpress 开发的控件有很强的实力,不仅功能丰富,应用简便,而且界面华丽,更可方便定制.对于编程人员来说是个不错的选择.它的菜单栏控件更具代表,完全可以替代开发环境提供的基本控件,而让您编写 ...