无可否认,在过去几年中,像Docker和Kubernetes这样的技术,彻底改变了我们对软件开发和部署方式。断路器模式是在微服务架构中广泛采用的那些模式之一。我们将比较实现它的两种不同方法的优缺点:Hystrix和Istio。
微服务同步通信的核心问题
想象一个非常简单的微服务架构,包括:
- 后端服务
- 前端服务
让我们假设后端和前端通过同步HTTP调用进行通信。
客户端C1并C2调用前端来检索一些信息。由于前端没有所有必需的数据,因此它会调用后端来获取缺失的部分。
但由于网络通信,可能会发生很多事情:
- 前端和后端之间出现网络故障
- 后端可能是因为一个错误的宕机
- 后端服务依赖的数据库宕机
而按照墨菲定律(“凡是可能出错就会出错”),前端和后端之间的通信失败是迟早的事。
在这种情况下唯一合理的解决方案是快速失败: 应该让前端知道后端出现问题,并立即将故障返回给自己的客户端。
断路器模式
在电路领域中,断路器是设计用于保护电路的自动操作的电气开关。其基本功能是在检测到故障后中断电流。然后可以在故障解决后重置(手动或自动)以恢复正常操作。
应用于上述超时问题的设计模式。它背后的流程非常简单:
- 如果呼叫失败,请将失败呼叫的数量增加1
- 如果失败的呼叫数超过某个阈值,请打开电路
- 如果电路处于打开状态,则立即返回错误或默认响应
- 如果电路开路且经过一段时间,则半开电路
- 如果电路半开,下一次呼叫失败,请再次打开
- 如果电路半开,下一次通话成功,请将其关闭
Istio断路器
Istio是服务网格,是微服务应用程序的可配置基础设施层。它使服务实例之间的通信变得灵活,可靠和快速,并提供服务发现,负载平衡,加密,身份验证和授权,对断路器模式的支持以及其他功能。
Istio的控制平面在底层集群管理平台上提供了一个抽象层,例如Kubernetes,Mesos等,并且需要以这种方式管理您的应用程序。
作为其核心,Istio使用边车容器模式:这是一个位于应用程序实例前面的Envoy代理实例,以及管理它们的工具Pilot。这种代理策略有许多优点:
- HTTP,gRPC,WebSocket和TCP流量的自动负载平衡。
- 通过丰富的路由规则,重试,故障转移和故障注入,对流量行为进行细粒度控制。
- 可插入的策略层和配置API,支持访问控制,速率限制和配额。
- 群集中所有流量的自动度量标准,日志和跟踪,包括群集入口和出口。
- 通过强大的基于身份的身份验证和授权,在群集中实现安全的服务到服务通信。
由于对后端的出口调用通过Envoy代理,因此很容易检测到它们何时超时。然后代理可以拦截进一步的调用并立即返回,有效地快速失败。特别是,这使得断路器图案能够以黑盒方式操作。
配置Istio断路器
正如我们所说,Istio在您选择的集群管理平台上构建,并且需要通过它来部署您的应用程序。根据以下模型,Kubernetes 实现断路器模式:通过一个 DestinationRule或更具体的路径策略TrafficPolicy调用 - >OutlierDetection:
参数如下:
consecutiveErrors: 在断路器打开前的返回5xx的数量。
interval:断路器检查分析之间的时间间隔。
baseEjectionTime:最短打开时间。电路将保持等于最小喷射持续时间和断路打开次数的乘积。
maxEjectionPercent:负载平衡池中可以弹出的上游服务的最大主机百分比。
与前面标准的断路器相比,有两个主要偏差:
- 没有半开状态这样的事情。但是,断路器保持打开的持续时间取决于被叫服务之前失败的次数。不断失效的服务将导致断路器的打开持续时间越来越长。
- 在基本模式中,有一个叫做应用程序(后端)。在更现实的生产设置中,可能会在负载均衡器后面部署同一应用程序的多个实例。有些实例可能会失败,有些可能会失效,而且由于Istio也扮演负载均衡器的角色,它能够跟踪失败的实例并将它们从负载平衡池中弹出删除,直到某一点:maxEjectionPercent的角色属性将保留实例池中的一小部分。
断路器Istio接近是黑盒子。它需要一个高视点支架,并且只能在出现问题时打开断路。另一方面,它设置起来非常简单,并且不需要任何底层代码知识,并且可以将其配置为事后想法。
Hystrix断路器
Hystrix是一个最初由Netflix提供的开源Java库。它是一个延迟和容错库,旨在隔离对远程系统,服务和第三方库的访问点,停止级联故障,并在复杂的分布式系统中实现弹性,在这些系统中,故障是不可避免的。
Hystrix有许多功能,包括:
- 通过第三方客户端库访问(通常通过网络)访问的依赖项,防止延迟和故障。
- 防止复杂分布式系统中的级联故障。
- 快速失败并迅速恢复。
- 在可能的情况下,后退并优雅地降级。
- 实现近实时监控,警报和操作控制。
当然,断路器模式在这些功能中占有一席之地。因为Hystrix是一个库,它以白盒方式实现它。
Resilience4J
Netflix最近宣布已停止开发Hystrix库,转而采用不太知名的Resilience4J项目。
即使客户端代码可能有点不同,Hystrix和Resilience4J之间的方法也是类似的。
Hystrix断路器示例
考虑电子商务Web应用程序的情况。该应用程序的体系结构由不同的微服务构成,每个服务都基于业务功能:
显示目录项时,将查询定价/报价微服务的价格。如果中间通信断开,将不会发回任何价格,也无法订购任何东西。
从商业角度来看,任何停机时间不仅会对品牌的感知产生影响,还会降低销售额。尽管价格并不完全正确,但大多数销售策略仍倾向于出售。实现此销售策略的解决方案可以是在可用时缓存定价/报价服务返回的价格,并在服务停止时返回缓存价格。
Hystrix通过提供断路器实现允许该方法,该电路断路器实现允许在电路断开时进行回退。
这是Hystrix模型的简化类图:
魔术发生在HystrixCommand方法run()和getFallback():
- run()是实际代码,例如从报价服务中获取价格
- getFallabck()当断路器打开时获得后备结果,例如返回缓存价格
这可以转换为以下代码,使用Spring RestTemplate:
public class FetchQuoteCommand extends HystrixCommand<Double> {
private final UUID productId; // 1
private final RestTemplate template; // 2
private final Cache<UUID, Double> cache; // 3
public FetchQuoteCommand(UUID productId,
RestTemplate template,
Cache<UUID, Double> cache) {
super(HystrixCommandGroupKey.Factory.asKey("GetQuote")); // 4
this.template = template;
this.cache = cache;
this.productId = productId;
}
@Override
protected Double run() {
Double quote = template.getForObject("https://acme.com/api/quote/{id}", // 5
Double.class,
productId);
cache.put(productId, quote); // 6
return quote;
}
@Override
protected Double getFallback() {
return cache.get(productId); // 7
}
}
这需要一些解释:
- 该命令包装了产品的id,模仿为a UUID。
- Spring RestTemplate用于进行REST调用。任何其他选择都可以。
- 共享JCache实例,用于在服务可用时存储引号。
- Hystrix命令需要组密钥,以便在需要时将它们组合在一起。这是Hystrix的另一个特性,超出了本文的范围。有兴趣的读者可以阅读Hystrix wiki中的命令组。
- 执行对引用服务的调用。如果失败,则启动Hystrix断路器流量。
- 如果调用成功,则将返回的引用缓存在JCache共享实例中。
- 在getFallback()当断路器打开时调用。在这种情况下,从缓存中获取引用。
Hystrix wiki具有更高级的示例, 例如 后备本身就是一个需要执行的命令。
(banq注:更简单Springcloud应用:)
@HystrixCommand(fallbackMethod = "createProduct")
public Product getProduct(@RequestParam String productId) {
...
}
private Product createProduct(String productId) {
Product product = new Product();
product.setId("999999");
product.setName("网络问题");
return product;
}
将Hystrix与Spring Cloud集成
虽然上面的代码有效,但每次引用时都需要创建一个Hystrix命令对象。
Spring Cloud是一个构建在Spring Boot之上的库(它本身构建在Spring框架之上),它提供了与Spring的完美集成。它允许在处理Hystrix命令对象的实例化时,只注释所需的回退方法:
public class FetchQuoteService {
private final RestTemplate template;
private final Cache<UUID, Double> cache;
public SpringCloudFetchQuoteCommand(RestTemplate template,
Cache<UUID, Double> cache) {
this.template = template;
this.cache = cache;
}
@HystrixCommand(fallbackMethod = "getQuoteFromCache") // 1
public Double getQuoteFor(UUID productId) { // 2
Double quote = template.getForObject("https://acme.com/api/quote/{id}", // 3
Double.class,
productId);
cache.put(productId, quote); // 4
return quote;
}
public Double getQuoteFromCache(UUID productId) { // 5
return cache.get(productId);
}
}
- 该方法应注明@HystrixCommand。该fallbackMethod元素引用了回退方法。显然,这将通过反射来处理,并且不是类型安全的 - 毕竟这是一个字符串。
- Spring Cloud Hystrix允许在方法调用时传递产品的id参数。与上面的简单Hystrix命令相比,这允许具有通用服务对象。Hystrix命令的创建由Spring Cloud在运行时处理。
- 核心逻辑不会改变。
- 同样,缓存过程保持不变。
- 后备方法是常规方法。 它将使用与main方法完全相同的参数值进行调用,因此它必须具有相同的参数类型(以相同的顺序)。因为该getQuoteFor()方法接受UUID,所以此方法也是如此。
Hystrix,无论是独立的还是由Spring Boot Cloud包装,都需要在代码级别处理断路器。因此,它需要提前计划,并且更改需要部署更新的二进制文件。但是,当出现问题时,可以实现非常精细的定制行为。
Istio vs Hystrix:断路器之战
断路器模式是处理服务可用性不足的方法之一:它不是排队请求和阻塞调用者,而是快速失败并立即返回。
有两种方法可以实现断路器,黑盒方式和白盒方式。Istio作为代理管理工具,采用黑盒方式。它实现起来很简单,它不依赖于底层技术堆栈,它可以被配置为事后的想法。
另一方面,Hystrix库使用白盒方式。它允许拥有所有不同类型的后备:
它还提供级联回退。这些附加功能需要付出代价:它需要在仍处于开发阶段时做出后退决策。
两种方法之间的最佳匹配可能取决于一个人自己的背景:在某些情况下,例如引用服务,带有后备的白盒策略可能更适合,而对于其他情况,快速失败可能完全可以接受,例如集中式远程日志服务。