spring cloud alibaba开发笔记十(netflix Hystrix熔断降级)

本文详细介绍了Spring Cloud Hystrix的使用,包括其作为延迟和故障容忍库的作用、设计目标和实现机制。Hystrix通过隔离服务访问点、熔断和回退策略实现容错,并提供了断路器、舱壁模式和请求缓存等功能。文中展示了如何通过注解和编码方式配置HystrixCommand,以及如何使用HystrixDashboard进行监控。此外,还讨论了请求合并以提高服务并发能力,并演示了OpenFeign集成Hystrix的后备模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Hystrix是什么、设计目标是什么

◆Hystrix 是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互

◆Hystrix 通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现服务之间的容错

◆Hystrix 有四大设计目标

1.对客户端访问的延迟和故障进行保护和控制

2.在复杂的分布式系统中阻止级联故障

3.快速失败,快速恢复

4.兜底回退,尽可能优雅的降级

Hystrix是如何实现它的目标的

1.对依赖项(服务)进行包装代理,不直接与依赖项交互

2.调用超时时间允许自行设定,超时之后立刻熔断报错

3.每一个依赖项都在自己的空间内(线程池或信号量隔离),依赖项之间不存在干扰

4.请求依赖项失败后,可以选择出错或者是兜底回退

SpringCloud Netflix Hystrix的三种模式

◆断路器模式 :设置超时或者失败等熔断策略

◆后备策略模式:断路器模式触发之后,如果存在后备策略,则执行后备

◆Hystrix 舱壁模式的启发:货船为了进行防止漏水和火灾的扩散,会将货仓分隔为多个,当发生灾害时,将所在货仓进行隔离就可以降低整艘船的风险

舱壁模式功能就是:我们可以通过为各个服务分别指定线程池

使用Hystrix

1.添加依赖

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

2.在启动入口添加注解

@EnableCircuitBreaker    //启用hystrix

3.使用注解的方式,使用Hystrix

/**
 * <h1>使用 HystrixCommand 注解</h1>
 * */
@Slf4j
@Service
public class UseHystrixCommandAnnotation {

    private final NacosClientService nacosClientService;

    public UseHystrixCommandAnnotation(NacosClientService nacosClientService) {
        this.nacosClientService = nacosClientService;
    }

    @HystrixCommand(
            // 用于对 Hystrix 命令进行分组, 分组之后便于统计展示于仪表盘、上传报告和预警等等
            // 内部进行度量统计时候的分组标识, 数据上报和统计的最小维度就是 groupKey
            groupKey = "NacosClientService",
            // HystrixCommand 的名字, 默认是当前类的名字, 主要方便 Hystrix 进行监控、报警等
            commandKey = "NacosClientService",
            // 舱壁模式
            threadPoolKey = "NacosClientService",
            // 后备模式
            fallbackMethod = "getNacosClientInfoFallback",
            // 断路器模式
            commandProperties = {
                    // 超时时间, 单位毫秒, 超时进 fallback
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500"),
                    // 判断熔断的最少请求数, 默认是10; 只有在一定时间内请求数量达到该值, 才会进行成功率的计算
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
                    // 熔断的阈值默认值 50, 表示在一定时间内有50%的请求处理失败, 会触发熔断
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "10"),
            },
            // 舱壁模式
            threadPoolProperties = {
                    @HystrixProperty(name = "coreSize", value = "30"),
                    @HystrixProperty(name = "maxQueueSize", value = "101"),
                    @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
                    @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
                    // 在时间窗口中, 收集统计信息的次数; 在 1440ms 的窗口中一共统计 12 次
                    @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
                    // 时间窗口, 从监听到第一次失败开始计时
                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440")
            }
    )
    public List<ServiceInstance> getNacosClientInfo(String serviceId) {

        log.info("use hystrix command annotation to get nacos client info: [{}], [{}]",
                serviceId, Thread.currentThread().getName());
        return nacosClientService.getNacosClientInfo(serviceId);
    }

    /**
     * <h2>getNacosClientInfo 的兜底策略 - Hystrix 后备模式</h2>
     * */
    public List<ServiceInstance> getNacosClientInfoFallback(String serviceId) {

        log.warn("can not get nacos client, trigger hystrix fallback: [{}], [{}]",
                serviceId, Thread.currentThread().getName());
        return Collections.emptyList();
    }
}

使用编码的形式,使用Hystrix,默认使用线程池

/**
 * <h1>给 NacosClientService 实现包装</h1>
 * Hystrix 舱壁模式:
 *  1. 线程池
 *  2. 信号量: 算法 + 数据结构, 有限状态机
 * */
@Slf4j
public class NacosClientHystrixCommand extends HystrixCommand<List<ServiceInstance>> {

    /** 需要保护的服务 */
    private final NacosClientService nacosClientService;

    /** 方法需要传递的参数 */
    private final String serviceId;

    public NacosClientHystrixCommand(NacosClientService nacosClientService, String serviceId) {

        super(
                Setter.withGroupKey(
                                HystrixCommandGroupKey.Factory.asKey("NacosClientService"))
                        .andCommandKey(HystrixCommandKey.Factory.asKey("NacosClientHystrixCommand"))
                        .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("NacosClientPool"))
                        // 线程池 key 配置
                        .andCommandPropertiesDefaults(
                                HystrixCommandProperties.Setter()
                                        .withExecutionIsolationStrategy(THREAD) // 线程池隔离策略
                                        .withFallbackEnabled(true)  // 开启降级
                                        .withCircuitBreakerEnabled(true)    // 开启熔断器
                        )
        );

        // 可以配置信号量隔离策略
//        Setter semaphore =
//                Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("NacosClientService"))
//                .andCommandKey(HystrixCommandKey.Factory.asKey("NacosClientHystrixCommand"))
//                .andCommandPropertiesDefaults(
//                        HystrixCommandProperties.Setter()
//                        .withCircuitBreakerRequestVolumeThreshold(10)
//                        .withCircuitBreakerSleepWindowInMilliseconds(5000)
//                        .withCircuitBreakerErrorThresholdPercentage(50)
//                        .withExecutionIsolationStrategy(SEMAPHORE)  // 指定使用信号量隔离
//                        //.....
//                );

        this.nacosClientService = nacosClientService;
        this.serviceId = serviceId;
    }

    /**
     * <h2>要保护的方法调用写在 run 方法中</h2>
     * */
    @Override
    protected List<ServiceInstance> run() throws Exception {
        log.info("NacosClientService In Hystrix Command to Get Service Instance: [{}], [{}]",
                this.serviceId, Thread.currentThread().getName());
        return this.nacosClientService.getNacosClientInfo(this.serviceId);
    }

    /**
     * <h2>降级处理策略</h2>
     * */
    @Override
    protected List<ServiceInstance> getFallback() {

        log.warn("NacosClientService run error: [{}], [{}]",
                this.serviceId, Thread.currentThread().getName());
        return Collections.emptyList();
    }
}

Controller的调用方式

    private final NacosClientService nacosClientService;

    public HystrixController(NacosClientService nacosClientService) {
        this.nacosClientService = nacosClientService;
    }

    @GetMapping("/simple-hystrix-command")
    public List<ServiceInstance> getServiceInstanceByServiceId(
            @RequestParam String serviceId) throws Exception {

        // 第一种方式
        List<ServiceInstance> serviceInstances01 = new NacosClientHystrixCommand(
                nacosClientService, serviceId
        ).execute();    // 同步阻塞
        log.info("use execute to get service instances: [{}], [{}]",
                JSON.toJSONString(serviceInstances01), Thread.currentThread().getName());

        // 第二种方式
        List<ServiceInstance> serviceInstances02;
        Future<List<ServiceInstance>> future = new NacosClientHystrixCommand(
                nacosClientService, serviceId
        ).queue();      // 异步非阻塞
        // 这里可以做一些别的事, 需要的时候再去拿结果
        serviceInstances02 = future.get();
        log.info("use queue to get service instances: [{}], [{}]",
                JSON.toJSONString(serviceInstances02), Thread.currentThread().getName());

        // 第三种方式
        Observable<List<ServiceInstance>> observable = new NacosClientHystrixCommand(
                nacosClientService, serviceId
        ).observe();        // 热响应调用
        List<ServiceInstance> serviceInstances03 = observable.toBlocking().single();
        log.info("use observe to get service instances: [{}], [{}]",
                JSON.toJSONString(serviceInstances03), Thread.currentThread().getName());

        // 第四种方式
        Observable<List<ServiceInstance>> toObservable = new NacosClientHystrixCommand(
                nacosClientService, serviceId
        ).toObservable();        // 异步冷响应调用
        List<ServiceInstance> serviceInstances04 = toObservable.toBlocking().single();
        log.info("use toObservable to get service instances: [{}], [{}]",
                JSON.toJSONString(serviceInstances04), Thread.currentThread().getName());

        // execute = queue + get
        return serviceInstances01;
    }

使用编码的形式,使用Hystrix,默认使用信号量

/**
 * <h1>HystrixCommand, 隔离策略是基于信号量实现的</h1>
 * */
@Slf4j
public class NacosClientHystrixObservableCommand extends
        HystrixObservableCommand<List<ServiceInstance>> {

    /** 要保护的服务 */
    private final NacosClientService nacosClientService;

    /** 方法需要传递的参数 */
    private final List<String> serviceIds;

    public NacosClientHystrixObservableCommand(NacosClientService nacosClientService,
                                               List<String> serviceIds) {
        super(
                HystrixObservableCommand.Setter
                        .withGroupKey(HystrixCommandGroupKey
                                .Factory.asKey("NacosClientService"))
                        .andCommandKey(HystrixCommandKey
                                .Factory.asKey("NacosClientHystrixObservableCommand"))
                        .andCommandPropertiesDefaults(
                                HystrixCommandProperties.Setter()
                                        .withFallbackEnabled(true)          // 开启降级
                                        .withCircuitBreakerEnabled(true)    // 开启熔断器
                        )
        );

        this.nacosClientService = nacosClientService;
        this.serviceIds = serviceIds;
    }

    /**
     * <h2>要保护的方法调用写在这里</h2>
     * */
    @Override
    protected Observable<List<ServiceInstance>> construct() {

        return Observable.create(new Observable.OnSubscribe<List<ServiceInstance>>() {

            // Observable 有三个关键的事件方法, 分别是 onNext、onCompleted、onError
            @Override
            public void call(Subscriber<? super List<ServiceInstance>> subscriber) {
                try {
                    if (!subscriber.isUnsubscribed()) {
                        log.info("subscriber command task: [{}], [{}]",
                                JSON.toJSONString(serviceIds),
                                Thread.currentThread().getName());
                        serviceIds.forEach(
                                s -> subscriber
                                        .onNext(nacosClientService.getNacosClientInfo(s))
                        );
                        subscriber.onCompleted();
                        log.info("command task completed: [{}], [{}]",
                                JSON.toJSONString(serviceIds),
                                Thread.currentThread().getName());
                    }
                } catch (Exception ex) {
                    subscriber.onError(ex);
                }
            }
        });
    }

    /**
     * <h2>服务降级</h2>
     * */
    @Override
    protected Observable<List<ServiceInstance>> resumeWithFallback() {

        return Observable.create(new Observable.OnSubscribe<List<ServiceInstance>>() {
            @Override
            public void call(Subscriber<? super List<ServiceInstance>> subscriber) {

                try {
                    if (!subscriber.isUnsubscribed()) {
                        log.info("(fallback) subscriber command task: [{}], [{}]",
                                JSON.toJSONString(serviceIds),
                                Thread.currentThread().getName());
                        subscriber.onNext(Collections.emptyList());
                        subscriber.onCompleted();
                        log.info("(fallback) command task completed: [{}], [{}]",
                                JSON.toJSONString(serviceIds),
                                Thread.currentThread().getName());
                    }
                } catch (Exception ex) {
                    subscriber.onError(ex);
                }
            }
        });
    }
}

Controller的调用方式

    @GetMapping("/hystrix-observable-command")
    public List<ServiceInstance> getServiceInstancesByServiceIdObservable(
            @RequestParam String serviceId) {

        List<String> serviceIds = Arrays.asList(serviceId, serviceId, serviceId);
        List<List<ServiceInstance>> result = new ArrayList<>(serviceIds.size());

        NacosClientHystrixObservableCommand observableCommand =
                new NacosClientHystrixObservableCommand(nacosClientService, serviceIds);

        // 异步执行命令
        Observable<List<ServiceInstance>> observe = observableCommand.observe();

        // 注册获取结果
        observe.subscribe(
                new Observer<List<ServiceInstance>>() {

                    // 执行 onNext 之后再去执行 onCompleted
                    @Override
                    public void onCompleted() {
                        log.info("all tasks is complete: [{}], [{}]",
                                serviceId, Thread.currentThread().getName());
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(List<ServiceInstance> instances) {
                        result.add(instances);
                    }
                }
        );

        log.info("observable command result is : [{}], [{}]",
                JSON.toJSONString(result), Thread.currentThread().getName());
        return result.get(0);
    }

SpringCloud Netflix Hystrix请求缓存

Hystrix请求缓存的两种实现方式

◆编程(继承HystrixCommand ) 方式,重写getCacheKey方法即可

◆注解方式

编程(继承HystrixCommand ) 方式,重写getCacheKey方法即可

首先需要添加一个过滤器(这个过滤器都是需要的,注解开发也需要)

/**
 * <h1>初始化 Hystrix 请求上下文环境</h1>
 * */
@Slf4j
@Component
@WebFilter(
        filterName = "HystrixRequestContextServletFilter",
        urlPatterns = "/*",
        asyncSupported = true
)
public class HystrixRequestContextServletFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {

        // 初始化 Hystrix 请求上下文
        // 在不同的 context 中缓存是不共享的
        // 这个初始化是必须的
        HystrixRequestContext context = HystrixRequestContext.initializeContext();

        try {
            // 配置
            hystrixConcurrencyStrategyConfig();
            // 请求正常通过
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            // 关闭 Hystrix 请求上下文
            context.shutdown();
        }
    }

    /**
     * <h2>配置 Hystrix 的并发策略</h2>
     * */
    public void hystrixConcurrencyStrategyConfig() {

        try {

            HystrixConcurrencyStrategy target =
                    HystrixConcurrencyStrategyDefault.getInstance();
            HystrixConcurrencyStrategy strategy =
                    HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (strategy instanceof HystrixConcurrencyStrategyDefault) {
                // 如果已经就是我们想要配置的
                return;
            }

            // 将原来其他的配置保存下来
            HystrixCommandExecutionHook commandExecutionHook =
                    HystrixPlugins.getInstance().getCommandExecutionHook();
            HystrixEventNotifier eventNotifier =
                    HystrixPlugins.getInstance().getEventNotifier();
            HystrixMetricsPublisher metricsPublisher =
                    HystrixPlugins.getInstance().getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy =
                    HystrixPlugins.getInstance().getPropertiesStrategy();

            // 先重置, 再把我们自定义的配置与原来的配置写回去
            HystrixPlugins.reset();
            HystrixPlugins.getInstance().registerConcurrencyStrategy(target);
            HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
            HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
            HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
            HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);

            log.info("config hystrix concurrency strategy success");
        } catch (Exception ex) {
            log.error("Failed to register Hystrix Concurrency Strategy: [{}]",
                    ex.getMessage(), ex);
        }
    }
}

在启动入口需要添加注解:@ServletComponentScan,这样过滤器才会生效。

/**
 * <h1>带有缓存功能的 Hystrix</h1>
 * */
@Slf4j
public class CacheHystrixCommand extends HystrixCommand<List<ServiceInstance>> {

    /** 需要保护的服务 */
    private final NacosClientService nacosClientService;

    /** 方法需要传递的参数 */
    private final String serviceId;

    private static final HystrixCommandKey CACHED_KEY =
            HystrixCommandKey.Factory.asKey("CacheHystrixCommand");

    public CacheHystrixCommand(NacosClientService nacosClientService, String serviceId) {

        super(
                HystrixCommand.Setter
                .withGroupKey(HystrixCommandGroupKey
                        .Factory.asKey("CacheHystrixCommandGroup"))
                .andCommandKey(CACHED_KEY)
        );

        this.nacosClientService = nacosClientService;
        this.serviceId = serviceId;
    }

    @Override
    protected List<ServiceInstance> run() throws Exception {

        log.info("CacheHystrixCommand In Hystrix Command to get service instance:" +
                " [{}], [{}]", this.serviceId, Thread.currentThread().getName());
        return this.nacosClientService.getNacosClientInfo(this.serviceId);
    }

    @Override
    protected String getCacheKey() {
        return serviceId;
    }

    @Override
    protected List<ServiceInstance> getFallback() {
        return Collections.emptyList();
    }

    /**
     * <h2>根据缓存 key 清理在一次 Hystrix 请求上下文中的缓存</h2>
     * */
    public static void flushRequestCache(String serviceId) {

        HystrixRequestCache.getInstance(
                CACHED_KEY,
                HystrixConcurrencyStrategyDefault.getInstance()
        ).clear(serviceId);
        log.info("flush request cache in hystrix command: [{}], [{}]",
                serviceId, Thread.currentThread().getName());
    }
}

Controller

    @GetMapping("/cache-hystrix-command")
    public void cacheHystrixCommand(@RequestParam String serviceId) {

        // 使用缓存 Command, 发起两次请求
        CacheHystrixCommand command1 = new CacheHystrixCommand(
                nacosClientService, serviceId
        );
        CacheHystrixCommand command2 = new CacheHystrixCommand(
                nacosClientService, serviceId
        );

        List<ServiceInstance> result01 = command1.execute();
        List<ServiceInstance> result02 = command2.execute();
        log.info("result01, result02: [{}], [{}]",
                JSON.toJSONString(result01), JSON.toJSONString(result02));

        // 清除缓存
        CacheHystrixCommand.flushRequestCache(serviceId);

        // 使用缓存 Command, 发起两次请求
        CacheHystrixCommand command3 = new CacheHystrixCommand(
                nacosClientService, serviceId
        );
        CacheHystrixCommand command4 = new CacheHystrixCommand(
                nacosClientService, serviceId
        );

        List<ServiceInstance> result03 = command3.execute();
        List<ServiceInstance> result04 = command4.execute();
        log.info("result03, result04: [{}], [{}]",
                JSON.toJSONString(result03), JSON.toJSONString(result04));
    }

注解方式

@CacheResult:该注解用来标记请求命令返回的结果应该被缓存,它必须与@HystrixCommand注解结合使用(属性:cacheKeyMethod)

 @CacheRemove:该注解用来让请求命令的綬存失效,失效的缓存根据commandKey进行查找。(属性:commandKey, cacheKeyMethod)

@CacheKey:该注解用来在请求命令的参数上标记,使其作为cacheKey,如果没有使用此注解则会使用所有参数列表中的参数作为cacheKey(属性:value)

/**
 * <h1>使用注解方式开启 Hystrix 请求缓存</h1>
 * */
@Slf4j
@Service
public class CacheHystrixCommandAnnotation {
    private final NacosClientService nacosClientService;

    public CacheHystrixCommandAnnotation(NacosClientService nacosClientService) {
        this.nacosClientService = nacosClientService;
    }

    // 第一种 Hystrix Cache 注解的使用方法

    @CacheResult(cacheKeyMethod = "getCacheKey")
    @HystrixCommand(commandKey = "CacheHystrixCommandAnnotation")
    public List<ServiceInstance> useCacheByAnnotation01(String serviceId) {
        log.info("use cache01 to get nacos client info: [{}]", serviceId);
        return nacosClientService.getNacosClientInfo(serviceId);
    }

    @CacheRemove(commandKey = "CacheHystrixCommandAnnotation",
            cacheKeyMethod = "getCacheKey")
    @HystrixCommand
    public void flushCacheByAnnotation01(String cacheId) {
        log.info("flush hystrix cache key: [{}]", cacheId);
    }

    public String getCacheKey(String cacheId) {
        return cacheId;
    }

    // 第二种 Hystrix Cache 注解的使用方法(常用)
    @CacheResult
    @HystrixCommand(commandKey = "CacheHystrixCommandAnnotation")
    public List<ServiceInstance> useCacheByAnnotation02(@CacheKey String serviceId) {

        log.info("use cache02 to get nacos client info: [{}]", serviceId);
        return nacosClientService.getNacosClientInfo(serviceId);
    }

    @CacheRemove(commandKey = "CacheHystrixCommandAnnotation")
    @HystrixCommand
    public void flushCacheByAnnotation02(@CacheKey String cacheId) {
        log.info("flush hystrix cache key: [{}]", cacheId);
    }

    // 第三种 Hystrix Cache 注解的使用方法
    @CacheResult
    @HystrixCommand(commandKey = "CacheHystrixCommandAnnotation")
    public List<ServiceInstance> useCacheByAnnotation03(String serviceId) {

        log.info("use cache03 to get nacos client info: [{}]", serviceId);
        return nacosClientService.getNacosClientInfo(serviceId);
    }

    @CacheRemove(commandKey = "CacheHystrixCommandAnnotation")
    @HystrixCommand
    public void flushCacheByAnnotation03(String cacheId) {
        log.info("flush hystrix cache key: [{}]", cacheId);
    }
}

SpringCloud Netflix Hystrix 请求合并

使用Hystrix的请求合并,将多个请求merge为一个,提高服务的并发能力

 ◆适用场景:单个对象的查询并发数很高,服务提供方负载较高,就可以考虑使用请求合并

◆注意事项:

1.请求在代码中人为的设置了延迟时间,会降低请求的响应速度

2.可能会提高服务提供方的负载,因为返回List 结果数据量偏大

3.实现请求合并比较复杂

编程方式实现请求合并

service中编写批量请求的方法

    /**
     * <h2>提供给编程方式的 Hystrix 请求合并</h2>
     * */
    public List<List<ServiceInstance>> getNacosClientInfos(List<String> serviceIds) {

        log.info("request nacos client to get service instance infos: [{}]",
                JSON.toJSONString(serviceIds));
        List<List<ServiceInstance>> result = new ArrayList<>(serviceIds.size());

        serviceIds.forEach(s -> result.add(discoveryClient.getInstances(s)));
        return result;
    }

为批量请求编写降级策略等

/**
 * <h1>批量请求 Hystrix Command</h1>
 * */
@Slf4j
public class NacosClientBatchCommand extends HystrixCommand<List<List<ServiceInstance>>> {

    private final NacosClientService nacosClientService;
    private final List<String> serviceIds;

    protected NacosClientBatchCommand(
            NacosClientService nacosClientService, List<String> serviceIds
    ) {

        super(
                HystrixCommand.Setter.withGroupKey(
                        HystrixCommandGroupKey.Factory.asKey("NacosClientBatchCommand")
                )
        );

        this.nacosClientService = nacosClientService;
        this.serviceIds = serviceIds;
    }

    @Override
    protected List<List<ServiceInstance>> run() throws Exception {

        log.info("use nacos client batch command to get result: [{}]",
                JSON.toJSONString(serviceIds));
        return nacosClientService.getNacosClientInfos(serviceIds);
    }

    @Override
    protected List<List<ServiceInstance>> getFallback() {

        log.warn("nacos client batch command failure, use fallback");
        return Collections.emptyList();
    }
}

请求合并器

/**
 * <h1>请求合并器</h1>
 * */
@Slf4j
public class NacosClientCollapseCommand extends HystrixCollapser<List<List<ServiceInstance>>, List<ServiceInstance>, String> {

    private final NacosClientService nacosClientService;
    private final String serviceId;

    public NacosClientCollapseCommand(NacosClientService nacosClientService, String serviceId) {

        super(
                HystrixCollapser.Setter.withCollapserKey(
                        HystrixCollapserKey.Factory.asKey("NacosClientCollapseCommand")
                ).andCollapserPropertiesDefaults(
                        HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(300)
                )
        );

        this.nacosClientService = nacosClientService;
        this.serviceId = serviceId;
    }

    /**
     * <h2>获取请求中的参数</h2>
     * */
    @Override
    public String getRequestArgument() {
        return this.serviceId;
    }

    /**
     * <h2>创建批量请求 Hystrix Command</h2>
     * */
    @Override
    protected HystrixCommand<List<List<ServiceInstance>>> createCommand(
            Collection<CollapsedRequest<List<ServiceInstance>, String>> collection) {
        List<String> serviceIds = new ArrayList<>(collection.size());
        serviceIds.addAll(
                collection.stream()
                        .map(CollapsedRequest::getArgument)
                        .collect(Collectors.toList())
        );

        return new NacosClientBatchCommand(nacosClientService, serviceIds);
    }

    /**
     * <h2>响应分发给单独的请求</h2>
     * */
    @Override
    protected void mapResponseToRequests(List<List<ServiceInstance>> lists,
                                         Collection<CollapsedRequest<List<ServiceInstance>, String>> collection) {
        int count = 0;

        for (CollapsedRequest<List<ServiceInstance>, String> collapsedRequest : collection) {
            // 从批量响应集合中按顺序取出结果
            List<ServiceInstance> instances = lists.get(count++);
            // 将结果返回原 Response 中
            collapsedRequest.setResponse(instances);
        }
    }
}

controller

    /**
     * <h2>编程方式实现请求合并</h2>
     * */
    @GetMapping("/request-merge")
    public void requestMerge() throws Exception {

        // 前三个请求会被合并
        NacosClientCollapseCommand collapseCommand01 = new NacosClientCollapseCommand(
                nacosClientService, "e-commerce-nacos-client1");
        NacosClientCollapseCommand collapseCommand02 = new NacosClientCollapseCommand(
                nacosClientService, "e-commerce-nacos-client2");
        NacosClientCollapseCommand collapseCommand03 = new NacosClientCollapseCommand(
                nacosClientService, "e-commerce-nacos-client3");

        Future<List<ServiceInstance>> future01 = collapseCommand01.queue();
        Future<List<ServiceInstance>> future02 = collapseCommand02.queue();
        Future<List<ServiceInstance>> future03 = collapseCommand03.queue();

        future01.get();
        future02.get();
        future03.get();

        Thread.sleep(2000);

        // 过了合并的时间窗口, 第四个请求单独发起
        NacosClientCollapseCommand collapseCommand04 = new NacosClientCollapseCommand(
                nacosClientService, "e-commerce-nacos-client4");
        Future<List<ServiceInstance>> future04 = collapseCommand04.queue();
        future04.get();
    }

注解的方式实现请

service

    // 使用注解实现 Hystrix 请求合并

    @HystrixCollapser(
            batchMethod = "findNacosClientInfos",
            scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
            collapserProperties = {
                    @HystrixProperty(name = "timerDelayInMilliseconds", value = "300")
            }
    )
    public Future<List<ServiceInstance>> findNacosClientInfo(String serviceId) {

        // 系统运行正常, 不会走这个方法
        throw new RuntimeException("This method body should not be executed!");
    }

    @HystrixCommand
    public List<List<ServiceInstance>> findNacosClientInfos(List<String> serviceIds) {

        log.info("coming in find nacos client infos: [{}]", JSON.toJSONString(serviceIds));
        return getNacosClientInfos(serviceIds);
    }

 controller

    /**
     * <h2>注解的方式实现请求合并</h2>
     * */
    @GetMapping("/request-merge-annotation")
    public void requestMergeAnnotation() throws Exception {

        Future<List<ServiceInstance>> future01 = nacosClientService.findNacosClientInfo(
                "e-commerce-nacos-client1"
        );
        Future<List<ServiceInstance>> future02 = nacosClientService.findNacosClientInfo(
                "e-commerce-nacos-client2"
        );
        Future<List<ServiceInstance>> future03 = nacosClientService.findNacosClientInfo(
                "e-commerce-nacos-client3"
        );

        future01.get();
        future02.get();
        future03.get();

        Thread.sleep(2000);

        Future<List<ServiceInstance>> future04 = nacosClientService.findNacosClientInfo(
                "e-commerce-nacos-client4"
        );
        future04.get();
    }

OpenFeign集成Hystrix开启后备模式

◆在配置文件中开启Hystrix的熔断功能: feign.hystrix.enabled: true

◆@FeignClient 注解的fallback和fallbackFactory属性

        1. fallback和fallbackFactory都是用于配置响应回退,但是不可以同时使用

        2. fallbackFactory能够获取到OpenFeign调用抛出的异常        

首先要在@FeignClient注解里面添加fallback属性,如图

/**
 * <h1>与 Authority 服务通信的 Feign Client 接口定义</h1>
 * */
@FeignClient(
        contextId = "AuthorityFeignClient", value = "e-commerce-authority-center",
        fallback = AuthorityFeignClientFallback.class)
public interface AuthorityFeignClient {

    /**
     * <h2>通过 OpenFeign 访问 Authority 获取 Token</h2>
     * */
    @RequestMapping(value = "/ecommerce-authority-center/authority/token",
            method = RequestMethod.POST,
            consumes = "application/json", produces = "application/json")
    JwtToken getTokenByFeign(@RequestBody UsernameAndPassword usernameAndPassword);
}

fallback 的使用

添加后备实现

/**
 * <h1>AuthorityFeignClient 后备 fallback</h1>
 * */
@Slf4j
@Component
public class AuthorityFeignClientFallback implements AuthorityFeignClient {
    @Override
    public JwtToken getTokenByFeign(UsernameAndPassword usernameAndPassword) {

        log.info("authority feign client get token by feign request error " +
                "(Hystrix Fallback): [{}]", JSON.toJSONString(usernameAndPassword));
        return new JwtToken("qinyi");
    }
}

在接口/ecommerce-authority-center/authority/token访问不到的时候,就会返回后备结果

fallbackFactory的使用

/**
 * <h1>OpenFeign 集成 Hystrix 的另一种模式</h1>
 * */
@Slf4j
@Component
public class AuthorityFeignClientFallbackFactory
        implements FallbackFactory<AuthorityFeignClient> {

    @Override
    public AuthorityFeignClient create(Throwable throwable) {

        log.warn("authority feign client get token by feign request error " +
                "(Hystrix FallbackFactory): [{}]", throwable.getMessage(), throwable);

        return new AuthorityFeignClient() {

            @Override
            public JwtToken getTokenByFeign(UsernameAndPassword usernameAndPassword) {
                return new JwtToken("qinyi-factory");
            }
        };
    }
}

使用Hystrix监控面板监测客户端容错

◆Hystrix Dashboard是一个单独的应用,用来实时的对Hystrix的[使用情况」进行实时的监控

创建微服务e-commerce-hystrix-dashboard

添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>e-commerce-springcloud</artifactId>
        <groupId>com.taluohui.ecommerce</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>e-commerce-hystrix-dashboard</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <!-- 模块名及描述信息 -->
    <name>e-commerce-hystrix-dashboard</name>
    <description>Hystrix Dashboard</description>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- spring cloud alibaba nacos discovery 依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
    </dependencies>

    <!--
        SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
        SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
     -->
    <build>
        <finalName>${artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

添加配置文件

server:
  port: 9999
  servlet:
    context-path: /ecommerce-hystrix-dashboard

spring:
  application:
    name: e-commerce-hystrix-dashboard
  cloud:
    nacos:
      # 服务注册发现
      discovery:
        enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
        server-addr: 127.0.0.1:8848
        #        server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址
        namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6
        metadata:
          management:
            context-path: ${server.servlet.context-path}/actuator

hystrix:
  dashboard:
    proxy-stream-allow-list: "127.0.0.1"

# 暴露端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

添加启动项

/**
 * <h1>hystrix dashboard 入口</h1>
 * 127.0.0.1:9999/ecommerce-hystrix-dashboard/hystrix/
 * http://127.0.0.1:8000/ecommerce-nacos-client/actuator/hystrix.stream
 * */
@EnableDiscoveryClient
@SpringBootApplication
@EnableHystrixDashboard     // 开启 Hystrix Dashboard
public class HystrixDashboardApplication {

    public static void main(String[] args) {

        SpringApplication.run(HystrixDashboardApplication.class, args);
    }
}

访问127.0.0.1:9999/ecommerce-hystrix-dashboard/hystrix/,就可以进入监控界面

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值