当系统的用户不断增长时, 每个微服务需要承受的并发压力越来越大。在分布式环境下,通常压力来自于对依赖服务的调用,因为请求依赖服务的资源需要通过通信来实现,这样的依赖方式比起进程内的调用方式引起一部分的性能损失,同时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数据一样,说明第二次是从缓存中拿的数据。
微信公众号