part3讨论了使用断路器来处理请求/响应API不可用的情况。现在,我们将讨论如何通过限流来遵守请求/响应API的流量限制。
由外部服务提供的请求/响应API通常有流量限制,如100次请求/s,上游微服务须遵守这些限制。与这些请求/响应API集成的事件驱动微服务可能会在没有额外措施的情况下超出其使用限制。
限流是一种确保不超出流量限制的有效方法。通常,下游微服务会使用限流来防止过多请求。实现这一目标的一种方法是安装一个Web应用防火墙(WAF),当上游微服务超过流量限制时,该防火墙会阻止请求。然后,WAF会返回一个错误,如429 TOO_MANY_REQUESTS。
从发送请求到请求/响应API的事件驱动微服务的角度,这种类型的错误响应会导致重试机制启动。与之前博文中的示例一样,事件最终进入死信队列。
如part3讨论那样,集成一个断路器只能在一定程度帮助解决问题。一旦事件驱动微服务超出使用限制并接收到429错误,断路器将暂停事件处理;它将在一段时间后恢复处理。如果断路器恢复处理前的等待时间足够长,WAF和请求/响应API将再次接受请求。然而,由于断路器并不影响事件处理的速度,流量限制很可能再次被超出。断路器会不断地停止和恢复事件处理。
在向事件驱动微服务添加限流器以防止其超出API的流量限制方面有过积极的经验。根据具体的限流实现,可提供不同的算法。如resilience4j库提供令牌桶算法实现,使用该算法可以实现每秒最多100次请求的场景。你可配置一个max为100个令牌的桶,并将限流器配置为每秒将桶填满至100个令牌。
在事件处理期间,在你向具有流量限制的API发送请求前,可用限流器从桶中获取一个令牌:
- 如桶中有足够令牌,可用令牌的数量减一,并且事件处理可继续
- 如果没有令牌可用,事件处理只能稍后继续
限流器可以阻止事件处理,直到桶被重新填满并且令牌再次可用。
blockAndAcquireToken()方法对应之前描述的行为。该方法尝试从桶中获取一个令牌;如果有令牌可用,令牌会从桶中移除,事件处理继续,通过创建并发送请求到API。如果没有令牌可用,该方法会等待直到新令牌可用。它会阻止事件处理,以避免超出API的流量限制。
// 使用限流器的事件处理
void processEvent(Event event) {
/* ... */
rateLimiter.blockAndAcquireToken();
Request request = createRequest(event);
Response response = sendRequestAndWaitForResponse(request);
moreBusinessLogic(event, response);
/* ... */
}在像resilience4j这样的实现中,blockAndAcquireToken()方法只会在指定时间内阻止处理,然后返回一个错误并使事件处理失败。配置的等待时间和中间件的可见性超时应保持一致。如果blockAndAcquireToken()方法因为没有令牌可用而阻止事件处理,它应该在可见性超时到期之前返回一个错误并使事件处理失败。此外,如果它获得了一个令牌并恢复了事件处理,那么在可见性超时到期之前应该有足够的时间来完成事件处理。
如果无法做到这一点,就会导致事件的重复处理。尽管part1讨论了如何处理重复事件,但通过适当地配置可见性超时和限流器的等待时间应该尽量避免这种情况。
外部服务提供商通常会定义并强制执行其提供的请求/响应API的流量限制。在本文中,我们已经看到限流是遵守事件驱动微服务中流量限制的有效解决方案。
本文是关于我们将事件驱动微服务与请求/响应API集成经验的系列文章的最后一篇。正如我们所见,集成涉及到一些相当大的挑战。然而,许多情况下集成是必须的——如依赖第三方服务提供商或系统尚未在遗留现代化过程中迁移到事件驱动架构,这是你将需要做的事情。