Envoy 局部故障处理机制
局部故障处理机制
- retry:分布式环境中对远程资源和服务的调用可能会由于瞬态故障(短时间内可自行恢复的故障)而失败,一般情况下,重试机制可解决此类问题
-
常见的瞬态故障有网络连接速度慢、超时、资源过量使用或暂时不可用等
-
-
timeout:此外,也存在因意外事件而导致故障,并且可能需要较长的时间才能得以恢复;
-
此类故障的严重性范围涵盖从部分连接中断到服务完全失败;
-
连续重试和长时间的等待对该类场景都没有太大意义
-
应用程序应迅速接受该操作已失败并主动地应对该失败
-
-
可以将调用服务的操作配置为实施“超时”,若该服务在超时时长内未能响应,则以失败消息响应
-
-
circuit breaker:还有,若服务非常繁忙,则系统某一部分的故障可能会导致级联故障;
-
对此,简单的超时策略可能导致对同一操作的许多并发请求被阻止,直到超时时长耗尽为止;
-
这些被阻止的请求可能包含关键的系统资源,例如内存、线程和数据库连接等
-
这类资源的耗尽可能导致需要使用相同资源的系统其他可能不相关的部分出现故障
-
于是,此时最好立即使操作失败,并且仅在可能成功的情况下才尝试调用服务
-
-
请求重试
-
分布式应用环境中的瞬态故障并不鲜见
-
常见的故障包括与组件和服务的网络连接的暂时丢失、服务暂时不可用或服务繁忙时发生的超时等
-
这类故障通常能自我纠正,并且如果在适当的延迟后重复此前触发了故障的请求操作,则可能会成功
-
- 通过透明地重试失败的操作,使应用程序在尝试连接到服务或网络资源时能够处理瞬态故障,可以显著提高应用程序的稳定性;
-
常见的瞬态处理策略有如下三个
-
重试:对于暂时性或不常见类型的故障,反复请求通常可以获得正确结果;
- 延迟后重试:若故障是由较常见的连接故障或繁忙故障引起,通常需要一些时间纠正连接问题或清除积压工作,应用程序则应等待适当的时间,然后重试请求;
-
取消:对于并非暂时性故障,或即便反复请求也不太可能成功的故障,则应用程序应取消该操作并报告异常;
-
- 对于更常见的瞬态故障,应合理设定重试之间的时间间隔,以尽可能均匀地传播来自应用程序多个实例的请求,从而减少繁忙服务持续超载的机率
- 显然,如果应用程序的许多实例因不断重试请求而使服务不堪重负,则恢复该服务将花费更长的时间
-
可以在重试尝试之间增加延迟的方式重复此过程,直到尝试了最大数量的请求为止
-
延迟可以增量或指数方式增加,具体取决于故障的类型以及在此期间得到纠正的可能性
-
请求重试的注意事项
- 重试策略需要匹配应用程序的业务需求和故障的性质,对于某些非关键操作,最好是快速失败而不是重试几次并影响应用程序的吞吐量
-
对于交互式Web应用程序,在较短的延迟后进行少量重试,而后向用户显示适当的消息,例如“请稍后重试”
-
对于批处理应用,增加重试次数可能会更合适,但重试次数之间的延迟应成倍或指数级增加
-
-
若大量重试后请求仍然失败,最好防止进一步的请求进入同一服务,并立即报告失败
-
在一定的过期时长后,设定服务暂时允许一个或多个请求通过,以查看它们是否成功,这是断路器的功能
-
-
还需要考虑操作幂等与否
-
对于幂等性操作,重试本质上是安全的
-
否则,重试可能导致该操作被执行多次,并产生意外的副作用
-
- 请求可能会由于多种原因而失败,它们可能分别会引发不同的异常,重试策略应根据异常的类型调整两次重试之间的时间间隔
-
有些异常可能是迅速可解决的故障
-
有些异常则可能会持续更长时间
-
- 确保所有重试代码已针对各种故障情况进行了全面测试,以检查它们是否不会严重影响应用程序的性能或可靠性,是否对服务和资源造成过多负担,是否产生竞争状况或瓶颈
HTTP请求重试
-
Envoy支持在虚拟主机及路由级别配置中以及通过特定请求的标头配置重试
-
路由级别重试的优先级高于虚拟主机级别
-
-
重试策略的相关属性包括重试次数和重试条件等
-
最大重试次数:可重试的最大次数
-
在每次重试之间使用指数退避算法
-
单次重试操作有其超时时长
-
所有重试都包含在整个请求的超时时长之内,以避免由于大量重试而导致的超长请求时间
-
-
重试条件:是指进行重试的前提条件,例如网络故障、5xx类的响应码等
-
HTTP请求重试格式
retry_policy: {...} # 重试策略,优先于虚拟主机级别的重试策略;
retry_on: ... # 重试发生的条件,其功能同x-envoy-retry-on和x-envoy-retry-grpc-on标头相同;
num_retries: {...} # 重试次数,默认值为1,其功能同x-envoy-max-retries标头相同,但采用二者中配置的最大值;
per_try_timeout: {...} # 每次重试时同上游端点建立连接的超时时长;
per_try_idle_timeout: {...} # 指定每次重试尝试(包括初始尝试)的上游空闲超时。此参数是可选的,如果不存在,则没有每次尝试空闲超时。
retry_priority: {...} # 配置重试优先级策略,用于在各优先级之间分配负载;
retry_host_predicate: [] # 重试时使用的主机断言(predicate)列表,各断言用于拒绝主机;在选择重试主机时将参考该列表中的各断言,若存在任何谓词拒绝了该主机,则需要重新尝试选择其它主机
retry_options_predicates: []
host_selection_retry_max_attempts: ... # 允许尝试重新选择主机的最大次数,默认为1;
retriable_status_codes: [] # 除了retry_on指定的条件之外,用于触发重试操作的http状态码列表;
retry_back_off: {...} # 配置用于控制回退算法的参数,默认基本间隔为25ms,给定基本间隔B和重试次数N,重试的退避范围为 [0,(2^N−1)B),最大间隔默认为基本间隔(250ms)的10倍;
rate_limited_retry_back_off: {...} # 定义控制重试回退策略的参数;
retriable_headers: [] # 触发重试的HTTP响应标头列表,上游端点的响应报文与列表中的任何标头匹配时将触发重试;
retriable_request_headers: [] # 必须在用于重试的请求报文中使用的HTTP标头列表;
HTTP请求重试条件
-
重试条件(同x-envoy-retry-on标头)
- 5xx:上游主机返回5xx响应码,或者根本未予响应(断开/重置/读取超时)
- gateway-error:网关错误,类似于5xx策略,但仅为502、503或504的应用进行重试
- connection-failure:在TCP级别与上游服务建立连接失败时进行重试
- retriable-4xx:上游服务器返回可重复的4xx响应码时进行重试
- refused-stream:上游服器务使用REFUSED——STREAM错误码重置时进行重试
- retriable-status-codes:上游服务器的响应码与重试策略或x-envoy-retriable-status-codes标头值中定义的响应码匹配时进行重试
- reset:上游主机完全不响应时(disconnect/reset/read超时),Envoy将进行重试;
- retriable-headers:如果上游服务器响应报文匹配重试策略或 x-envoy-retriable-header-names标头中包含的任何标头,则Envoy将尝试重试
- envoy-ratelimited:标头中存在x-envoy-ratelimited时进行重试
-
重试条件2(同x-envoy-retry-grpc-on标头)
- cancelled:gRPC应答标头中的状态码是“cancelled”时进行重试
- deadline-exceeded:gRPC应答标头中的状态码是“deadline-exceeded”时进行重试
- internal:gRPC应答标头中的状态码是“internal”时进行重试
- resource-exhausted:gRPC应答标头中的状态码是“resource-exhausted”时进行重试
- unavailable:gRPC应答标头中的状态码是“unavailable”时进行重试
-
默认情况下,Envoy不会进行任何类型的重试操作,除非明确定义
重试和超时机制配置示例
配置示例说明
测试超时
# 在后端Envoy上被注入10秒延迟的请求,在请求时长超过一秒钟后即会触发前端Envoy上的重试操作,进而进行请求重试,直至首次遇到未被注入延迟的请求,因此其总的响应时长一般为1秒多一点:
测试重试
# 重试操作;最大3次的重试,仍有可能在连续多次的错误响应后,仍然响应以错误信息,但其比例会大大降低。
front-envoy.yaml
查看代码
admin:
profile_path: /tmp/envoy.prof
access_log_path: /tmp/admin_access.log
address:
socket_address:
address: 0.0.0.0
port_value: 9901
layered_runtime:
layers:
- name: admin
admin_layer: {}
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 80 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: backend
domains:
- "*"
routes:
- match:
prefix: "/service/blue"
route:
cluster: blue_abort
retry_policy:
retry_on: "5xx" # 响应码为5xx时,则进行重试,重试最大次数为3次;
num_retries: 3
- match:
prefix: "/service/red"
route:
cluster: red_delay
timeout: 1s # 超时时长为1秒,长于1秒,则执行超时操作;
- match:
prefix: "/service/green"
route:
cluster: green
- match:
prefix: "/service/colors"
route:
cluster: mycluster
retry_policy: # 超时和重试策略同时使用;
retry_on: "5xx"
num_retries: 3
timeout: 1s
http_filters:
- name: envoy.filters.http.router
clusters:
- name: red_delay
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: red_delay
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: service_red
port_value: 80
- name: blue_abort
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: blue_abort
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: service_blue
port_value: 80
- name: green
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: green
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: service_green
port_value: 80
- name: mycluster
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: mycluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: colored
port_value: 80
service-envoy-sidecar-fault-injection-delay.yaml
查看代码
admin:
profile_path: /tmp/envoy.prof
access_log_path: /tmp/admin_access.log
address:
socket_address:
address: 0.0.0.0
port_value: 9901
layered_runtime:
layers:
- name: admin
admin_layer: {}
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 80 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: local_service
http_filters:
- name: envoy.filters.http.fault
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
max_active_faults: 100
delay:
fixed_delay: 10s
percentage:
numerator: 10 # 向10%的请求注入10秒钟的延迟
denominator: HUNDRED
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: local_service
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: local_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8080
service-envoy-sidecar-fault-injection-abort.yaml
查看代码
admin:
profile_path: /tmp/envoy.prof
access_log_path: /tmp/admin_access.log
address:
socket_address:
address: 0.0.0.0
port_value: 9901
layered_runtime:
layers:
- name: admin
admin_layer: {}
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 80 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: local_service
http_filters:
- name: envoy.filters.http.fault
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
max_active_faults: 100
abort:
http_status: 503
percentage:
numerator: 10 # 向10%的请求注入503中断
denominator: HUNDRED
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: local_service
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: local_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8080
重试插件
重试行为
-
重试操作会触发重新选择目标主机
-
重试期间选择主机与首次处理请求时选择主机的方式相同,具体的选择行为由调度策略决定
-
重试插件为用户提供了配置重试期间重新选择主机的方式
-
Host Predicates
- 用于拒绝重试选择一个主机的断言,任何主机匹配断言列表中的任何断言时将会被拒绝,并导致重试进行主机选择;
- Envoy 支持以下内置host predicates
-
envoy.retry_host_predicates.previous_hosts:跟踪以前尝试过的主机,并拒绝已经尝试过的主机;
-
envoy.retry_host_predicates.omit_canary_hosts:拒绝任何标记为Canary的主机,其标记配置在端点的过滤器过滤的元数据中;
- envoy.retry_host_predicates.omit_host_metadata:这将根据预定义的元数据匹配标准拒绝任何主机。
-
Priority Predicates
-
用于调整重试尝试选择优先级时使用的优先级负载,此类断言仅能指定一个;
- Envoy 支持以下内置priority predicates
-
envoy.retry_priority.previous_priorities:跟踪先前尝试的优先级,并据此调整优先级负载以便在后续的重试尝试中将其它优先级作为目标;
-
重试插件配置示例
- 例如,要将重试配置为首选尚未尝试过的主机, 可以使用内置predicates:
envoy.retry_host_predicates.previous_hosts
retry_policy:
retry_host_predicate:
- name: envoy.retry_host_predicates.previous_hosts
typed_config:
"@type": type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
host_selection_retry_max_attempts: 3
这将拒绝以前尝试过的主机,最多重试主机选择 3 次。为了处理不可能找到可接受的主机(没有主机满足predicates)或不太可能(唯一合适的主机具有非常低的相对权重)的情况,尝试限制是必要的。
- 要根据其元数据拒绝主机,可以使用
envoy.retry_host_predicates.omit_host_metadata
:
retry_policy:
retry_host_predicate:
- name: envoy.retry_host_predicates.omit_host_metadata
typed_config:
"@type": type.googleapis.com/envoy.extensions.retry.host.omit_host_metadata.v3.OmitHostMetadataConfig
metadata_match:
filter_metadata:
envoy.lb:
key: value
这将拒绝任何在其元数据中匹配(键、值)的主机。
- 要配置重试以在重试期间尝试其他优先级,可以使用内置envoy.retry_priorities.previous_priorities
retry_policy:
retry_priority:
name: envoy.retry_priorities.previous_priorities
typed_config:
"@type": type.googleapis.com/envoy.extensions.retry.priority.previous_priorities.v3.PreviousPrioritiesConfig
update_frequency: 2
这将针对尚未使用的后续重试尝试中的优先级。该update_frequency
参数决定了重新计算优先级负载的频率。
- 这些插件可以组合,这将排除以前尝试过的主机以及以前尝试过的优先级。
retry_policy:
retry_host_predicate:
- name: envoy.retry_host_predicates.previous_hosts
typed_config:
"@type": type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
host_selection_retry_max_attempts: 3
retry_priority:
name: envoy.retry_priorities.previous_priorities
typed_config:
"@type": type.googleapis.com/envoy.extensions.retry.priority.previous_priorities.v3.PreviousPrioritiesConfig
update_frequency: 2