高并发环境下如果能处理好缓存就可以有效的减小服务器的压力,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. java-权限修饰符的区别

    说明:所谓访问权限,是指对象是否可以通过“.”运算符操作自己的变量或通过“.”运算符使用类中的方法. 1.Java中的四种访问修饰符:public.protected.default(无修饰符,默认) ...

  2. Python 面向对象(创建类和对象,面向对象的三大特性是指:封装、继承和多态,多态性)

    概念:                                                                                                 ...

  3. openresty 编译ngx_pagespeed 模块-docker 构建

    ngx_pagespeed 是一个很不错的web 优化模块,我们通过简单的配置就可以对于web页面的加载有很大的提升 ngx_pagespeed 依赖psol 模块 Dockerfile   # Do ...

  4. 自定义简单的模板引擎-JS模板引擎

    http://www.cnblogs.com/52fhy/p/5393673.html

  5. Java基础语法 第1节 Java语言发展和JDK安装

    一.Java发展历程 1995年5月23日,Java语言诞生: 1996年1月,第一个JDK-JDK1.0诞生: 1996年4月,10个最主要的操作系统供应商申明将在其产品中嵌入Java技术: 199 ...

  6. junit 测试quartz

    Junit本身是不支持普通的多线程测试的,这是因为Junit的底层实现上,是用System.exit退出用例执行的.JVM都终止了,在测试线程启动的其他线程自然也无法执行,JunitCore代码如下: ...

  7. idea+maven+springmvc+helloworld

    1.添加依赖,并在项目上添加Spring mvc框架的支持(add FrameWork Support): <dependency> <groupId>junit</gr ...

  8. go thrift报错问题--WriteStructEnd

    问题 go thrift开发过程中,多个goroutine共用一个client时,报错: panic: runtime error: index out of range goroutine 24 [ ...

  9. mysql日常处理

    http://blog.csdn.net/zengxuewen2045/article/details/52349731 https://github.com/enmotplinux/On-Site- ...

  10. xe5 android 手机上使用sqlite [转]

    在android手机上怎样使用sqlite数据库,这里用Navigator实现 增删改查. 1.新建firemonkey mobile application 2.选择blank applicatio ...