博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
服务容错保护断路器Hystrix之六:缓存功能的使用
阅读量:6252 次
发布时间:2019-06-22

本文共 12859 字,大约阅读时间需要 42 分钟。

 

高并发环境下如果能处理好缓存就可以有效的减小服务器的压力,Java中有许多非常好用的缓存工具,比如Redis、EHCache等,当然在Spring Cloud的Hystrix中也提供了请求缓存的功能,我们可以通过一个注解或者一个方法来开启缓存,进而减轻高并发环境下系统的压力。

请求缓存的整个生命周期

下图关于是请求缓存的整个生命周期

缓存优势

  • 复用性:这里的复用性指的是代码复用性
  • 一致性:也就是常说的幂等性,不管请求几次,得到的结果应该都是一样的
  • 减少重复工作:由于请求缓存是在HystrixCommand的construct()或run()运行之前运行,所有可以有效减少线程的使用

适用场景

请求缓存的优势显而易见,但是也不是银弹。

在读少写多的场景就显得不太合适,对于读的请求,需要add缓存。对于增删改的请求,需要把缓存remove。在增加系统资源开销的同时,又很鸡肋。

所以一般适合读多写少的场景。似乎所有缓存机制都有这个局限性吧。

 

为了介绍Hystrix的缓存如何使用,先搭建一些服务做好准备工作:

准备工作

1、consul,window上通过consul agent -dev启动一个。详细见《》

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
{ 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只能限于当前线程。

 

转载地址:http://ksysa.baihongyu.com/

你可能感兴趣的文章
java多态 动态绑定_Java JVM 多态(动态绑定)
查看>>
jpa 去重_JPA 查询Distinct Join条件示例
查看>>
mysql date 索引性能_【转】MYSQL数据库时间字段INT,TIMESTAMP,DATETIME性能效率比较
查看>>
java 加密解密算法_用JAVA设计一个简单的加密、解密算法,用该算法来实现对数据的加密、解密...
查看>>
java 窗口最小化_Java使窗口最小化为图标
查看>>
hessian java php_hessian在PHP中的使用
查看>>
虚幻4能用java吗_如果用虚幻四引擎开发我的世界而非java 它会比现在取得更好的成绩吗?...
查看>>
JAVA常见告警怎么解决_JAVA 线上故障排查全套路
查看>>
java协程和线程_Kotlin中的线程和协程之间的区别
查看>>
微信开放平台授权java_解决微信等开放平台授权域名只能配置一个的问题
查看>>
mysql 去除a标签_CSS去除手机移动端链接标签a点击所带的背景颜色样式
查看>>
datagrid排序 java_easyUI 自定义排序datagrid
查看>>
sine之舞 java_JAVA程序实例:Sine之舞的游戏之Java版
查看>>
java实现建权授权_JAVA项目实现授权 (一)-Go语言中文社区
查看>>
java h5服务器推送事件_服务器推送事件的详细介绍
查看>>
python图像转字符画_Python 实现图片转字符画(动图也能转)
查看>>
php hash 解密,emlog使用PHP5.5自带password_hash()函数
查看>>
php让提交表单的数据保留,php基础教程--表单验证(必填、提交后数据保留)
查看>>
java类可选,类层次结构中的Java可选接口
查看>>
php 二维数组分页效率,PHP二维数组分页排序分页_PHP数组分页
查看>>