微服务–第4部分:使用Netflix Hystrix的Spring Cloud Breaker

本文探讨了在微服务架构中使用Spring Cloud和Netflix Hystrix实现断路器模式,以防止级联故障。介绍了如何配置Hystrix断路器,包括超时、后备方法和线程隔离策略,以及如何传播ThreadLocal变量和监控断路器状态。
摘要由CSDN通过智能技术生成

在微服务世界中,为了满足客户请求,一个微服务可能需要与其他微服务对话。 我们应该最小化这种对其他微服务的直接依赖,但是在某些情况下这是不可避免的。 如果微服务已关闭或无法正常运行,则问题可能会扩展到上游服务。 Netflix创建了实施电路断路器模式的 Hystrix库,以解决此类问题。 我们可以使用Spring Cloud Netflix Hystrix断路器来保护微服务免于级联故障。

使用Spring Boot和Spring Cloud的微服务

在这篇文章中,我们将学习:

  • 使用@HystrixCommand实现断路器模式
  • 如何传播ThreadLocal变量
  • 使用Hystrix仪表板监控断路器

实施Netflix Hystrix断路器模式

目录服务中,我们正在库存服务上调用REST端点以获取产品的库存水平。 如果库存服务中断了怎么办? 如果库存服务花费的时间太长而导致响应速度变慢,该怎么办呢? 我们希望有一个timeout并实现一些后备机制。

Hystrix起动器添加到目录服务。

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

要启用断路器, 在目录服务入口点类上添加@EnableCircuitBreaker批注。

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.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableCircuitBreaker
@SpringBootApplication
public class CatalogServiceApplication {

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

    public static void main(String[] args) {
        SpringApplication.run(CatalogServiceApplication.class, args);
    }
}

现在,我们可以在要应用超时和后备方法的任何方法上使用@HystrixCommand批注。

让我们创建InventoryServiceClient.java ,它将调用清单服务REST端点并通过回退实现应用@HystrixCommand

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.sivalabs.catalogservice.web.models.ProductInventoryResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.Optional;

@Service
@Slf4j
public class InventoryServiceClient {
    private final RestTemplate restTemplate;

    @Autowired
    public InventoryServiceClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @HystrixCommand(fallbackMethod = "getDefaultProductInventoryByCode")
    public Optional<ProductInventoryResponse> getProductInventoryByCode(String productCode)
    {
        ResponseEntity<ProductInventoryResponse> itemResponseEntity =
                restTemplate.getForEntity("http://inventory-service/api/inventory/{code}",
                        ProductInventoryResponse.class,
                        productCode);
        if (itemResponseEntity.getStatusCode() == HttpStatus.OK) {
            return Optional.ofNullable(itemResponseEntity.getBody());
        } else {
            log.error("Unable to get inventory level for product_code: " + productCode + ", StatusCode: " + itemResponseEntity.getStatusCode());
            return Optional.empty();
        }
    }

    @SuppressWarnings("unused")
    Optional<ProductInventoryResponse> getDefaultProductInventoryByCode(String productCode) {
        log.info("Returning default ProductInventoryByCode for productCode: "+productCode);
        ProductInventoryResponse response = new ProductInventoryResponse();
        response.setProductCode(productCode);
        response.setAvailableQuantity(50);
        return Optional.ofNullable(response);
    }
}
import lombok.Data;

@Data
public class ProductInventoryResponse {
    private String productCode;
    private int availableQuantity;
}

我们已经在使用@HystrixCommand( fallbackMethod =“ getDefaultProductInventoryByCode”)进行REST调用的位置注释了该方法,以便如果在特定时间限制内未收到响应,则该调用将超时并调用配置的fallback方法。 后备方法应在同一类中定义,并且应具有相同的签名。 在后备方法getDefaultProductInventoryByCode()中,我们将availableQuantity设置为50显然,此行为取决于业务需求

我们可以通过使用@HystrixProperty批注配置属性来自定义@HystrixCommand默认行为。

@HystrixCommand(fallbackMethod = "getDefaultProductInventoryByCode",
    commandProperties = {
       @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
       @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value="60")
    }
)
public Optional<ProductInventoryResponse> getProductInventoryByCode(String productCode)
{
    ....
}

可以在bootstrap.properties/yml文件中配置它们,而不是在代码中配置这些参数值,如下所示。

hystrix.command.getProductInventoryByCode.execution.isolation.thread.timeoutInMilliseconds=2000
hystrix.command.getProductInventoryByCode.circuitBreaker.errorThresholdPercentage=60

请注意,我们使用方法名称作为commandKey ,这是默认行为。 我们可以自定义commandKey名称,如下所示:

@HystrixCommand(commandKey = "inventory-by-productcode", fallbackMethod = "getDefaultProductInventoryByCode")
public Optional<ProductInventoryResponse> getProductInventoryByCode(String productCode)
{
    ...
}
hystrix.command.inventory-by-productcode.execution.isolation.thread.timeoutInMilliseconds=2000
hystrix.command.inventory-by-productcode.circuitBreaker.errorThresholdPercentage=60

您可以在https://github.com/Netflix/Hystrix/wiki/Configuration上找到所有可用的配置选项。

如何传播ThreadLocal变量

默认情况下,具有@HystrixCommand的方法将在不同的线程上执行,因为默认的execution.isolation.strategyExecutionIsolationStrategy.THREAD 。 所以,ThreadLocal的变量,我们调用@HystrixCommand方法不会@HystrixCommand方法中可用之前设置。

使ThreadLocal变量可用的一种选择是使用execution.isolation.strategy = SEMAPHORE

@HystrixCommand(fallbackMethod = "getDefaultProductInventoryByCode",
    commandProperties = {
        @HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE")
    }
)
public Optional<ProductInventoryResponse> getProductInventoryByCode(String productCode)
{
    ...
}

如果设置该属性execution.isolation.strategy 信号量 ,然后猬将使用信号量,而不是线程来限制并发线程父的那调用命令的数量。 您可以在https://github.com/Netflix/Hystrix/wiki/How-it-Works#isolation上了解有关隔离如何工作的更多信息。

使ThreadLocal变量在Hystrix命令方法中可用的另一种方法是实现我们自己的HystrixConcurrencyStrategy

假设您要传播一些CorrelationId设置为ThreadLocal变量。

public class MyThreadLocalsHolder {
    private static final ThreadLocal<String> CORRELATION_ID = new ThreadLocal();

    public static void setCorrelationId(String correlationId) {
        CORRELATION_ID.set(correlationId);
    }

    public static String getCorrelationId() {
        return CORRELATION_ID.get();
    }
}

让我们实现自己的HystrixConcurrencyStrategy

@Component
@Slf4j
public class ContextCopyHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {

    public ContextCopyHystrixConcurrencyStrategy() {
        HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
    }

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        return new MyCallable(callable, MyThreadLocalsHolder.getCorrelationId());
    }

    public static class MyCallable<T> implements Callable<T> {

        private final Callable<T> actual;
        private final String correlationId;

        public MyCallable(Callable<T> callable, String correlationId) {
            this.actual = callable;
            this.correlationId = correlationId;
        }

        @Override
        public T call() throws Exception {
            MyThreadLocalsHolder.setCorrelationId(correlationId);
            try {
                return actual.call();
            } finally {
                MyThreadLocalsHolder.setCorrelationId(null);
            }
        }
    }
}

现在,您可以在调用Hystrix命令之前设置CorrelationId ,并在Hystrix命令中访问CorrelationId。

ProductService.java

public Optional<Product> findProductByCode(String code) 
{
    ....
    String correlationId = UUID.randomUUID().toString();
    MyThreadLocalsHolder.setCorrelationId(correlationId);
    log.info("Before CorrelationID: "+ MyThreadLocalsHolder.getCorrelationId());
    Optional<ProductInventoryResponse> responseEntity = inventoryServiceClient.getProductInventoryByCode(code);
    ...
    log.info("After CorrelationID: "+ MyThreadLocalsHolder.getCorrelationId());
    ....
    
}

InventoryServiceClient.java

@HystrixCommand(fallbackMethod = "getDefaultProductInventoryByCode")
public Optional<ProductInventoryResponse> getProductInventoryByCode(String productCode)
{
    ...
    log.info("CorrelationID: "+ MyThreadLocalsHolder.getCorrelationId());
}

这只是如何将数据传播到Hystrix命令的一个示例。 同样,我们可以传递当前HTTP请求中可用的任何数据,例如,使用诸如RequestContextHolder等Spring组件。

Jakub Narloch写了一篇很好的文章,介绍如何传播请求上下文,甚至还创建了一个Spring Boot启动程序。 请查看他的博客https://jmnarloch.wordpress.com/2016/07/06/spring-boot-hystrix-and-threadlocals/和GitHub Repo https://github.com/jmnarloch/hystrix-context-spring- boot-starter

使用Hystrix仪表板监控断路器

Hystrix启动器添加到目录服务后,我们可以使用Actuator端点http:// localhost:8181 / actuator / hystrix.stream来获取电路状态作为事件流,假设目录服务在8181端口上运行。

Spring Cloud还提供了一个不错的仪表板来监视Hystrix命令的状态。
使用Hystrix Dashboard starter创建一个Spring Boot应用程序,并使用@EnableHystrixDashboard注释主入口点类。

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

假设我们在8788端口上运行Hystrix仪表板,然后转到http:// localhost:8788 / hystrix查看仪表板。

现在,在Hystrix仪表板主页中,输入http:// localhost:8181 / actuator / hystrix.stream作为流URL,并给Catalog Service作为标题,然后单击Monitor Stream按钮。

现在调用目录服务REST端点,该端点在内部调用清单服务REST端点,您可以看到Circuit状态以及成功调用的次数和发生失败的次数等。

不必为每个服务都有单独的仪表板,我们可以使用Turbine在单个仪表板中提供所有服务的统一视图。 有关更多详细信息,请参见http://cloud.spring.io/spring-cloud-static/Finchley.M7/single/spring-cloud.html#_turbine

您可以在https://github.com/sivaprasadreddy/spring-boot-microservices-series上找到本文的源代码。

翻译自: https://www.javacodegeeks.com/2018/03/microservices-part-4-spring-cloud-circuit-breaker-using-netflix-hystrix.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值