在处理分布式交互时,我们经常使用超时。超时是一种简单的机制,当您认为响应不会出现时,它允许您停止对响应的等待。良好放置的超时提供了故障隔离,确保失败仅限于它所影响的微服务,并允许您处理超时并以降级模式(degraded mode)继续执行。

client.get(path)
    .rxSend() // Invoke the service
    // We need to be sure to use the Vert.x event loop
    .subscribeOn(RxHelper.scheduler(vertx))
    // Configure the timeout, if no response, it publishes
    // a failure in the Observable
    .timeout(5, TimeUnit.SECONDS)
    // In case of success, extract the body
    .map(HttpResponse::bodyAsJsonObject)
    // Otherwise use a fallback result
    .onErrorReturn(t -> {
        // timeout or another exception
        return new JsonObject().put("message", "D'oh! Timeout");
    })
.subscribe(
    json -> {
        System.out.println(json.encode());
    }
);

超时通常与重试一起使用。当超时发生时,我们可以再试一次。在失败后立即重试操作有许多副作用,但其中只有一些是有益的。如果是因为调用微服务的一个重要问题而操作失败,再次重新尝试很可能再次失败。然而,某些类型的瞬时故障可以通过重试来克服,特别是网络故障,例如删除消息。您可以像下面这样决定是否重新尝试操作:

client.get(path)
.rxSend()
.subscribeOn(RxHelper.scheduler(vertx))
.timeout(5, TimeUnit.SECONDS)
// Configure the number of retries
// here we retry only once.
.retry(1)
.map(HttpResponse::bodyAsJsonObject)
.onErrorReturn(t -> {
    return new JsonObject().put("message", "D'oh! Timeout");
})
.subscribe(
    json -> System.out.println(json.encode())
);

同样重要的是要记住,超时并不意味着操作失败。在分布式系统中,失败的原因有很多。让我们来看一个例子。您有两个微服务, A和B. A向B发送请求,但是没有及时响应,而A得到超时。在这种情况下,可能发生三种类型的故障:

1.A和B之间的消息已经丢失------操作没有执行。

2.在B中的操作失败——即该操作没有完成它的执行

3.B和A之间的响应消息丢失——操作已经成功执行,但是A没有得到响应。

最后一种情况经常被忽视,而且可能是有害的。在这种情况下,将超时与重试结合起来可以破坏系统的完整性。重试只能用于幂等运算,即:您可以多次调用,而无需更改初始调用之外的结果(译者注:请与HTTP的GET/DELETE来做类比)。在使用重试之前,请始终检查您的系统是否能够优雅地处理重新尝试的操作。

重试也会让消费者等待更长的时间来得到响应,这也不是一件好事。通常情况下,返回一个退而求其次的结果(fallback)要比多次重复一个操作要好。此外,不断地调用失败的服务可能无法帮助它回到正轨。这两个问题是由另一个弹性模式管理的:熔断器。

results matching ""

    No results matching ""