• 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数据一样,说明第二次是从缓存中拿的数据。

     微信公众号

     

     

  • 相关阅读:
    计算机网络--->6. 传输层(1)
    问题与解决方案
    现在的状态
    再议学习-一点新的感想
    再次剖析一下自己要做的事
    Not in a good mood.
    ASP.NET知识总结(7.状体保持)
    ASP.NET知识总结(6.一般处理程序动态处理图片(验证码、水印、缩略图))
    ASP.NET知识总结(5.文件上传 文件下载)
    ASP.NET知识总结(4.请求管道中的19个事件)
  • 原文地址:https://www.cnblogs.com/niugang0920/p/12194888.html
Copyright © 2020-2023  润新知