当前位置 博文首页 > 文章内容

    layman的博客:Spring Cloud入门系列(十一)- 服务熔断与降级之Hystrix(已停更,建议切换到Sentinel)

    作者:shunshunshun18 栏目:未分类 时间:2021-10-27 20:26:05

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    前言

    服务降级,既可以配置在客户端,也可以配置在服务端,需要根据具体的业务需求,进行灵活配置。

    本文模拟的情况,是在服务端80进行配置。

    服务降级

    服务降级,是通过注解 @HystrixCommand 来实现的。

    /**
         * 模拟超时操作
         * fallbackMethod:服务降级后的回调方法(超时异常or运行异常都会触发)
         * commandProperties:
         *      isolation.thread.timeoutInMilliseconds:配置HystrixCommand执行的超时时间,单位为毫秒。
         * 本类的配置:该方法如果执行超过三秒钟没有返回,将不再继续等待,转而执行paymentTimeOutHandler方法)
         * @param id
         * @return String
         */
        @Override
        @HystrixCommand(fallbackMethod = "paymentTimeOutHandler", commandProperties = {
                @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
        })
        public String paymentTimeOut(Integer id) {
            int timeNumber = 5000;
            //timeNumber =  10/0;
            try {
                TimeUnit.MILLISECONDS.sleep(timeNumber);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "线程名称: " + Thread.currentThread().getName() +
                    " ,方法名:paymentTimeOut, id: " + id + " ,超时了~ 耗时:" + timeNumber + "毫秒";
        }
        /**
        * =========================== 服务降级 ===========================
        * 服务降级的处理方法
        * paymentTimeOutHandler与paymentTimeOut的参数列表要对应上,否则会报错
        */
        public String paymentTimeOutHandler(Integer id) {
            return "线程名称: " + Thread.currentThread().getName() +
                    "  ,系统繁忙系统报错 ,paymentInfo_TimeOutHandler  ,请稍后再试 ,id: " + id + "\t " + "o(╥﹏╥)o";
        }
    
    • tips:服务降级方法的参数列表,需要与业务的方法中的参数列表保持一致

    可以看到,凡是存在隐患的业务方法,都需要配置服务降级。

    如果每个方法,都要配置服务降级,如果由100个方法,1000个方法呢。

    因此,引申出一个问题——能不能针对业务,配置一个全局的fallback方法,由他统一管理服务降级?

    全局服务降级配置

    全局服务降级配置,是通过在类上添加注解 @DefaultProperties 指定

    @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
    public class OpenFeignOrderController {
    	/**
    	* 模拟异常
        * HystrixCommand注解没有配置属性,走全局fallback方法
        */
        @GetMapping("/global/fallback")
        @HystrixCommand
        public String feignGlobalFallback(){
            String result = null;
            return result.toString();
        }
    	/**
         * 全局fallback 降级方法
         */
        public String payment_Global_FallbackMethod() {
            return "Global异常处理信息,请稍后再试,o(╥﹏╥)o";
        }
    }
    

    到了这一步,我们又发现了一个让人难受的地方,虽然每个业务都配置了全局的降级方法。

    但是业务逻辑和服务降级,耦合在了一起。

    代码非常的不优雅,如何解决?

    全局服务降级(解耦)

    package com.banana.springcloud.service;
    
    import com.banana.springcloud.service.fallback.OpenFeignOrderFallbackService;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    /**
     * FeignClient注解中的value属性指定了要调用的{服务}名
     * @author layman
     * @date 2021/1/18
     * fallback为value指定的服务名,提供统一的服务降级处理类OpenFeignOrderFallbackService。
     *      如果访问正常,不会走fallback指定的OpenFeignOrderFallbackService类,而是直接访问。
     *      如果访问异常,需要服务降级,就会走fallback指定的OpenFeignOrderFallbackService。
     *          A:如果是feignSuccess方法出现异常,那么就会去OpenFeignOrderFallbackService.class找对应的feignSuccess方法,处理fallback(方法名需一一对应)
     */
    @Component
    @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = OpenFeignOrderFallbackService.class)
    public interface OpenFeignOrderService {
    
        /**
         * 模拟成功访问
         */
        @GetMapping(value = "/payment/hystrix/success/{id}")
        String feignSuccess(@PathVariable("id") Long id);
        /**
         * 模拟openFeign访问超时
         */
        @GetMapping("/payment/hystrix/timeout/{id}")
        String feignTimeout(@PathVariable("id") Long id);
    }
    

    服务熔断

    服务熔断的定义在这里插入图片描述
    服务熔断的架构图:
    在这里插入图片描述
    代码演示(服务熔断的代码,放在服务端8001)

    PaymentServiceImpl

    /**
         * =========================== 服务熔断 ===========================
         * 服务的降级->进而熔断->恢复调用链路(会回复调用链路,而不是永久熔断)
         * 本类的配置详解:如果在10秒钟的时间窗口期内,8次请求失败率达到60%,触发熔断,5秒钟(默认)后,进入半开状态,尝试放行请求
         */
        @Override
        @HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallback", commandProperties = {
                @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //是否开启断路器
                @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "8"), //请求次数,默认为20
                @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //时间窗口期,默认为10秒
                @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),//失败率达到多少后触发熔断,默认为50
        })
        public String paymentCircuitBreaker(Integer id) {
            if (id < 0) {
                throw new RuntimeException("******id 不能为负数");
            }
            //不带-的UUID
            String serialNumber = IdUtil.simpleUUID();
            return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber;
        }
    

    @HystrixCommand注解中的参数配置

    都在com.netflix.hystrix.HystrixCommandProperties这个类里

    有兴趣的同学,可以去围观一下。

    测试服务熔断

    服务端8001进行自测。

    PaymentController

    package com.banana.springcloud.controller;
    
    import com.banana.springcloud.service.PaymentService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    /**
     * @author layman
     * @date 2021/1/18
     */
    @RestController
    @Slf4j
    @RequestMapping("/payment")
    public class PaymentController {
    /**
         * 测试服务熔断
         * @param id
         * @return
         */
        @GetMapping("/hystrix/circuit/{id}")
        public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
            String result = paymentService.paymentCircuitBreaker(id);
            log.info("------------result: " + result);
            return result;
        }
    }
    

    模拟的逻辑是,如果id是负数,抛出异常,如果是正数,则放行。
    在这里插入图片描述

    服务熔断的开启和关闭条件

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

    服务熔断的调用流程

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

    服务限流

    详情请参见:Alibaba的sentinel

    cs