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