1. 微服务运行时稳定性的问题
微服务的稳定性一直是开发者非常关注的话题。随着业务从单体架构向分布式架构演进以及部署方式的变化,服务之间的依赖关系变得越来越复杂,业务系统也面临着巨大的高可用挑战。大家可能都经历过以下的场景:
演唱会抢票瞬间洪峰流量导致系统超出最大负载,load 飙高,用户无法正常下单;
在线选课时同一时刻提交选课的请求过多,系统无法响应;
页面服务中某一块内容访问很慢,一直加载不出来,导致整个页面都卡住,无法正常操作
影响微服务可用性的因素有非常多,而这些不稳定的场景可能会导致严重后果。我们从微服务流量的视角来看,可以粗略分为两类常见的运行时场景:
服务自身流量超过承载能力导致不可用。比如激增流量、批量任务投递导致服务负载飙高,无法正常处理请求。
服务因依赖其他不可用服务,导致自身连环不可用。比如我们的服务可能依赖好几个第三方服务,假设某个支付服务出现异常,调用非常慢,而调用端又没有有效地进行预防与处理,则调用端的线程池会被占满,影响服务自身正常运转。在分布式系统中,调用关系是网状的、错综复杂的,某个服务出现故障可能会导致级联反应,导致整个链路不可用。
在遇到这些微服务运行时稳定性的问题时,我们应该如何解决呢?针对这些不稳定的场景,MSE 提供全方位的流量防护能力,基于开源流量治理组件 Sentinel 的稳定性防护能力,以流量为切入点,从流量控制、并发控制、熔断降级、热点防护、系统自适应保护等多个维度来帮助保障服务的稳定性,覆盖微服务框架、云原生网关、Service Mesh 等几大场景,支持 Java、Go、C++、Rust 等多种语言的异构微服务架构。
2. CloudWeGo 原生支持 Sentinel 及 OpenSergo
CloudWeGo是一套由字节跳动开源的、可快速构建企业级云原生微服务架构的中间件集合。CloudWeGo 项目共同的特点是高性能、高扩展性、高可靠,专注于微服务通信与治理。其中 Kitex 是下一代高性能、强可扩展的 Golang RPC 框架,提供整套微服务的能力;Hertz 是高易用性、高性能、高扩展性的 Golang 微服务 HTTP 框架,旨在为开发人员简化构建微服务。
近期 CloudWeGo 社区与 Sentinel 社区合作共建,提供了 CloudWeGo Kitex 及 Hertz 对接 Sentinel Go 的适配模块,只需简单地引入适配模块并引入对应 Middleware,即可快速将 Kitex 及 Hertz 服务接入 Sentinel Go,享受全面的流量治理与防护能力。同时结合 Sentinel Go 与 OpenSergo 微服务治理标准的对接,CloudWeGo 也将支持 OpenSergo spec 中流控降级与容错的标准配置,未来可以通过统一的 CRD 方式进行流量治理管控。
适配模块文档可以参考:
同时,基于 Sentinel 的 Kitex 和 Hertz 适配模块,我们可以结合 MSE 服务治理 Go SDK 接入方式,来方便地将 Kitex 和 Hertz 服务接入到阿里云 MSE 微服务治理产品中,通过白屏化的观测与治理配置手段来保障微服务的运行时流量稳定性。
3. CloudWeGo + MSE 流量防护最佳实践
MSE 微服务治理提供全方位的流量治理、流量防护及微服务视角的数据库治理能力,其中对于 Go 微服务,开发者可以通过 Go SDK 方式接入 MSE 来享受流控降级与容错相关的治理、白屏化配置与观测能力,保障服务运行时的稳定性。MSE 流量治理支持 CloudWeGo, gRPC, Gin, dubbo-go, go-micro 等常用 Go 微服务框架的流控降级与容错能力。首先我们先来看一下流量防护的典型场景。
3.1 流量控制保障激增流量下服务的稳定性
流量是非常随机性的、不可预测的。前一秒可能还风平浪静,后一秒可能就出现流量洪峰了(例如双十一零点的场景)。然而我们系统的容量总是有限的,如果突然而来的流量超过了系统的承受能力,就可能会导致请求处理不过来,堆积的请求处理缓慢,CPU/Load 飙高,最后导致系统崩溃。因此,我们需要针对这种突发的流量来进行限制,在尽可能处理请求的同时来保障服务不被打垮,这就是流量控制。流量控制的场景是非常通用的,像脉冲流量类的场景都是适用的。
MSE Sentinel 基于毫秒级滑动窗口精确统计与令牌桶、 漏桶、WarmUp 等流量控制算法,提供包括秒级精准流控、集群总量流控、匀速排队等在内的多种维度的流量控制场景。通常在 Web 入口或 RPC 服务提供方(Service Provider)的场景下,我们需要保护服务提供方自身不被流量洪峰打垮。这时候通常根据服务提供方的服务能力进行流量控制,或针对特定的服务调用方进行限制。我们可以结合前期压测评估核心接口的承受能力,配置 QPS 模式的流控规则,当每秒的请求量超过设定的阈值时,会自动拒绝多余的请求。
3.2 熔断降级与隔离保障服务不被慢依赖调用拖垮
一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。
MSE Sentinel 提供以下的能力避免慢调用等不稳定因素造成服务不可用:
并发控制(隔离规则):作为一种轻量级隔离的手段,控制某些调用的并发数(即正在进行的数目),防止过多的慢调用占满线程池造成整体不可用。并发控制规则可作为一种重要的保底手段,防止服务被大量慢调用拖垮。
不稳定调用熔断:对不稳定的弱依赖调用进行自动熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。
提前降级:对于一些弱依赖的服务(非关键链路依赖),可以在活动前或资源紧张时进行动态降级,以优先保障重要服务的稳定性。被降级的服务将直接返回给定的 mock 值而不会触发实际调用。
熔断降级特性基于熔断器模式的思想,在服务出现不稳定因素(如响应时间变长,错误率上升)的时候暂时切断服务的调用,等待一段时间再进行渐进式恢复尝试。一方面防止给不稳定服务“雪上加霜”,另一方面保护服务的调用方不被拖垮。目前支持两种熔断策略:基于响应时间(慢调用比例)和基于错误(错误比例/错误数),可以有效地针对各种不稳定的场景进行防护。
注意熔断器模式一般适用于弱依赖调用,即熔断后不影响业务主流程,而并发控制对于弱依赖和强依赖调用均适用。开发者需要设计好降级后的 fallback 逻辑和返回值。另外需要注意的是,即使服务调用方引入了熔断降级机制,我们还是需要在 HTTP 或 RPC 客户端配置请求超时时间,来做一个兜底的防护。
3.3 CloudWeGo 接入 MSE 流量防护示例
接下来我们就以 CloudWeGo Kitex 服务为例,展示如何结合 MSE Sentinel 来保障 CloudWeGo 微服务的运行时稳定性。
首先,我们先在项目中引入 MSE Go SDK,并进行一些简单初始化配置;同时我们也引入 Kitex Sentinel 适配模块,以便将我们的 Kitex 服务接入到 MSE:
import (
sentinel "github.com/alibaba/sentinel-golang/api"
sentinelPlugin "github.com/alibaba/sentinel-golang/pkg/adapters/kitex"
api "github.com/cloudwego/kitex-examples/hello/kitex_gen/api/hello"
mse "github.com/aliyun/aliyun-mse-go-sdk"
)
func main() {
// 初始化 MSE Sentinel;可以通过环境变量或 sentinel.yml 文件配置应用名
err := mse.InitMseDefault()
if err != nil {
log.Fatalf("Failed to init MSE: %+v", err)
}
// 创建 Kitex server 时添加 Sentinel 适配模块中的 middleware
svr := api.NewServer(new(HelloImpl), server.WithMiddleware(sentinelPlugin.SentinelServerMiddleware()))
err = svr.Run()
if err != nil {
log.Println(err.Error())
}
}
接下来我们启动服务 provider,即可在 MSE 控制台看到我们的 Kitex 服务。我们通过 consumer 触发流量访问,然后可以在 MSE 控制台的接口详情页面看到我们 Kitex 服务调用的详细监控信息。
接下来我们针对 Hello:Echo 这个接口方法配置一条单机 QPS 为 10 的流控规则:
配置完成后,规则会实时生效到服务中。稍等片刻我们就可以在接口详情页面看到 Hello:Echo 这个接口方法的单机处理量被限制到了 10 次每秒,同时 consumer 端也会接收到相应的流控 error。
4. 结合 OpenSergo 实现标准化的流量治理
业界微服务治理存在概念不统一、配置形式不统一、能力不统一、多框架统一管控较为复杂等问题。比如我们希望给某个接口配置熔断降级规则,在 Sentinel 中可能需要通过 Sentinel 动态规则的方式进行配置,在 Istio 中可能又是另一套配置方式,在其它组件上可能又是不同的配置。不同框架治理配置方式的不一致使得微服务统一治理管控的复杂度相当高。
基于以上背景,由阿里巴巴、bilibili、CloudWeGo 等企业与社区共同发起的 OpenSergo 项目应运而生。OpenSergo 旨在提供一套开放通用的、面向云原生服务、覆盖微服务及上下游关联组件的微服务治理标准,并根据标准提供一系列的 API 与 SDK 实现。OpenSergo 的最大特点就是以统一的一套配置/DSL/协议定义服务治理规则,面向多语言异构化架构,覆盖微服务框架及上下游关联组件。无论微服务的语言是 Java, Go, Node.js 还是其它语言,无论是标准微服务还是 Mesh 接入,从网关到微服务框架,从数据库到缓存访问,从服务注册发现到配置,开发者都可以通过同一套 OpenSergo CRD 标准配置进行统一的治理管控,而无需关注各框架、语言的差异点,降低异构化、全链路微服务治理管控的复杂度。
在 OpenSergo 中,我们结合 Sentinel 及 MSE 的场景实践对流控降级与容错抽出标准 CRD。一个容错治理规则 (FaultToleranceRule) 由以下三部分组成:
Target: 针对什么样的请求
Strategy: 容错或控制策略,如流控、熔断、并发控制、自适应过载保护、离群实例摘除等
FallbackAction: 触发后的 fallback 行为,如返回某个错误或状态码
以下 YAML CR 示例定义的规则针对 Hello:Echo 这个服务方法(用资源名标识)配置了一条流控策略,全局不超过 10 QPS:
apiVersion: fault-tolerance.opensergo.io/v1alpha1
kind: RateLimitStrategy
metadata:
name: rate-limit-foo
spec:
metricType: RequestAmount
limitMode: Global
threshold: 10
statDuration: "1s"
---
apiVersion: fault-tolerance.opensergo.io/v1alpha1
kind: FaultToleranceRule
metadata:
name: my-fault-tolerance-rule
spec:
selector:
app: foo-app # 规则配置生效的服务名
targets:
- targetResourceName: 'Hello:Echo'
strategies:
- name: rate-limit-foo
# 这里还可以单独定义 fallbackAction,比如自定义返回值或错误;不指定则为默认行为
Sentinel 2.0 将原生支持 OpenSergo 流量治理相关 CRD 配置及能力,结合 Sentinel 提供的各框架的适配模块,让 Dubbo, Spring Cloud Alibaba, gRPC, CloudWeGo 等20+框架能够无缝接入到 OpenSergo 生态中,用统一的 CRD 来配置流量路由、流控降级、服务容错等治理规则。无论是 Java 还是 Go 还是 Mesh 服务,无论是 HTTP 请求还是 RPC 调用,还是数据库 SQL 访问,我们都可以用这统一的容错治理规则 CRD 来给微服务架构中的每一环配置治理,来保障我们服务链路的稳定性。MSE 作为 OpenSergo 微服务治理标准的企业级产品,也将原生支持 OpenSergo spec。
5. 总结与展望
流控降级与容错是微服务流量治理中的重要的一环,同时 MSE 还提供更广范围、更多场景的微服务治理能力,包括全链路灰度、无损上下线、微服务数据库治理、日志治理等一系列的微服务治理能力。服务治理是微服务改造深入到一定阶段之后的必经之路,是将微服务做稳做好的关键。同时我们也在与 CloudWeGo、Kratos、Spring Cloud Alibaba、Dubbo、ShardingSphere 等社区共同建设 OpenSergo 微服务治理标准,将企业与社区中微服务治理的场景与最佳实践共同提取成标准规范,也欢迎更多社区与企业一起参与 OpenSergo 微服务治理标准的共建。欢迎大家加入 OpenSergo 社区交流群(钉钉群)进行讨论:34826335
相关链接: