Netflix Hystrix - 编程式断路器

141 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


Hystrix:断路器

雪崩效应

微服务架构的项目中,服务和服务之间形成了一种链式的调用关系。

当其中某个服务突然遇到大量请求时,导致链条上其他服务负载过高,导致其他调用服务的链条出现问题,从而所有的项目可能都出现的问题。

雪崩效应的原因

服务提供者(Application Service):

重试加大流量:

服务调用者(Application Client):

\

防止雪崩

降级

超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值.

保证:服务出现问题整个项目还可以继续运行。


熔断

当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复。

通俗理解:熔断就是具有特定条件的降级,当出现熔断时在设定的时间内容就不在请求Application Service了。所以在代码上熔断和降级都是一个注解

保证:服务出现问题整个项目还可以继续运行。


请求缓存

提供了请求缓存。服务A调用服务B,如果在A中添加请求缓存,第一次请求后走缓存了,就不在访问服务B了,即使出现大量请求时,也不会对B产生高负载。

请求缓存可以使用Spring Cache实现。

保证:减少对Application Service的调用。

请求合并

提供请求合并。当服务A调用服务B时,设定在5毫秒内所有请求合并到一起,对于服务B的负载就会大大减少,解决了对于服务B负载激增的问题。

保证:减少对Application Service的调用。


隔离

隔离分为线程池隔离和信号量隔离。通过判断线程池或信号量是否已满,超出容量的请求直接降级,从而达到限流的作用。

Hystrix简介

在Spring Cloud中解决雪崩效应就是通过Spring Cloud Netflix Hystrix实现的。

Hystrix(中文:断路器)是Netflix开源的一款容错框架,同样具有自我保护能力。

Hystrix就是保证在高并发下即使出现问题也可以保证程序继续运行的一系列方案。作用包含两点:容错和限流。

在Spring cloud中处理服务雪崩效应,都需要依赖hystrix组件。在Application Client应用的pom文件中都需要引入下述依赖:

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

Netflix Hystrix编程式断路器

HystrixCommand & HystrixObservableCommand

Eureka Server启动

EUREKA-PROVIDER-RIBBON-FEIGN-API项目构建

EUREKA-PROVIDER-RIBBON-FEIGN-API-IMPL项目构建

EUREKA-PROVIDER-RIBBON-FEIGN-API-IMPL-BACKUP项目构建

EUREKA-CONSUMER-RIBBON-FEIGN-API-IMPL-HYSTRIX项目构建

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
## server
server.port=9091
## spring.application
spring.application.name=eureka-consumer-ribbon-feign-api-impl-hystrix
## eureka instance
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
## eureka client
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
package center.leon.eurekaconsumerribbonfeignapiimplhystrix.cache.controller;

import center.leon.common.web.response.RestResponse;
import center.leon.eurekacommon.entity.ProductEntity;
import center.leon.eurekaconsumerribbonfeignapiimplhystrix.cache.service.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.List;

/**
 * @author : Leon on XXM Mac
 * @since : create in 2022/7/19 7:35 PM
 */
@RestController
@RequestMapping(value = "/cache")
public class CacheController {

    @Autowired
    private CacheService cacheService;

    /**
     * @param id
     * @return
     */
    @GetMapping(value = "/product/{id}")
    public RestResponse cacheProductById(@PathVariable(value = "id") Long id) {
        ProductEntity productEntity = cacheService.cacheProductById(id);
        return RestResponse.success(productEntity);
    }

    /**
     * 请求合并
     *
     * @param ids
     * @return
     */
    @GetMapping(value = "/products/{ids}")
    public RestResponse cacheProductByIds(@PathVariable(value = "ids") String ids) {
        List<ProductEntity> productEntityList = cacheService.cacheProductByIds(ids);
        return RestResponse.success(productEntityList);
    }
}
package center.leon.eurekaconsumerribbonfeignapiimplhystrix.cache.service.impl;

import center.leon.eurekacommon.entity.ProductEntity;
import center.leon.eurekaconsumerribbonfeignapiimplhystrix.cache.service.CacheService;
import center.leon.eurekaconsumerribbonfeignapiimplhystrix.command.BatchGetByIdCommand;
import center.leon.eurekaconsumerribbonfeignapiimplhystrix.command.GetByIdCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import rx.Observable;
import rx.Observer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author : Leon on XXM Mac
 * @since : create in 2022/7/19 7:36 PM
 */
@Slf4j
@Service
public class CacheServiceImpl implements CacheService {

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public ProductEntity cacheProductById(Long productId) {
        GetByIdCommand getByIdCommand = new GetByIdCommand(productId, restTemplate);
        ProductEntity productEntity = getByIdCommand.execute();
        log.info("cacheProductById : {}", productEntity);
        return productEntity;
    }

    @Override
    public List<ProductEntity> cacheProductByIds(String ids) {
        List<ProductEntity> productEntityList = new ArrayList<>();
        BatchGetByIdCommand batchGetByIdCommand = new BatchGetByIdCommand(restTemplate,
                Arrays.stream(ids.split(",")).mapToLong(value -> Long.valueOf(value)).boxed().collect(Collectors.toList()));
        Observable<ProductEntity> observable = batchGetByIdCommand.observe();
        observable.subscribe(new Observer<ProductEntity>() {
            @Override
            public void onCompleted() {
                log.info("cacheProductByIds::onCompleted");
            }

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

            @Override
            public void onNext(ProductEntity productEntity) {
                log.info("cacheProductByIds::onNext : {}", productEntity);
                productEntityList.add(productEntity);
            }
        });
        for (; productEntityList.size() != 3; ) ;
        return productEntityList;
    }
}
package center.leon.eurekaconsumerribbonfeignapiimplhystrix.command;

import center.leon.eurekacommon.entity.ProductEntity;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

/**
 * @author : Leon on XXM Mac
 * @since : create in 2022/7/19 5:25 PM
 */
public class GetByIdCommand extends HystrixCommand<ProductEntity> {

    private Long productId;
    private RestTemplate restTemplate;


    public GetByIdCommand(Long productId, RestTemplate restTemplate) {
        // groupKey:服务名(相同服务用一个名称,如商品、用户等等)。默认值:getClass().getSimpleName()
        // 。在consumer里面为每个provider服务,设置group标识,一个group使用一个线程池。
        super(HystrixCommandGroupKey.Factory.asKey("GetByIdCommandGroup"));
        this.productId = productId;
        this.restTemplate = restTemplate;
    }


    @Override
    protected ProductEntity run() throws Exception {
        ResponseEntity<ProductEntity> forEntity = restTemplate.getForEntity("http://eureka" +
                "-provider-ribbon-feign-api-impl/product/facade/{0}", ProductEntity.class,
                productId);
        ProductEntity productEntity = forEntity.getBody();
        return productEntity;
    }

    @Override
    protected ProductEntity getFallback() {
        return new ProductEntity().setId(productId).setProductName("你怕不是个憨憨哟");
    }

}
package center.leon.eurekaconsumerribbonfeignapiimplhystrix.command;

import center.leon.eurekacommon.entity.ProductEntity;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixObservableCommand;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import rx.Observable;
import rx.Subscriber;
import rx.schedulers.Schedulers;

import java.util.List;

/**
 * @author : Leon on XXM Mac
 * @since : create in 2022/7/19 6:22 PM
 */
public class BatchGetByIdCommand extends HystrixObservableCommand<ProductEntity> {


    private RestTemplate restTemplate;
    private List<Long> productIdList;

    public BatchGetByIdCommand(RestTemplate restTemplate, List<Long> productIdList) {
        //
        super(HystrixCommandGroupKey.Factory.asKey("BatchGetByIdCommandGroup"));
        this.restTemplate = restTemplate;
        this.productIdList = productIdList;
    }

    @Override
    protected Observable<ProductEntity> construct() {
        // 创建主题,注册订阅者回调逻辑
        return Observable.create(new Observable.OnSubscribe<ProductEntity>() {
            @Override
            public void call(Subscriber<? super ProductEntity> subscriber) {
                try {
                    for (Long productId : productIdList) {
                        ResponseEntity<ProductEntity> forEntity = restTemplate.getForEntity("http" +
                                        "://eureka-provider-ribbon-feign-api-impl" + "/product" +
                                        "/facade" +
                                        "/{0}",
                                ProductEntity.class, productId);
                        ProductEntity productEntity = forEntity.getBody();
                        // 通知订阅者数据返回
                        subscriber.onNext(productEntity);
                    }
                    // 通知订阅者请求完成
                    subscriber.onCompleted();
                } catch (Exception e) {
                    // 通知订阅者发生异常
                    subscriber.onError(e);
                }
            }
        }).subscribeOn(Schedulers.io());
    }


}
package center.leon.eurekaconsumerribbonfeignapiimplhystrix.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @author : Leon on XXM Mac
 * @since : create in 2022/7/19 5:53 PM
 */
@Configuration
public class WebConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Command的四种调用方式

  1. HystrixCommand同步调用
// 本质是调用queue()方法返回Futrue对象,然后调用Future#get()方法死等。。。
// HystrixCommand 同步调用
ProductEntity productEntity = getByIdCommand.execute();
  1. HystrixCommand异步调用
// getByIdCommand仅仅是任务入队,立即返回Future对象。
// HystrixCommand 异步调用
Future<ProductEntity> productEntityFuture = getByIdCommand.queue();
for(;!productEntityFuture.isDone();){
    log.info("HystrixCommand 异步调用方式。 等待结果处理完成");
}
ProductEntity productEntity = productEntityFuture.get();
  1. HystrixObservableCommand同步调用
// 确保batchGetByIdCommand只有一个请求。
ProductEntity productEntity = batchGetByIdCommand.observe().toBlocking().single();
  1. HystrixObservableCommand异步调用
Observable<ProductEntity> observable = batchGetByIdCommand.observe();
observable.subscribe(new Observer<ProductEntity>() {
    @Override
    public void onCompleted() {
        log.info("cacheProductByIds::onCompleted");
    }

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

    @Override
    public void onNext(ProductEntity productEntity) {
        log.info("cacheProductByIds::onNext : {}", productEntity);
        productEntityList.add(productEntity);
    }
});
for (; productEntityList.size() != idsNum; ) {
    log.info("HystrixObservableCommand 异步调用方式。 等待结果处理完成");
}

observe() & toObservable()

observe()方法:会立即执行observable.subscribe(final Observer<? super T> observer)

toObservable()方法:不会立即执行observable.subscribe(final Observer<? super T> observer)