Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SCB-2228]add documents for fast fail and retry (#232)
- Loading branch information
Showing
3 changed files
with
112 additions
and
0 deletions.
There are no files selected for viewing
110 changes: 110 additions & 0 deletions
110
java-chassis-reference/zh_CN/docs/references-handlers/fail-retry.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# 快速失败和重试 | ||
|
||
随机失败在微服务系统经常发生。产生随机失败的原因非常多,以JAVA微服务应用为例,造成请求超时这种随机失败的原因包括:网络波动和软硬件 | ||
升级,可能造成随机的几秒中断;JVM垃圾回收、线程调度导致的时延增加;流量并不是均匀的,同时到来的1000个请求和1秒内平均来的1000个请求 | ||
对系统的冲击是不同的,前者更容易导致超时;应用程序、系统、网络的综合影响,一个应用程序突然的大流量可能会对带宽产生影响,从而影响其他 | ||
应用程序运行;其他应用程序相关的场景,比如SSL需要获取操作系统熵,如果熵值过低,会有几秒的延迟。系统不可避免的面临随机故障,必须具备 | ||
一定的随机故障保护能力。 | ||
|
||
重试是解决随机失败的一个非常有效的措施。但是实施重试是一个非常复杂的课题。本文提供一个重试的最佳架构方案,分析如何解决重试面临的处理 | ||
快速失败的问题。 | ||
|
||
使用`请求超时`配置来控制快速失败是非常常见的手段,因为它简洁无侵入,适用于大多数对于性能要求不高的场景。但是对于大规模应用系统,以及 | ||
对于请求时延要求非常高的系统,`请求超时`并不是一个快速失败的有效手段。 | ||
|
||
* 请求超时在使用HTTP协议的情况下,会关闭连接。连接重连是非常耗时的操作,大量重连会导致系统性能的严重恶化。 | ||
* 在多线程系统,请求超时都是通过独立的线程进行检测的。这里涉及一个实时计算的问题。JDK并不能很好的应用于实时计算。简单的讲就是超时 | ||
检查并不是准确的。当系统繁忙的时候,超时的检测准确性会持续下降,本来一些正常处理未超时的请求,可能被误认为超时;一些超时的请求, | ||
可能推迟了一段时间才检测到。 应用程序应该减少实时性假设,包括:client实时感知实例上下线;超时时间很精确;任务按照预期的执行 | ||
时间处理;任务的处理时间恒定等。 从可靠性的角度,用户配置的超时时间不应该很短,从经验上来讲,超时时间配置小于1s的情况,系统发生 | ||
超时失败的概率会显著提升。如果业务的平均时延10ms数量级,建议超时时间配置不应该低于1s,平均时延越高,超时时间应该等比例增加。 | ||
* 超时以后,请求返回,并不会中断已经进行的任务。强制中断进行的任务(比如终止线程的方式),会破坏程序内部状态,导致非常复杂难于分析 | ||
的故障,正在执行的任务需要优雅可控制的结束。 | ||
|
||
## 如何应用重试和快速失败 | ||
|
||
* 在网关进行重试一般是比较推荐的做法,除了重试,网关还需要加入流量控制和流量梳理功能,过滤超出系统处理能力的流量,并将突发的流量 | ||
转换为平滑的流量。微服务系统内部的请求进行重试可以作为补充,但是超时等错误场景,不应该进行重试。 | ||
* 当业务的平均时延在1ms~10ms,建议超时时间配置不小于1s;10~100ms,超时时间配置不小于5s;大于100ms,超时时间配置不小于10s。 | ||
不建议超时时间超过30s(缺省值)。当业务某些请求需要超过30s的时候,应该对这些业务逻辑进行特殊处理,比如修改为独立线程池执行, | ||
并设置独立的超时时间;或者修改为异步执行,请求来的时候立即返回,通过异步的方式查询任务执行结果。 | ||
|
||
通常业务都能够容忍超时给用户带来的偶然操作错误,无需对超时场景重试,依赖于超时设置进行快速失败是非常简单易用的技术手段。有些业务系统对用户体验 | ||
提出了更高的要求,比如快速失败控制在100ms以下,网关能够对超时的场景也进行重试。由于超时时间设置并不能很好的处理这种精度,侵入式的 | ||
超时检测就变得非常重要。 | ||
|
||
侵入式超时检测在业务执行线程中执行,当业务逻辑执行到某个点,就进行一个超时检测,如果发现超时,就立即停止处理并返回超时错误。侵入式 | ||
超时检测有非常多的优点: | ||
|
||
* 不会导致HTTP连接关闭。因此应用可以设置更大的非侵入式超时时间,更小的侵入式超时时间,避免网络请求超时时间过小,引起的随机故障。 | ||
* 侵入式检测可以由业务在合理的执行点进行检测,能够更加优雅的进行资源清理,防止程序状态不一致带来的问题。 | ||
|
||
侵入式超时检测需要额外在业务代码中插入检测代码,会给代码带来一定的复杂性,可以采用切面等技术将这些逻辑进行有效隔离。 | ||
|
||
|
||
## Java Chassis的侵入式超时检测机制 | ||
|
||
Java Chassis 将请求的执行分为很多阶段,以客户端A将REST请求发送到服务端B为例,请求执行包括如下阶段: | ||
|
||
* 开始 | ||
* A 执行 Handler | ||
* A 执行 HttpClientFilter | ||
* A 发送请求 | ||
* B 收到请求 | ||
* B 执行 HttpServerFilter | ||
* B 执行 Handler | ||
* B 执行 业务逻辑 | ||
* B 执行 Handler | ||
* B 执行 HttpServerFilter | ||
* B 发送响应 | ||
* A 收到响应 | ||
* A 执行 HttpClientFilter | ||
* A 执行 Handler | ||
* 结束 | ||
|
||
Java Chassis 会在上述过程的开始阶段,执行超时检测,如果发现请求超时,会返回 InvocationException, 并包含 408 错误码。 | ||
|
||
侵入式超时机制在 2.3.0 版本提供, 控制侵入式超时有如下配置: | ||
|
||
| 配置项 | 默认值 | 含义 | | ||
| :--- | :--- | :--- | | ||
| servicecomb.invocation.enableTimeoutCheck | true | 功能开关,默认启用 | | ||
| servicecomb.invocation.${op-any-priority}.timeout | -1 | 请求超时时间,默认为-1,表示不超时 | | ||
|
||
侵入式超时时间支持全局配置和针对某个具体接口配置, Producer 和 Consumer 配置不同。 比如: | ||
|
||
```yaml | ||
# 指定默认的超时时间为 60 秒 | ||
servicecomb.invocation.timeout: 60000 | ||
|
||
# 指定 Producer 的 ${schema_id}.${operation_id} 的执行时间为 1 秒 | ||
servicecomb.invocation.${schema_id}.${operation_id}.timeout: 1000 | ||
|
||
# 指定 Consumer 的 ${target_service}.${schema_id}.${operation_id} 的执行时间为 1 秒 | ||
servicecomb.invocation.${target_service}.${schema_id}.${operation_id}.timeout: 1000 | ||
``` | ||
|
||
侵入式超时检测具备传播机制。 比如客户端->A->B的场景,当B判断是否已经超时的时候,会加上在A已经处理的时间。因此可以用侵入式超时时间控制 | ||
请求链路的全局超时。 由于机器时间同步问题,全局超时并不是所有环节都是精确的,比如B在计算超时的时候,A的请求在网络传输的时间被忽略掉了,只计算实际 | ||
在A已经处理的时间。 | ||
|
||
开发者也可以在自定义 Filter, Handler, 业务逻辑(比如执行数据库操作前和操作后)增加超时检测。 具体方式是先获取 `Invocation` 对象, 然后调用 | ||
`ensureInvocationNotTimeout` 方法。 | ||
|
||
```java | ||
public String testInvocationTimeout(InvocationContext context) { | ||
someTimeConsumingOperartion(); | ||
|
||
Invocation invocation = (Invocation) context; | ||
invocation.ensureInvocationNotTimeout(); | ||
|
||
otherOpertions(); | ||
} | ||
``` | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters