zoukankan      html  css  js  c++  java
  • Spring Cloud(5):Hystrix的使用

    熔断:类似生活中的保险丝,电流过大就会熔断

    降级:类似生活中的旅行,行李箱只有那么大,所以要抛弃一些非必需的物品

    熔断降级应用:

    某宝双十一商品下单,用户量巨大,于是考虑抛弃相关商品推荐等模块,确保该商品信息和下单功能通畅

    熔断和降级的区别以及联系:

    1.两者都是为了防止系统崩溃,提高可用性

    2.最终给用户的体验是某些功能暂时不可用

    3.服务熔断一般是由下游服务故障导致的,服务降级一般是从系统整体负荷考虑,由调用方控制

    Hystrix的使用:

    基于上一篇Feign的使用:https://www.cnblogs.com/xuyiqing/p/10869026.html

    订单服务Order-Service中加入依赖

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            </dependency>

    在启动类中加入注解

    package org.dreamtech.orderservice;
    
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    @EnableFeignClients
    @EnableCircuitBreaker
    public class OrderServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderServiceApplication.class, args);
        }
    
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }

    Controller层进行修改

    package org.dreamtech.orderservice.controller;
    
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import org.dreamtech.orderservice.service.ProductOrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @RestController
    @RequestMapping("/api/order")
    public class OrderController {
        private final ProductOrderService productOrderService;
    
        @Autowired
        public OrderController(ProductOrderService productOrderService) {
            this.productOrderService = productOrderService;
        }
    
        @RequestMapping("/save")
        @HystrixCommand(fallbackMethod = "saveOrderFail")
        public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId) {
            Map<String, Object> data = new HashMap<>();
            data.put("code", 0);
            data.put("data", productOrderService.save(userId, productId));
            return data;
        }
    
        private Object saveOrderFail(int userId, int productId) {
            Map<String, Object> msg = new HashMap<>();
            msg.put("code", -1);
            msg.put("msg", "请稍后重试");
            return msg;
        }
    }

    注意:saveOrderFail方法的参数必须和save的参数一致

    依次启动Eureka-Server->Product-Service->Order-Service

    访问:http://localhost:8781/api/order/save?user_id=1&product_id=4

    正常显示:

    {"code":0,"data":{"id":0,"productName":""iPhone4 data from port=8771"","tradeNo":"13933d59-34fd-473a-8dbe-f114bc9fd2b9","price":4444,"createTime":"2019-05-17T03:54:12.682+0000","userId":1,"userName":null}}

    这时候我关闭Product-Service,模拟熔断

    再次访问:http://localhost:8781/api/order/save?user_id=1&product_id=4

    显示:

    {"msg":"请稍后重试","code":-1}

    说明Hystrix配置和使用成功!

    Feign结合Hystrix的使用:

    配置Feign允许Hystrix

    feign:
      hystrix:
        enabled: true

    FeignClient加入fallback处理

    package org.dreamtech.orderservice.service;
    
    import org.dreamtech.orderservice.fallback.ProductClientFallback;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    @FeignClient(name = "product-service",fallback = ProductClientFallback.class)
    public interface ProductClient {
        @RequestMapping("/api/product/find")
        String findById(@RequestParam(value = "id")int id);
    }

    ProductClientFallback

    package org.dreamtech.orderservice.fallback;
    
    import org.dreamtech.orderservice.service.ProductClient;
    import org.springframework.stereotype.Component;
    
    @Component
    public class ProductClientFallback implements ProductClient {
        @Override
        public String findById(int id) {
            System.out.println("Feign调用Product-Service异常");
            return null;
        }
    }

    启动项目,访问:http://localhost:8781/api/order/save?user_id=1&product_id=3成功

    关闭Product-Service,访问http://localhost:8781/api/order/save?user_id=1&product_id=3

    显示

    {"msg":"请稍后重试","code":-1}

    并且在控制台打印

    Feign调用Product-Service异常

    服务异常报警通知:

    只是熔断和降级是不够了,还需要报警机制

    由于访问量较大,要做到只报警一次,那么需要进行标识

    通常实现原理基于缓存,比如Redis

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>

    配置

    spring:
      application:
        name: order-service
      redis:
        host: 127.0.0.1
        database: 0
        port: 6379
        timeout: 2000

    代码

    package org.dreamtech.orderservice.controller;
    
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import org.apache.commons.lang.StringUtils;
    import org.dreamtech.orderservice.service.ProductOrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    @RestController
    @RequestMapping("/api/order")
    public class OrderController {
        private final ProductOrderService productOrderService;
    
        private final StringRedisTemplate redisTemplate;
    
        @Autowired
        public OrderController(ProductOrderService productOrderService, StringRedisTemplate redisTemplate) {
            this.productOrderService = productOrderService;
            this.redisTemplate = redisTemplate;
        }
    
        @RequestMapping("/save")
        @HystrixCommand(fallbackMethod = "saveOrderFail")
        public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId) {
            Map<String, Object> data = new HashMap<>();
            data.put("code", 0);
            data.put("data", productOrderService.save(userId, productId));
            return data;
        }
    
        private Object saveOrderFail(int userId, int productId) {
    
            String saveOrderKey = "save-order";
            String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
    
            new Thread(() -> {
                if (StringUtils.isBlank(sendValue)) {
                    // TODO 由于没有短信发送接口,模拟发送短信
                    System.out.println("短信发送成功");
                    redisTemplate.opsForValue().set(saveOrderKey, "save-order-fail", 20, TimeUnit.SECONDS);
                } else {
                    System.out.println("已经发送过短信,20秒内不重复发送");
                }
            }).start();
    
    
            Map<String, Object> msg = new HashMap<>();
            msg.put("code", -1);
            msg.put("msg", "请稍后重试");
            return msg;
        }
    }

    测试

    启动项目,访问http://localhost:8781/api/order/save?user_id=1&product_id=3没有问题

    关闭Product-Service再访问,控制台打印:

    Feign调用Product-Service异常
    短信发送成功

    再次访问:

    Feign调用Product-Service异常
    已经发送过短信,20秒内不重复发送

    等待20秒访问:

    Feign调用Product-Service异常
    短信发送成功

    成功

    或者更进一步,拿到IP,发送短信

    package org.dreamtech.orderservice.controller;
    
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import org.apache.commons.lang.StringUtils;
    import org.dreamtech.orderservice.service.ProductOrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    @RestController
    @RequestMapping("/api/order")
    public class OrderController {
        private final ProductOrderService productOrderService;
    
        private final StringRedisTemplate redisTemplate;
    
        @Autowired
        public OrderController(ProductOrderService productOrderService, StringRedisTemplate redisTemplate) {
            this.productOrderService = productOrderService;
            this.redisTemplate = redisTemplate;
        }
    
        @RequestMapping("/save")
        @HystrixCommand(fallbackMethod = "saveOrderFail")
        public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId, HttpServletRequest request) {
            Map<String, Object> data = new HashMap<>();
            data.put("code", 0);
            data.put("data", productOrderService.save(userId, productId));
            return data;
        }
    
        private Object saveOrderFail(int userId, int productId,HttpServletRequest request) {
    
            String saveOrderKey = "save-order";
            String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
            String ip = request.getRemoteAddr();
    
            new Thread(() -> {
                if (StringUtils.isBlank(sendValue)) {
                    // TODO 由于没有短信发送接口,模拟发送短信
                    System.out.println("短信发送成功,紧急情况,用户下单失败!来自于IP:"+ip);
                    redisTemplate.opsForValue().set(saveOrderKey, "save-order-fail", 20, TimeUnit.SECONDS);
                } else {
                    System.out.println("已经发送过短信,20秒内不重复发送");
                }
            }).start();
    
    
            Map<String, Object> msg = new HashMap<>();
            msg.put("code", -1);
            msg.put("msg", "请稍后重试");
            return msg;
        }
    }

    Hystrix自定义配置:

    关闭超时时间(实际情况不要用)

    hystrix:
      command:
        default:
          execution:
            timeout:
              enabled: false

    修改超时时间为4秒:

    hystrix:
      command:
        default:
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 4000

    不过Feign默认超时时间少于4秒,所以对Feign也进行修改:

    feign:
      hystrix:
        enabled: true
      client:
        config:
          default:
            connectTimeout: 4000
            readTimeout: 4000

     感觉YML的配置真的不舒服,不如继续用PROPERTIES

    Hystrix-Dashboard:对Hystrix过程和结果的可视化界面

    Hystrix-Dashboard的使用:

    加入依赖:不只是Dashboard,还依赖于SpringBoot的监控Actuator

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>

    在启动类中加入注解:@EnableHystrixDashboard

    package org.dreamtech.orderservice;
    
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    @EnableFeignClients
    @EnableCircuitBreaker
    @EnableHystrixDashboard
    public class OrderServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderServiceApplication.class, args);
        }
    
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }

    启动后访问:http://localhost:8781/hystrix

    根据提示输入http://localhost:8781/actuator/hystrix.stream,发现报错:

    Unable to connect to Command Metric Stream.

    而且我如我直接访问http://localhost:8781/actuator/hystrix.stream也会显示404

    解决:配置文件

    management:
      endpoints:
        web:
          exposure:
            include: "*"

    原因:配置Actuator开启全部端点

    启动后访问:http://localhost:8781/hystrix

    根据提示输入http://localhost:8781/actuator/hystrix.stream

    或者直接访问:http://localhost:8781/actuator/hystrix.stream不会报错

  • 相关阅读:
    Rolling File Appender使用示例
    log4net生成dll文件
    看涨期权(call options)
    log4net file Appender使用示例
    log4net不能记录日志,IsErrorEnabled值为false
    C#委托
    打印事件处理顺序
    Zigbee、WiFi和433MHz无线技术
    log4net Tutorial
    安装服务出现异常
  • 原文地址:https://www.cnblogs.com/xuyiqing/p/10874654.html
Copyright © 2011-2022 走看看