在单体架构中应用Hystrix


Hystrix是一个非常成熟的库,用于隔离分布式系统中的远程操作。通常只有在“纯”微服务架构中运行时才由开发人员考虑。但是即使我们的项目“只有”一个或两个连接到外部系统,是否也值得一试呢?
我想是的,但是如果您的项目连接到某些外部系统,可以试试Hystrix。

回退
当连接到外部系统时,我们通常不会考虑如果远程系统停机我们应该支持什么回退操作,我们倾向于乐观并假设,在99%的情况下,这个系统将在没有任何错误的情况下做出响应并且响应速度非常快。一些更成熟的开发人员将处理大多数可预测的错误,记录它们并可能通知用户操作失败。如果我们开始使用Hystrix会有什么变化?
当然,我们会鼓励(或者甚至强迫)我们考虑在出现错误时应该做些什么,因为Hystrix的基本配置包括为指定的业务操作定义了回退。
让我们假设我们正在设计一种管理我们书籍的服务。对于我们展示的每本书,我们希望从外部系统加载它的平均价格。在代码中它看起来像:

public class BookPriceService {
   BookPrice fetchPriceFor(BookId bookId) {
     ... 
   }
}

如果我们使用Spring和一个集成库将Spring与Hystrix(Hystrix javanica)集成在一起,我们可以轻松地更改此代码,以便在获取失败时支持回退。我们添加了一个注释和一个回退函数。
public class BookPriceService {
    @HystrixCommand(fallbackMethod = "undefinedPrice")
    BookPrice fetchPriceFor(BookId bookId) {
       
//...
    }

    BookPrice undefinedPrice() {
        return BookPrice.undefined();
    }
}

现在,如果我们的服务失败(确实抛出了fetchPriceFor方法的一些异常),我们得到具有undefinedPrice的BookPrice(由静态方法BookPrice.undefined()返回)。现在我们只需要在前端支持这个值并向用户显示正确的消息。
可以为许多其他服务创建这样的回退(特别是那些获得一些不重要信息的服务)。

超时
当远程调用变得滞后时,
处理外部系统变得非常令人沮丧。通常我们没有为此做好准备,我们将这种滞后传播到我们的系统甚至最终用户。让我们举例说一下用户填写某种表单提交服务器后,从中获取信息,并通过SMTP服务器发送电子邮件。在发送电子邮件之前,用户填写的表单将有一个等待显示正在进行的操作。
如果SMTP服务器开始响应非常慢,会发生什么?当花费太长等待时间,用户会尝试再次执行它。也许又有了这一个请求,这第二个请求是否会顺利进行?
Hystrix将帮助您在系统中配置此类行为,可以设置发送邮件的方法等待三秒后执行取消操作,执行fallback 。

线程池分离
外部系统慢还导致一个问题 - 线程池会用光,当越来越多的线程执行远程调用并且它们永远留在那里无法收回到线程池时会发生什么?当然我们所有的线程都挂在这个调用上,我们正在消耗越来越多的线程。在最糟糕的情况下,我们最终可能没有更多的线程来处理与服务器的任何额外连接,因为它们都在等待外部系统。
令人恐惧的是,某些只涉及我们所有系统功能的某些部分的外部系统可能会破坏我们的整个项目。
Hystrix再次帮助我们以几乎零成本避免这种情况。默认情况下,如前面的示例所示配置Hystrix时,Hystrix将创建另外一个线程池,该池与应用程序服务器中的默认池分开。当然你可以调整这个线程池来改变它的大小,queueSize和许多其他(这里都描述)。
现在,如果Hystrix中的所有线程都将被消耗,您可以拒绝接下来的线程或进行几个排队。一般情况下,您可以按照您希望的方式调整它,不要拒绝太多的请求,也不要在执行时停留太长时间。
而且你不仅可以配置一个线程池。例如,如果为每个系统连接到2个外部系统,则可以配置不同的线程池。或者甚至在使用一个系统进行一些非常持久的远程调用时,您可以使用不同的线程池设置。
配置多个线程池不是零成本。您需要考虑到它会增加上下文切换和计算机负载。

断路器
我想提到的Hystrix的最后一件事是断路器模式。简而言之,Hystrix正在测量每次呼叫远程系统的统计数据。如果故障超过某些阈值,则Hystrix会自动拒绝下一次呼叫而不调用外部系统(Hystrix将此外部系统标记为“关闭”)。当然并非所有请求都被拒绝 - Hystrix将不时绕过一个请求以检查系统是否已启动。
如果否,则再次下一次请求被自动拒绝而不调用外部系统直到下次测试请求到来。
如果测试请求成功,那么我们将清除所有先前的统计信息并转到初始状态
这个解决方案有哪些优势?首先,我们不会向外部系统添加更多调用,因为它看起来在快速响应时存在实际问题。多亏了这一点,它可以尝试从缓慢恢复到正常状态。其次,我们不必等待超时才能发现外部系统已关闭:如果Hystrix处于“拒绝”状态,我们会在零时间内拒绝远程呼叫(快速失败)。