Spring Cloud 微服务之Hystrix(六)

22 篇文章 1 订阅
18 篇文章 0 订阅

一、什么是Spring Cloud Hystrix?

  • Spring Cloud Hystrix 是Spring Cloud Netflix 子项目的核心组件之一,具有服务容错及线程隔离等一系列服务保护功能。
  • 在微服务架构中,服务与服务之间通过远程调用的方式进行通信,一旦某个被调用的服务发生了故障,其依赖服务也会发生故障,此时就会发生故障的蔓延,最终导致系统瘫痪。
  • Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下不会导致整体服务失败,避免了级联故障,以提高分布式系统的弹性。
  • Hystrix 实现了断路器模式,当某个服务发生故障时,它会通过断路器的监控,给调用方返回一个备选的错误响应,而不是长时间的等待,这样就不会使得调用方由于长时间得不到响应而占用线程, 从而避免了故障在分布式系统中的蔓延,乃至雪崩。
  • Hystrix 具备服务降级、服务熔断、服务限流、线程隔离、请求缓存、请求合并及服务监控等强大功能。

二、什么是服务雪崩?

  • 多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”、如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
  • 对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒中内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

三、服务熔断示例

一般写在服务提供方,当服务方出现异常时熔断

  • 添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  • 添加配置

    • 启动类

      @EnableDiscoveryClient // 服务发现
      @EnableEurekaClient // 服务注册
      @EnableCircuitBreaker //服务熔断
      @SpringBootApplication
      public class HystrixServiceApplication {
          public static void main(String[] args) {
              SpringApplication.run(HystrixServiceApplication.class, args);
          }
      }
      
    • application.yml配置文件

      server:
        port: 8021
        servlet:
        	context-path: /
      spring:
        application:
          name: hystrix-service
      
      # eureka配置    
      eureka:
        instance:
        	instance-id: hystrix-service-8081
        client:
          register-with-eureka: true
          fetch-registry: false
          service-url:
            defaultZone: http://localhost:8010/eureka/
      
  • 业务接口实现类

    @Service
    public class UserServiceImpl implements UserService{
    
        @Override
        @HystrixCommand(fallbackMethod = "getUserByIdBreak")
        public User getUserById(Long id) {
            User user = null;
            if(id > 0 && id <= 10){
                user = new User(1L, "张三", "zhangsan");
            }else{
                throw new RuntimeException("未找到此用户");
            }
    
            return user;
        }
    
        public User getUserByIdBreak(Long id) {
            User user = new User(1L, "默认", "默认");;
    
            return user;
        }
    }
    
  • 测试使用

    访问: http://localhost:8010/get/10
    # 返回
    {"msg":"查询成功","type":"ok","content":{"id":1,"username":"张三","password":"zhangsan"}}
    
    访问: http://localhost:8010/get/100
    # 返回
    {"msg":"查询成功","type":"ok","content":{"id":1,"username":"默认","password":"默认"}}
    
  • @HystrixCommand注解参数说明

    • fallbackMethod:指定服务熔断处理方法;
    • ignoreExceptions:忽略某些异常,不发生服务熔断;
    • commandKey:命令名称,用于区分不同的命令;
    • groupKey:分组名称,Hystrix会根据不同的分组来统计命令的告警及仪表盘信息;
    • threadPoolKey:线程池名称,用于划分线程池。

四、服务降级示例

一般写在服务调用方,当服务方出现网络异常断开时降级

  • 添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>
    
  • 添加配置

    • 启动类

      @EnableEurekaClient
      @EnableCircuitBreaker
      @SpringBootApplication
      public class HystrixServiceApplication {
          public static void main(String[] args) {
              SpringApplication.run(HystrixServiceApplication.class, args);
          }
      }
      
    • application.yml配置文件

      server:
        port: 8080
        servlet:
          context-path: /
      
      spring:
        application:
          name: hystrix-client
      
      # eureka的配置
      eureka:
        client:
          register-with-eureka: false
          fetch-registry: true
          service-url:
            defaultZone: http://localhost:8010/eureka
      
      # 服务访问地址
      user-service-url: http://hystrix-service/
      
  • 控制器类

    @RestController
    public class UserController {
    
        @Autowired
        private RestTemplate restTemplate;
    
        private static String SERVICE_URL = "http://hystrix-service/";
    
        @GetMapping("/get/{id}")
        @HystrixCommand(fallbackMethod = "getUserHystrix")
        public Map<String, Object> getUser(@PathVariable Long id){
            Map result = restTemplate.getForObject(SERVICE_URL + "get/" + id, Map.class);
    
            return result;
        }
    
        public Map<String, Object> getUserHystrix(Long id){
            User user = new User(1L,"张三","zhangsan");
    
            Map<String, Object> result = new HashMap<>();
            result.put("type", "ok");
            result.put("msg", "查询成功(服务降级)");
            result.put("content", user);
    
            return result;
        }
    }
    
  • 测试使用

    访问: http://localhost:8010/get/10
    # 返回
    {"msg":"查询成功","type":"ok","content":{"id":1,"username":"张三","password":"zhangsan"}}
    
    关闭服务方再次访问: http://localhost:8010/get/10
    # 返回
    {"msg":"查询成功(服务降级)","type":"ok","content":{}}}
    

五、请求缓存示例

当系统并发量越来越大时,我们需要使用缓存来优化系统,达到减轻并发请求线程数,提供响应速度的效果。

  • 常用注解

    • @CacheResult

      开启缓存,默认所有参数作为缓存的key,cacheKeyMethod可以通过返回String类型的方法指定key;

    • @CacheKey

      指定缓存的key,可以指定参数或指定参数中的属性值为缓存key,cacheKeyMethod还可以通过返回String类型的方法指定;

    • @CacheRemove

      移除缓存,需要指定commandKey。

  • 使用缓存

    • 控制器类

      @RestController
      public class UserController {
          @Autowired
          private UserService userService;
      
          @GetMapping("/get/{id}")
          public Map<String, Object> getUser(@PathVariable Long id) {
              User user = userService.getUserById(id);
              User user = userService.getUserById(id);
              User user = userService.getUserById(id);
      
              Map<String, Object> result = new HashMap<>();
              result.put("type", "ok");
              result.put("msg", "查询成功");
              result.put("content", user);
      
              return result;
          }
      }
      
    • 业务接口

      @Service
      public class UserServiceImpl implements UserService{
          @Autowired
          private RestTemplate restTemplate;
      
          @Value("${user-service-url}")
          private static String SERVICE_URL;
          
          @Override
          @CacheResult(cacheKeyMethod = "getCacheKey")
          @HystrixCommand(fallbackMethod = "getUserByIdBreak",commandKey = "getUserById")
          public User getUserById(Long id) {
              User user=restTemplate.getForObject(SERVICE_URL + "get/" + id, User.class);
      
              return user;
          }
      
          public User getUserByIdBreak(Long id) {
              User user = new User(1L, "默认", "默认");;
      
              return user;
          }
          
          public String getCacheKey(Long id) {
              return String.valueOf(id);
          }
      }
      
    • 测试使用

      访问:http://localhost:8080/get/1
      

      在这里插入图片描述

      发现调用了三次getUserById方法,但是只打印了一次日志,说明有两次走的是缓存。
      
  • 移除缓存

    • 控制器类

      @RestController
      public class UserController {
          @Autowired
          private UserService userService;
      
          @GetMapping("/get/{id}")
          public Map<String, Object> getUser(@PathVariable Long id) {
              User user = userService.getUserById(id);
              User user = userService.removeKey(id);
              User user = userService.getUserById(id);
      
              Map<String, Object> result = new HashMap<>();
              result.put("type", "ok");
              result.put("msg", "查询成功");
              result.put("content", user);
      
              return result;
          }
      }
      
    • 业务接口

      @Service
      public class UserServiceImpl implements UserService{
          @Autowired
          private RestTemplate restTemplate;
      
          @Value("${user-service-url}")
          private static String SERVICE_URL;
          
          /**
           * 查询时使用缓存
           */
          @Override
          @CacheResult(cacheKeyMethod = "getCacheKey")
          @HystrixCommand(fallbackMethod = "getUserByIdBreak",commandKey = "getUserById")
          public User getUserById(Long id) {
              User user=restTemplate.getForObject(SERVICE_URL + "get/" + id, User.class);
      
              return user;
          }
      
          public User getUserByIdBreak(Long id) {
              User user = new User(1L, "默认", "默认");;
      
              return user;
          }
          
          /**
           * 删除时移除缓存
           */
          @CacheRemove(commandKey = "getUserById", cacheKeyMethod = "getCacheKey")
      	@HystrixCommand
          public User removeKey(Long id) {
              User user = restTemplate.postForObject(SERVICE_URL + "delete/" + id, null);
              
              return user;
          }
          
          public String getCacheKey(Long id) {
              return String.valueOf(id);
          }
      }
      
    • 测试使用

      访问:http://localhost:8080/get/1
      

      在这里插入图片描述

      发现调用了两次getUserById方法

  • 缓存中的问题

    在缓存使用过程中,我们需要在每次使用缓存的请求前后对HystrixRequestContext进行初始化和关闭,否则会出现异常: java.lang.IllegalStateException: Request caching is not available.

    我们通过使用过滤器,在每个请求前后初始化和关闭HystrixRequestContext来解决该问题

    @Component
    @WebFilter(urlPatterns = "/*",asyncSupported = true)
    public class HystrixRequestContextFilter implements Filter {
        @Override
        public void doFilter(
                ServletRequest servletRequest, 
                ServletResponse servletResponse, 
                FilterChain filterChain) throws IOException, ServletException {
            
            HystrixRequestContext context = HystrixRequestContext.initializeContext();
            
            try {
                filterChain.doFilter(servletRequest, servletResponse);
            } finally {
                context.close();
            }
        }
    }
    

六、请求合并示例

微服务系统中的服务间通信,需要通过远程调用来实现,随着调用次数越来越多,占用线程资源也会越来越多。Hystrix中提供了@HystrixCollapser用于合并请求,从而达到减少通信消耗及线程数量的效果。

  • 注解说明

    • batchMethod:用于设置请求合并的方法
    • collapserProperties:请求合并属性,用于控制实例属性,有很多
    • timerDelayInMilliseconds:collapserProperties中的属性,用于控制每隔多少时间合并一次请求
  • 案例演示

    • 控制器类

      @GetMapping("/testCollapser")
      public CommonResult testCollapser() throws ExecutionException, InterruptedException {
          Future<User> future1 = userService.getUserFuture(1L);
          Future<User> future2 = userService.getUserFuture(2L);
          future1.get();
          future2.get();
          
          ThreadUtil.safeSleep(200);
          Future<User> future3 = userService.getUserFuture(3L);
          future3.get();
          
          return new CommonResult("操作成功", 200);
      }
      
    • 业务接口

      @HystrixCollapser(
          batchMethod = "getUserByIds",
          collapserProperties = {
          	@HystrixProperty(name = "timerDelayInMilliseconds", value = "100")
      	}
      )
      public Future<User> getUserFuture(Long id) {
          return new AsyncResult<User>(){
              @Override
              public User invoke() {
                  CommonResult commonResult 
                      = restTemplate.getForObject(Url+"/user/"+id, CommonResult.class);
                  Map data = (Map) commonResult.getData();
                  User user = BeanUtil.mapToBean(data,User.class,true);
      
                  return user;
              }
          };
      }
      
      @HystrixCommand
      public List<User> getUserByIds(List<Long> ids) {
          CommonResult commonResult = restTemplate.getForObject(
              Url+"/user/getUserByIds?ids="+CollUtil.join(ids,","),CommonResult.class);
          
          return (List<User>) commonResult.getData();
      }
      
      
    • 测试使用

      访问:http://localhost:8080/testCollapser
      

      在这里插入图片描述

      由于我们设置了100毫秒进行一次请求合并,前两次被合并,最后一次自己单独合并了

七、服务监控示例

Hystrix Dashboard 是Spring Cloud中查看Hystrix实例执行情况的一种仪表盘组件,支持查看单个实例和查看集群实例

利用 Hystrix Dashboard 和 Spring Boot Admin 实现对服务状态的可视化监控

  • 监控端添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>
    
  • 监控端添加配置

    • 启动类

      @SpringBootApplication
      @EnableHystrixDashboard
      public class HystrixDashboardApplication {
          public static void main(String[] args) {
              SpringApplication.run(HystrixDashboardApplication.class, args);
          }
      }
      
    • application.yml配置文件

      server:
        port: 8090
        servlet:
          context-path: /
      
      spring:
        application:
          name: hystrix-dashboard
      
  • 服务端添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>
    
  • 服务端添加配置

    • 启动类

      @EnableEurekaClient
      @EnableCircuitBreaker
      @SpringBootApplication
      public class HystrixServiceApplication {
          public static void main(String[] args) {
              SpringApplication.run(HystrixServiceApplication.class, args);
          }
          
          @Bean
          public ServletRegistrationBean getServlet() {
              HystrixMetricsStreamServlet streamServlet 
                  = new HystrixMetricsStreamServlet();
              ServletRegistrationBean registrationBean 
                  = new ServletRegistrationBean(streamServlet);
              
              registrationBean.setLoadOnStartup(1);
              registrationBean.addUrlMappings("/actuator/hystrix.stream");
              registrationBean.setName("HystrixMetricsStreamServlet");
              
              return registrationBean;
          }
      }
      
  • 测试使用

    访问:http://localhost:8090/hystrix
    

    在这里插入图片描述
    在这里插入图片描述

  • 图表解读

    小球代表该实例健康状态及流量情况,颜色越显眼,表示实例越不健康,小球越大,表示实例流量越大

    曲线表示Hystrix实例的实时流量变化

    在这里插入图片描述

八、常用配置

  • 全局配置

    hystrix:
      #用于控制HystrixCommand的行为
      command: 
        default:
          execution:
            isolation:
              #控制HystrixCommand的隔离策略
              strategy: THREAD 
              thread:
                #配置HystrixCommand执行的超时时间,执行超过该时间会进行服务降级处理
                timeoutInMilliseconds: 1000 
                #配置HystrixCommand执行超时的时候是否要中断
                interruptOnTimeout: true 
                #配置HystrixCommand执行被取消的时候是否要中断
                interruptOnCancel: true 
              timeout:
                #配置HystrixCommand的执行是否启用超时时间
                enabled: true 
              semaphore:
                #当使用信号量隔离策略时,用来控制并发量的大小,超过该并发量的请求会被拒绝
                maxConcurrentRequests: 10 
          fallback:
            #用于控制是否启用服务降级
            enabled: true 
          #用于控制HystrixCircuitBreaker的行为
          circuitBreaker: 
            #用于控制断路器是否跟踪健康状况以及熔断请求
            enabled: true 
            #超过该请求数的请求会被拒绝
            requestVolumeThreshold: 20 
            #强制打开断路器,拒绝所有请求
            forceOpen: false 
            #强制关闭断路器,接收所有请求
            forceClosed: false 
          requestCache:
            #用于控制是否开启请求缓存
            enabled: true 
            
      #用于控制HystrixCollapser的执行行为      
      collapser: 
        default:
          #控制一次合并请求合并的最大请求数
          maxRequestsInBatch: 100 
          #控制多少毫秒内的请求会被合并成一个
          timerDelayinMilliseconds: 10 
          #控制合并请求是否开启缓存
          requestCache:
            enabled: true 
            
      #用于控制HystrixCommand执行所在线程池的行为      
      threadpool: 
        default:
          #线程池的核心线程数
          coreSize: 10 
          #线程池的最大线程数,超过该线程数的请求会被拒绝
          maximumSize: 10 
          #用于设置线程池的最大队列大小
          maxQueueSize: -1 
          #用于设置线程池队列的拒绝阀值
          queueSizeRejectionThreshold: 5
    
  • 实例配置

    hystrix:
      command:
      	# 将default换成HystrixComrnandKey(对应@HystrixCommand中的commandKey属性)
        HystrixComandKey: 
          execution:
            isolation:
              strategy: THREAD
      collapser:
        #将default换成HystrixCollapserKey(对应@HystrixCollapser注解中的collapserKey属性)
        HystrixCollapserKey: 
          maxRequestsInBatch: 100
      threadpool:
        #将default换成HystrixThreadPoolKey(对应@HystrixCommand中的threadPoolKey属性)
        HystrixThreadPoolKey: 
          coreSize: 10
    

【源码地址】:GitHub

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小吉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值