1. 项目背景
之前文章【服务网格可观测性之平台化监控报警】介绍了汽车之家云原生服务网格化改造过程中平台化监控报警的相关技术。本篇文章将介绍云原生服务网格化改造过程服务治理的相关实践,其中服务限流、服务熔断为业务方使用场景最多的治理方案,本篇文章也将围绕服务限流、服务熔断两个方面介绍。
服务限流功能是保护服务的重要手段,外部流量请求量超过了系统处理能力的极限,后续新进入的请求会持续加重服务负载,导致资源耗尽发生服务出错。限流的目的就是拒绝过多的请求流量,保证服务整体负载处于合理水平。系统的吞吐量一般是可以被测算的,为了保证系统的稳定运行,一旦达到需要限制的阈值,就需要采取措施限制流量,比如延迟处理、拒绝处理,或者部分拒绝处理等。
引发故障的原因除了外部问题(流量激增)外还有系统内部的问题。与单体应用相比微服务架构的服务调用链路更加复杂,微服务的拆分虽然提升了系统的总体能力,但同时也增大了级联错误出现的可能性,多个服务之间存在依赖调用,如果某个服务无法及时响应请求,故障向调用源头方向传播,可能引发集群的大规模级联故障,造成整个系统不可用。
为了防止此情况发生就需要引入服务熔断策略,熔断器的工作原理过程可以比作电路中的熔丝原理,即当电流过大可能引起线路等设备损坏时,熔丝熔断以切断电路,保护其他设备。与之类似,服务熔断机制在服务调用中设置阈值,当调用失败或超时次数超过这个阈值时,熔断器就会开启。一旦熔断器开启,之后的一段时间内所有的调用都将直接失败,而不会再请求远程服务。这样可以给出问题的服务留出恢复的时间,并防止故障进一步向其他服务蔓延。
如上图,所示传统微服务架构下服务限流、服务熔断会以SDK形式侵入到业务代码的方式进行服务治理相关工作,但是这种强耦合的方案会增加开发的难度,增加维护成本,增加质量风险。比如 SDK 需要新增新特性,业务侧也很难配合 SDK 开发人员进行升级,所以很容易造成 SDK 的版本碎片化问题。如果再存在跨语言应用间的交互,对于多语言 SDK 的支持也非常的低效。一方面是相当于相同的代码以不同语言重复实现,实现这类代码既很难给开发人员带来成就感,团队稳定性难以保障;另一方面是如果实现这类基础框架时涉及到了语言特性,其他语言的开发者也很难直接翻译。
而到了云原生架构下的服务网格(istio)体系会将服务限流、服务熔断等能力从微服务代码中抽离出来,形成统一的控制层面或者代理层面,实现业务与技术的复杂度分离,研发更专注于对业务价值的交付。
服务限流和服务熔断在云原生架构下的微服务中起着保护系统稳定运行的重要作用,通过防止系统过载和失败级联,维护了系统的可用性,提升了业务连续性。下面将详细介绍服务限流、服务熔断在云原生架构下的工作原理以及之家实践过程。
2. 服务限流
在服务网格(istio)体系中服务限流分为本地限流和全局限流两种:本地限流、全局限流。
2.1
本地限流
► 2.1.1、整体架构
本地限流需要通过EnvoyFilter来实现,他不会请求外部服务,在envoy内部实现支持,而不需要依赖外部的流量控制组件。这种限流机制通过在应用程序中实施并监控一定的策略,来控制请求的发送速率或并发数,以确保底层的服务资源不会被过度使用或超载,其本质是一个令牌桶的算法。
► 2.1.2、部署示例
下面是一个本地限流的EnvoyFilter 声明:
apiVersion: networking.istio.io/v1alpha3kind: EnvoyFiltermetadata: name: filter-local-ratelimit-svcspec: workloadSelector: labels: app: productpage configPatches: - applyTo: HTTP_FILTER match: listener: filterChain: filter: name: "envoy.filters.network.http_connection_manager" patch: operation: INSERT_BEFORE value: name: envoy.filters.http.local_ratelimit typed_config: "@type": type.googleapis.com/udpa.type.v1.TypedStruct type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit value: stat_prefix: http_local_rate_limiter #表示生成stat的指标前缀 token_bucket: #配置令牌桶 max_tokens: 10 #表示最大令牌数量 tokens_per_fill: 10 #表示每次填充的令牌数量 fill_interval: 60s #表示填充令牌的间隔 filter_enabled: #表示启用但不是强制 runtime_key: local_rate_limit_enabled default_value: numerator: 100 denominator: HUNDRED filter_enforced: #表示强制,可以配置百分比。 runtime_key: local_rate_limit_enforced default_value: numerator: 100 denominator: HUNDRED response_headers_to_add: #修改响应头信息,append为false表示修改,true表示添加。 - append: false header: key: x-local-rate-limit value: 'true'
通常情况下,本地限流对保护应用不被高流量冲击有的显著的作用,由于本地限流是作用在每个应用的边车上并进行单点的令牌桶计算,本质是客户端级别的限流方案,无法实施服务级别的整体限流措施。这种情况下就需要引入全局限流的方案。
2.2
全局限流
► 2.2.1、整体架构
Istio的全局限流方案中集成了一个全局的 gRPC ratelimit 服务。
"Ratelimit 服务"是一种 Go/gRPC 服务,可在支持不同类型应用程序的通用限流方案。应用程序根据一个域和一组描述符[详见”限流策略“章节]请求作出速率限制决定。该服务通过运行时从文件(或者xDS 管理服务)读取配置,创建缓存密钥,并与 Redis 缓存对话,然后将决定返回给调用者。
► 2.2.2、http endpoint
"Ratelimit 服务" 提供了一个http接口用来暴露一些核心能力,比如:
健康检查
GET http://{ratelimitServiceName}:8080/healthcheck
查看当前生效策略
GET http://{ratelimitServiceName}:6060/rlconfig
更新细节请参考:
https://github.com/envoyproxy/ratelimit#http-port
► 2.2.3、限流策略
"Ratelimit 服务"可以通过两种方式加载限流策略,分别是:File Based Configuration Loading(文件方式)以及xDS Management Server Based 加载方式,下面是文件加载方式的一个示例:
domain: ratelimit_demo01 descriptors: - key: demoKey value: users rate_limit: unit: second requests_per_unit: 500 - key: demoKey value: default rate_limit: unit: second requests_per_unit: 500
详细说明参照:
https://github.com/envoyproxy/ratelimit#example
汽车之家采用的是第二种加载方式:XDS Management Server是一款实现Aggregated Discovery Service(ADS)的GRPC服务器。ratelimit 服务可以使用 XDS Management Server 的grpc服务 实时获取最新的限流策略。”ratelimit-config-server“是基于”go-control-plane + gin + gorm“自研的XDS Management Server 服务,支持http配置限流策略,并通过grpc将限流策略下发给限流服务。
策略配置服务(ratelimit-config-server),新建策略接口请求示例:
{ "domain": "ratelimit_demo01", "descriptors": [ { "key": "demoKey", "value": "users", "rate_limit": { "unit": 1, # second:1 minute:2 hour:3 day:4 "requests_per_unit": 500 } }, { "key": "Remote_IP", "value": "default", "rate_limit": { "unit": 1, # second:1 minute:2 hour:3 day:4 "requests_per_unit": 500 } } ]}
在上面的配置中,域是“ratelimit_demo01”,我们在顶级描述符列表中设置了2个不同的速率限制。每个限制都有相同的键(“demoKey”)。它们具有不同的值(“users”和“default”),并且它们都设置了每秒500个请求的速率限制。
► 2.2.4、wasm插件
客户端访问汽车之家主要分两种情况:
两种情况获取用户IP的方式略有不同,所以为了统一用户IP的获取方式,我们编写了wasm插件来处理用户IP获取问题:
插件作用
将 header 中 x-forwarded-for 信息分割
示例:
x-forwarded-for= 2409:8910:688:ab45:8542:c124:5236:128b,111.63.51.91, 182.138.255.115
拆分为:
x-forwarded-for-0 = 2409:8910:688:ab45:8542:c124:5236:128bx-forwarded-for-1 = 111.63.51.91x-forwarded-for-2 = 182.138.255.11
将haeder 中 :path 进行分割
示例:
:path=/getUser?name=autohome
拆分为:
x-path-0 = /getUser
► 2.2.5、部署示例
# 为服务名[dotnet-car-automesh-10001] 定义规则 IP=10.18.162.163 and path=/api/trace/health ,限速规则为 5次/分钟
定义限流策略:
{ "domain": "dotnet-car-automesh-10001", "descriptors": [ { "key": "pathRaliemit", "value": "/api/trace/health", "descriptors": [ { "key": "Remote_IP", "value": "10.18.162.163", "detailed_metric": true, "shadow_mode": false, "rate_limit": { "unit": 2, "requests_per_unit": 5 } } ] } ]}
定义envoyfilter:
apiVersion: networking.istio.io/v1alpha3kind: EnvoyFiltermetadata: name: filter-ratelimit-dotnetdemo namespace: en-888spec: workloadSelector: # select by label in the same namespace labels: app_service: dotnet-demo-yzmeshjava-36643 configPatches: # The Envoy config you want to modify - applyTo: HTTP_FILTER match: context: SIDECAR_INBOUND listener: filterChain: filter: name: "envoy.filters.network.http_connection_manager" subFilter: name: "envoy.filters.http.router" patch: operation: INSERT_BEFORE # Adds the Envoy Rate Limit Filter in HTTP filter chain. value: name: envoy.filters.http.ratelimit typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit domain: automesh-ratelimit failure_mode_deny: false request_type: both # internal、external、both timeout: 20ms rate_limited_as_resource_exhausted: true enable_x_ratelimit_headers: DRAFT_VERSION_03 disable_x_envoy_ratelimited_header: false rate_limit_service: grpc_service: envoy_grpc: cluster_name: rate_limit_cluster timeout: 10s transport_api_version: V3 - applyTo: CLUSTER match: cluster: service: ratelimit.ratelimit patch: operation: ADD # Adds the rate limit service cluster for rate limit service defined in step 1. value: name: rate_limit_cluster type: STRICT_DNS connect_timeout: 10s lb_policy: ROUND_ROBIN http2_protocol_options: {} load_assignment: cluster_name: rate_limit_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: ratelimit.ratelimit port_value: 8081
定义action envoyfilter:
apiVersion: networking.istio.io/v1alpha3kind: EnvoyFiltermetadata: name: filter-ratelimit-svc-{app_service} namespace: en-888spec: workloadSelector: labels: app_service: {app_service} configPatches: - applyTo: VIRTUAL_HOST match: context: SIDECAR_INBOUND routeConfiguration: vhost: name: "inbound|http|8080" route: action: ANY patch: operation: MERGE # Applies the rate limit rules. value: rate_limits: - actions: - request_headers: header_name: "x-path-0" descriptor_key: "pathRaliemit" - request_headers: header_name: "x-forwarded-for-0" descriptor_key: "remoteIPRaliemit"
测试
根据测试结果可以看出,已经触发限流机制【429 Too Many Requests (太多请求)】
3. 服务熔断
常用的服务熔断方式通常以SDK的形式嵌入业务代码中,常用的SDK组件为Hystrix和Sentinel ,之家部分核心应用也采用了此方案。这种强耦合的方案带来增加开发的难度,增加维护成本,增加质量风险等问题,所以我们利用Istio的特性将服务熔断能力下沉到Sidecar,由Sidecar接管服务的流量并对其进行治理。下面将介绍不侵入代码的Istio服务熔断方案。Istio中熔断在DestinationRule的CRD资源的TrafficPolicy中设置:
“连接池connectionPool”熔断方式,使用上和局部限流功能差不多,所以我们在实际应用中很少采用此方式进行熔断,一般会采用“异常检测outlierDetection”方式进行熔断配置。
DestinationRule实现:
apiVersion: networking.istio.io/v1beta1kind: DestinationRulemetadata: name: dotnet-car-automeshspec: host: dotnet-car-automesh-10001.autohome.com trafficPolicy: outlierDetection: consecutive5xxErrors: 10 interval: 5s baseEjectionTime: 5s maxEjectionPercent: 100
EnvoyFilter实现:
apiVersion: networking.istio.io/v1alpha3kind: EnvoyFiltermetadata: name: matchspec: workloadSelector: labels: app_service: dotnet-car-automesh configPatches: - applyTo: CLUSTER match: cluster: name: outbound|8080||dotnet-car-automesh-10001.autohome.com patch: operation: MERGE value: outlierDetection: consecutive5xxErrors: 10 interval: 5s baseEjectionTime: 5s maxEjectionPercent: 100
此熔断策略的目的是在每5秒钟进行异常点检测,当一个实例返回连续的5xx错误10次时,该实例会被标记为异常点并被隔离(剔除)5秒。同时,所有实例都可以被标记为异常点并隔离,以保护整个系统免受错误实例的影响。
其中“DestinationRule实现”会把熔断策略下发到所有的相关调用方边车上,生成类似全局的熔断措施,服务治理体验非常不好。采用“EnvoyFilter实现”则可以通过“workloadSelector” 使熔断策略下发到指定的服务调用方,从而实现细粒度的配置下发。
然而以上两种方式都存在弊端,那就是只能以“域名”或者“服务名称”为单位进行熔断,不能细粒度到应用path级别比如想熔断指定接口(
http://dotnet-car-automesh-10001:8080/api/trace/health)Istio 官方的熔断方案是不能满足需求的,基于此种情况我们计划通过wasm插件的方式实现平台级并满足更细粒度的服务熔断方案。实现思路为利用 proxy-wasm extension 机制,借助 Sentinel Go 熔断算法的实现,在Istio侧实现服务熔断能力。目前该功能还在研发中,有兴趣的小伙伴欢迎留言交流。
4. 总结
通过本篇文章,为大家介绍了云原生下的服务治理的相关概念以及汽车之家基于原生方案的技术改造。云原生能力的泛化与平台化可为技术人员提供便捷的服务治理能力,使其有更多的精力聚焦在业务本身。
参考:
作者简介
李建彪
■ 服务端研发部-服务端看选技术团队-基础架构组
■ 简介:擅长微服务架构、云原生架构体系,主要负责汽车之家服务端云原生服务网格化改造。
来源:微信公众号:之家技术
出处
:https://mp.weixin.qq.com/s/6N_SCB9d8B8sMhhrR23EAA