9.Spring-Cloud-Hystrix之请求缓存(踩坑)

Posted 盲目的拾荒者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了9.Spring-Cloud-Hystrix之请求缓存(踩坑)相关的知识,希望对你有一定的参考价值。

          当系统的用户不断增长时, 每个微服务需要承受的并发压力越来越大。在分布式环境下,通常压力来自于对依赖服务的调用,因为请求依赖服务的资源需要通过通信来实现,这样的依赖方式比起进程内的调用方式引起一部分的性能损失,同时HTTP相比于其他高性能的通信协议在速度上没有任何优势,所以它有些类似于对数据库这样的外部资源进行读写操作,在高并发的情况下可能会成为系统的瓶颈。

       在高并发的场景之下,Hystrix中提供了请求缓存的功能,可以很方便的开启和使用请求缓存来优化系统,达到减轻高并发时的请求线程消耗、降低请求响应时间的效果。

具体如下:

一:服务消费者

1.pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>6.spring-cloud-hystrix-consumer</groupId>
<artifactId>hystrix-consumer</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cloud Maven Webapp</name>
<url>http://maven.apache.org</url>
<!--springboot采用1.5.x 对应springcloud版本为 Dalston -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath />
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.RELEASE</spring-cloud.version>
</properties>

     <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>

</dependency>

<!--引入ribbon 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<!-- 搞了好久踩的坑,就是在复制上面代码的时候 顺手把scope test复制了,其意思为紧参与junit类似的测试工作,所以一直报找不到服务或者或者拒绝连接 -->
<!-- <scope>test</scope> -->
</dependency>
<!--引入hystrix熔断器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!--引入hystrix dashboard(仪表盘)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
        <!--fastjson  -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.15</version>
</dependency>

</dependencies>

        <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 这样变成可执行的jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

 

技术图片


2.启动类

package com.niugang;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.client.RestTemplate;
/**
 * 负责服务的发现与消费
 * 
 * @author niugang
 *
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
/*
 * @SpringCloudApplication
 * 包含了
 * @SpringBootApplication
 * @EnableDiscoveryClient
 * @EnableCircuitBreaker
 * 着三个注解
 */

@ComponentScan
public class Application {
    //负载均衡
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {

SpringApplication.run(Application.class, args);
}
}

 

技术图片


3.controller

@RequestMapping(value = "/queryUser/{id}")
public Map queryUser(@PathVariable("id")String id) {
    
String queryUser1 = helloService.queryUser(id);
logger.info("第一次调用数据:{}",queryUser1);
String queryUser2 = helloService.queryUser(id);
logger.info("第二次调用数据:{}",queryUser2);
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("one", queryUser1);
hashMap.put("two", queryUser2);
return hashMap;

}
@RequestMapping(value = "/updateUser")
public String updateUser(Integer id,String username,String phone) {
return helloService.updateUser(id,username,phone);
}

 

技术图片

4.service

 

/**
* 查询用户信息
* 1.execution.isolation.thread.timeoutInMilliseconds:调用当前依赖服务,响应超过2000ms将执行降级服务处理,
* 
* 在配置文件中配置全局的超时时间:hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 
* 
* 2.@CacheResult 默认方法参数将作为缓存的key执行
* 也可通过String queryUser(@CacheKey("id") String id) 这样设置,而且用@CacheKey会报错,问题还未解决
* 也可通过@CacheResult(cacheKeyMethod="getKeyMethod")优先级比@CacheKey("id")高
*/
@CacheResult
@HystrixCommand(fallbackMethod = "queryUserBackMethod",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000"),
@HystrixProperty(name = "requestCache.enabled", value = "true") 
})
public String queryUser( String id) {


String body = restTemplate
.postForEntity("http://service-provide/queryUser/{1}", String.class, String.class,id)
.getBody();
return  body;


}

     /**
* commandKey用于找到适当的Hystrix命令,缓存应该被清除
*/
@CacheRemove(cacheKeyMethod = "getKeyMethod", commandKey = "queryUser")
@HystrixCommand(fallbackMethod = "queryUserBackMethod")
public String updateUser(Integer id, String username, String phone) {
User user = new User(id, username, phone);
return restTemplate.postForEntity("http://service-provide/updateUser", user, String.class).getBody();
}


/**
* key只支持String类型的
* 
* @param id
* @return
*/
public String getKeyMethod(String id) {
return id;

}

     /**
* 回退函数必须和他正常调用函数必须具有相同的
*/
public String queryUserBackMethod(String id, Throwable e) {
return "error1:" + e.getMessage();
}

 



5.filter   存放filter的包
技术图片
package com.niugang.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

@WebFilter(urlPatterns = "/*")
public class RequestCacheFilter implements javax.servlet.Filter {
private static Logger logger = LoggerFactory.getLogger(RequestCacheFilter.class);
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("初始化init filter...");
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
logger.info("执行filter");
/**
* 不初始化,会报如下错误
* Request caching is not available. Maybe you need to
* initialize the HystrixRequestContext
* 初始化是在filter中进行(官方建议),但是每一次初始化之前的缓存就失效了,所以要测缓存,就只能在controller中调用两次,
* 才能看到缓存的结果是否相同
*  在同一用户请求的上下文中,相同依赖服务的返回数据始终保持一致  ---《spring cloud 微服务实战》有争论
*/
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
chain.doFilter(request, response);
} finally {
context.shutdown();
}
}
public void destroy() {
// TODO Auto-generated method stub
}


}

 

 


5.config  存放配置文件的包

 

package com.niugang.config;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * 
 * @author niugang 扫描过滤器

 *
 */
@ServletComponentScan(basePackages="com.niugang.filter")
@Configuration
public class FilterCompentConfig {
}

 

技术图片

6.entity

 

package com.niugang.entity;
import java.io.Serializable;
/**
 * 用户实体
 * 
 * @author niugang
 *
 */
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String username;
private String phone;
private int  randNum;
//
public User() {
super();
}


public User(Integer id, String username, String phone) {
super();
this.id = id;
this.username = username;
this.phone = phone;
}


public Integer getId() {
return id;
}


public void setId(Integer id) {
this.id = id;
}


public String getUsername() {
return username;
}


public void setUsername(String username) {
this.username = username;
}


public String getPhone() {
return phone;
}


public void setPhone(String phone) {
this.phone = phone;
}


public int getRandNum() {
return randNum;
}


public void setRandNum(int randNum) {
this.randNum = randNum;
}

 


技术图片

二.服务提供者改动controller

package com.niugang.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
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 com.alibaba.fastjson.JSONObject;
import com.niugang.controller.entity.User;
@RestController
public class ComputeController {

private final Logger logger = LoggerFactory.getLogger(ComputeController.class);
public static List<User> list = new ArrayList<User>();
static {
User user1 = new User(1, "张三", "134565676776");
list.add(user1);
User user2 = new User(2, "李四", "134565676776");
list.add(user2);
User user3 = new User(3, "王五", "134565676776");
list.add(user3);
User user4 = new User(4, "赵六", "134565676776");
list.add(user4);
User user5 = new User(5, "田七", "134565676776");
list.add(user5);
}
@Autowired

private DiscoveryClient client;

       @RequestMapping(value = "/hello", method = RequestMethod.GET)
public String add() {
ServiceInstance serviceInstance = null;
// 方法已过时,不建议使用
// ServiceInstance instance = client.getLocalServiceInstance();
List<String> services = client.getServices();
if (!services.isEmpty() && services.size() > 0) {
String serviceId = services.get(0);
List<ServiceInstance> instances = client.getInstances(serviceId);
if (!instances.isEmpty() && instances.size() > 0) {
serviceInstance = instances.get(0);
logger.info(
"/hello, host:" + serviceInstance.getHost() + ", service_id:" + serviceInstance.getServiceId());
}
}


return "hello spring cloud" + serviceInstance.getHost();

}

/**
* 根据id获取用户信息
* 
* @param id
* @return
* @throws InterruptedException 
*/
@RequestMapping(value = "/queryUser/{id}", method = RequestMethod.POST)
public String queryUser(@PathVariable("id") Integer id) throws InterruptedException {
logger.info("query user info id:{}", id);
//------------start----------------------------//
//演示请求超时,当产生的随机数大于2000,消费者就会进行服务降级处理,因为请求超时了
int nextInt = new Random().nextInt(3000);
Thread.sleep(nextInt);
logger.info("query user info id:{},sleep:{}", id,nextInt);
//------------end----------------------------//
if (id == null) {
throw new RuntimeException("用户id不能为空");
}
User user = list.get(id - 1);

if (user == null) {
throw new RuntimeException("用户不存在");
}
user.setRandNum(new Random().nextInt(100));
return JSONObject.toJSONString(user);

}

     /**
     *更新用户信息
     * @param user
     * @return
     */
@RequestMapping(value = "/updateUser", method = RequestMethod.POST)
public String updateUser(User user) {
logger.info("update user info id:{}", user.getId());
try {
Integer id = user.getId();
User set = list.set(id - 1, user);
return JSONObject.toJSONString(set);
} catch (Exception e) {
throw new RuntimeException("更新失败");
}
}
}

 

技术图片


启动高可能注册中心(之前博客中有),启动服务提供者(之前博客中有),启动消费者。

一次浏览器请求,在controller中调用两次服务,randNum数据一样,说明第二次是从缓存中拿的数据。

                                                                               微信公众号: 

                                               技术图片技术图片?

                                                                             JAVA程序猿成长之路

                          分享资源,记录程序猿成长点滴。专注于Java,Spring,SpringBoot,SpringCloud,分布式,微服务。 

                     

以上是关于9.Spring-Cloud-Hystrix之请求缓存(踩坑)的主要内容,如果未能解决你的问题,请参考以下文章

HTTP之request请求

网络请求之GETPOST请求

SpringMVC之请求部分

HTTP协议之请求协议

Retrofit之请求参数

flask基础之请求处理核心机制