## 前言
微服务的稳定性一直是开发者非常关注的话题。随着业务从单体架构向分布式架构演进以及部署方式的变化,服务之间的依赖关系变得越来越复杂,业务系统也面临着巨大的高可用挑战。
![image](https://user-images.githubusercontent.com/9434884/102049406-bfedbd80-3e1b-11eb-9524-b091ae1b609c.png)
在生产环境中大家可能遇到过各种不稳定的情况,比如:
- 大促时瞬间洪峰流量导致系统超出最大负载,load 飙高,系统崩溃导致用户无法下单
- “黑马”热点商品击穿缓存,DB 被打垮,挤占正常流量
- 调用端被不稳定第三方服务拖垮,线程池被占满,调用堆积,导致整个调用链路卡死
这些不稳定的场景可能会导致严重后果,但很多时候我们又容易忽视这些与流量/依赖相关的高可用防护。大家可能想问:如何预防这些不稳定因素带来的影响?如何针对流量进行高可用的防护?如何保障服务“稳如磐石”?这时候我们就要请出阿里双十一同款的高可用防护中间件 —— Sentinel。在今年刚刚过去的天猫双十一大促中,Sentinel 完美地保障了阿里成千上万服务双十一峰值流量的稳定性,同时 Sentinel Go 版本也在近期正式宣布 GA。下面我们来一起了解下 Sentinel Go 的核心场景以及社区在云原生方面的探索。
## Sentinel 介绍
[Sentinel](https://github.com/alibaba/Sentinel) 是阿里巴巴开源的,面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统自适应保护等多个维度来帮助开发者保障微服务的稳定性。Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀、冷启动、消息削峰填谷、集群流量控制、实时熔断下游不可用服务等,是保障微服务高可用的利器,原生支持 Java/Go/C++ 等多种语言,并且提供 Istio/Envoy 全局流控支持来为 Service Mesh 提供高可用防护的能力。
<img src="https://user-images.githubusercontent.com/9434884/74398114-87730100-4e51-11ea-9267-288cbc71b508.png" alt="Sentinel Golang Logo" height="70%" width="70%">
今年年初,[Sentinel 社区宣布了 Sentinel Go 版本的诞生](https://studygolang.com/topics/10971),为 Go 语言的微服务和基础组件提供高可用防护和容错能力的原生支持,标志着 Sentinel 朝着多元化与云原生迈出了新的一步。在这半年的时间内,社区推出了近 10 个版本,逐步对齐了核心高可用防护和容错能力,同时也在不断扩充开源生态,与 dubbo-go、蚂蚁 MOSN 等开源社区进行共建。
就在近期,[Sentinel Go 1.0 GA 版本](https://github.com/alibaba/sentinel-golang/releases/tag/v1.0.0)正式发布,标志着 Go 版本正式进入生产可用阶段。Sentinel Go 1.0 版本对齐了 Java 版本核心的高可用防护和容错能力,包括限流、流量整形、并发控制、熔断降级、系统自适应保护、热点防护等特性。同时 Go 版本已覆盖主流开源生态,提供了 Gin、gRPC、go-micro、dubbo-go 等[常用微服务框架的适配](https://github.com/sentinel-group/sentinel-go-adapters),并提供了 etcd、Nacos、Consul 等[动态数据源扩展支持](https://github.com/sentinel-group/sentinel-go-datasources)。Sentinel Go 也在朝着云原生的方向不断演进,1.0 版本中也进行了一些云原生方面的探索,包括 Kubernetes CRD data-source, Kubernetes HPA 等。对于 Sentinel Go 版本而言,我们期望的流控场景并不局限于微服务应用本身。云原生基础组件中 Go 语言生态占比较高,而这些云原生组件很多时候又缺乏细粒度、自适应的保护与容错机制,这时候就可以结合组件的一些扩展机制,利用 Sentinel Go 来保护自身的稳定性。
Sentinel 底层通过高性能的滑动窗口进行秒级调用指标统计,结合 token bucket, leaky bucket 和自适应流控算法来透出核心的高可用防护能力。
![sliding-window-with-occupy](https://github.com/sentinel-group/sentinel-website/raw/master/blog/zh-cn/img/sentinel-sliding-window-with-occupy.png)
那么我们如何利用 Sentinel Go 来保证我们微服务的稳定性?下面我们来看几个典型的应用场景。
## 高可用防护的核心场景
### 流量控制与调配
流量是非常随机性的、不可预测的。前一秒可能还风平浪静,后一秒可能就出现流量洪峰了(例如双十一零点的场景)。然而我们系统的容量总是有限的,如果突然而来的流量超过了系统的承受能力,就可能会导致请求处理不过来,堆积的请求处理缓慢,CPU/Load 飙高,最后导致系统崩溃。因此,我们需要针对这种突发的流量来进行限制,在尽可能处理请求的同时来保障服务不被打垮,这就是流量控制。流量控制的场景是非常通用的,像脉冲流量类的场景都是适用的。
通常在 Web 入口或服务提供方(Service Provider)的场景下,我们需要保护服务提供方自身不被流量洪峰打垮。这时候通常根据服务提供方的服务能力进行流量控制,或针对特定的服务调用方进行限制。我们可以结合前期压测评估核心接口的承受能力,配置 QPS 模式的流控规则,当每秒的请求量超过设定的阈值时,会自动拒绝多余的请求。
下面是最简单的一个 Sentinel 限流规则的配置示例:
```go
_, err = flow.LoadRules([]*flow.Rule{
{
Resource: "some-service", // 埋点资源名
Count: 10, // 阈值为 10,默认为秒级维度统计,即该请求单机每秒不超过 10 次
ControlBehavior: flow.Reject, // 控制效果为直接拒绝,不控制请求之间的时间间隔,不排队
},
})
```
### 并发控制与熔断降级
一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
![dependency](https://user-images.githubusercontent.com/9434884/62410811-cd871680-b61d-11e9-9df7-3ee41c618644.png)
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。Sentinel Go 提供以下的能力避免慢调用等不稳定因素造成不可用:
- 并发控制(isolation 模块):作为一种轻量级隔离的手段,控制某些调用的并发数(即正在进行的数目),防止过多的慢调用挤占正常的调用
- 熔断降级(circuitbreaker 模块):对不稳定的弱依赖调用进行自动熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。
Sentinel Go 熔断降级特性基于熔断器模式的思想,在服务出现不稳定因素(如响应时间变长,错误率上升)的时候暂时切断服务的调用,等待一段时间再进行尝试。一方面防止给不稳定服务“雪上加霜”,另一方面保护服务的调用方不被拖垮。Sentinel 支持两种熔断策略:基于响应时间(慢调用比例)和基于错误(错误比例/错误数),可以有效地针对各种不稳定的场景进行防护。
![cb](https://user-images.githubusercontent.com/9434884/62473394-3bc10a00-b7d3-11e9-8998-68d9cf3e01f2.png)
注意熔断器模式一般适用于**弱依赖调用**,即降级后不影响业务主流程,开发者需要设计好降级后的 fallback 逻辑和返回值。另外需要注意的是,即使服务调用方引入了熔断降级机制,我们还是需要在 HTTP 或 RPC 客户端配置请求超时时间,来做一个兜底的防护。
### 热点防护
流量是随机的,不可预测的。为了防止被大流量打垮,我们通常会对核心接口配置限流规则,但有的场景下配置普通的流控规则是不够的。我们来看这样一种场景——大促峰值的时候,总是会有不少“热点”商品,这些热点商品的瞬时访问量非常高。一般情况下,我们可以事先预测一波热点商品,并对这些商品信息进行缓存“预热”,以便在出现大量访问时可以快速返回而不会都打到 DB 上。但每次大促都会涌现出一些“黑马”商品,这些“黑马”商品是我们无法事先预测的,没有被预热。当这些“黑马”商品访问量激增时,大量的请求会击穿缓存,直接打到 DB 层,导致 DB 访问缓慢,挤占正常商品请求的资源池,最后可能会导致系统挂掉。这时候,利用 Sentinel 的[热点参数流量控制](https://github.com/alibaba/sentinel-golang/wiki/热点参数限流)能力,自动识别热点参数并控制每个热点值的访问 QPS 或并发量,可以有效地防止过“热”的参数访问挤占正常的调用资源。
![image](https://user-images.githubusercontent.com/9434884/86080028-82db7c00-bac4-11ea-8f93-11e5c5813686.png)
再比如有的场景下我们希望限制每个用户调用某个 API 的频率,将 API 名称+userId 作为埋点资源名显然是不合适的。这时候我们可以在给 API 埋点的时候通过 `WithArgs(xxx)` 将 userId 作为参数传入到 API 埋点中,然后配置热点规则即可针对每个用户分别限制调用频率;同时,Sentinel 也支持针对某些具体值单独配置限流值,进行精细化流控。像其他规则一样,热点流控规则同样支持通过动态数据源进行动态配置。
Sentinel Go 提供的 RPC 框架整合模块(如 Dubbo、gRPC)均会自动将 RPC 调用的参数列表附带在埋点中,用户可以直接针对相应的参数位置配置热点流控规则。注意如果需要配置具体值限流,受类型系统限制,目前仅支持基本类型和 string 类型。
Sentinel Go 的热点流量控制基于缓存淘汰机制+令牌桶机制实现。Sentinel 通过淘汰机制(如 LRU、LFU、ARC 策略等)来识别热点参数,通过令牌桶机制来控制每个热点参数的访问量。目前的 Sentinel Go 版本采用 LRU 策略统计热点参数,社区也已有贡献者提交了优化淘汰机制的 PR,在后续的版本中社区会引入更多的缓存淘汰机制来适配不同的场景。
### 系统自适应保护
有了以上的流量防护场景,是不是就万事无忧了呢?其实不是的,很多时候我们无法事先就准确评估某个接口的准确容量,甚至无法预知核心接口的流量特征(如是否有脉冲情况),这时候靠事先配置的规则可能无法有效地保护当前服务节点;一些情况下我们可能突然发现机器的 Load 和 CPU usage 等开始飚高,但却没有办法很快的确认到是什么原因造成的,也来不及处理异常。这个时候我们其实需要做的是快速止损,先通过一些**自动化的兜底防护手段**,将濒临崩溃的微服务“拉”回来。针对这些情况,Sentinel Go 提供了一种系统自适应保护规则,结合系统指标和服务容量,自适应动态调整流量。
![BBR](https://user-images.githubusercontent.com/9434884/50813887-bff10300-1352-11e9-9201-437afea60a5a.png)
Sentinel 系统自适应保护策略借鉴了 TCP BBR 算法的思想,结合系统的 Load、CPU 使用率以及服务的入口 QPS、响应时间和并发量等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。系统规则可以作为整个服务的一个兜底防护策略,保障服务不挂,对 CPU 密集型的场景会有比较好的效果。同时,社区也在结合自动化控制理论和强化学习等手段,持续完善自适应流控的效果和适用场景。在未来的版本中,社区会也推出更多试验性的自适应流控策略,来满足更多的可用性场景。
## 云原生探索
云原生是 Sentinel Go 版本演进最为重要的一环。在 GA 的过程中,Sentinel Go 社区也在 Kubernetes 和 Service Mesh 等场景下进行了一些探索。
### Kubernetes CRD data-source
在生产环境中我们一般都需要通过配置中心来动态管理各种规则配置。在 Kubernetes 集群中,我们可以天然利用 Kubernetes CRD 的方式来管理应用的 Sentinel 规则。在 Go 1.0.0 版本中社区提供了基本的 Sentinel 规则 CRD 抽象以及相应的[数据源实现](https://github.com/sentinel-group/sentinel-go-datasource-k8s-crd)。用户只需要先导入 Sentinel 规则 CRD 定义文件,接入 Sentinel 时注册对应的 data-source,然后按照 CRD 定义的格式编写 YAML 配置并 kubectl apply 到对应的 namespace 下即可实现动态配置规则。以下是一个流控规则的示例:
```yaml
apiVersion: datasource.sentinel.io/v1alpha1
kind: FlowRules
metadata:
name: foo-sentinel-flow-rules
spec:
rules:
- resource: simple-resource
threshold: 500
- resource: something-to-smooth
threshold: 100
controlBehavior: Throttling
maxQueueingTimeMs: 500
- resource: something-to-warmup
threshold: 200
tokenCalculateStrategy: WarmUp
controlBehavior: Reject
warmUpPeriodSec: 30
warmUpColdFactor: 3
```
Kubernetes CRD data-source 模块地址:https://github.com/sentinel-group/sentinel-go-datasource-k8s-crd
后续社区会进一步完善 Rule CRD 定义并与其它社区一起探讨高可用防护相关的标准抽象。
### Service Mesh
Service Mesh 是微服务向云原生演进的趋势之一。在 Service Mesh 架构下,一些服务治理和策略控制的能力都逐渐下沉到了 data plane 层。去年 Sentinel 社区在 Java 1.7.0 版本里面做了一些尝试,提供了 Envoy Global Rate Limiting gRPC Service 的实现 —— [Sentinel RLS token server](https://github.com/alibaba/Sentinel/tree/master/sentinel-cluster/sentinel-cluster-server-envoy-rls),借助 Sentinel 集群限流 token server 来为 Envoy 服务网格提供集群流量控制的能力。今年随着 Sentinel Go 版本的诞生,社区与更多的 Service Mesh 产品开展合作、整合。我们与蚂蚁的 MOSN 社区进行共建,在 MOSN Mesh 中原生支持了 Sentinel Go 的流控降级能力,同时也已在蚂蚁内部落地。社区也在探索更为通用的方案,如利用 Istio 的 Envoy WASM 扩展机制实现 Sentinel 插件,让 Istio/Envoy 服务网格可以借助 Sentinel 原生的流控降级与自适应保护的能力来保障整个集群服务的稳定性。
![sentinel-and-mesh](https://user-images.githubusercontent.com/9434884/102049584-1c50dd00-3e1c-11eb-8675-45ac1b8a7c53.png)
### Kubernetes HPA based on Sentinel metrics
保障服务稳定性的方法多种多样,除了各种规则对流量进行“控制”之外,“弹性”也是一种思路。对于部署在 Kubernetes 中的应用来说,可以利用 Kubernetes HPA 能力进行对服务进行水平扩缩容。HPA 默认支持多种系统指标,并且支持自定义指标统计。目前我们已经在阿里云 Kubernetes 容器服务上结合 [AHAS Sentinel](https://help.aliyun.com/document_detail/101132.html) 支持基于服务的平均 QPS、响应时间等作为条件进行弹性伸缩。社区也正在这一块做一些尝试,将一些 Sentinel 的服务级别的指标统计(通过量,拒绝量,响应时间等)通过 Prometheus 或 OpenTelemetry 等标准方式透出,并适配到 Kubernetes HPA 中。
![k8s-hpa-sentinel](https://user-images.githubusercontent.com/9434884/102049601-270b7200-3e1c-11eb-9458-691a7a90c599.png)
当然基于 Sentinel 的弹性方案不是万灵药,它只适用于一些特定的场景,比如适用于启动速度快的无状态服务(Serverless 场景)。对于启动较慢的服务,或非本服务容量问题的场景(如依赖的 DB 容量不够),弹性的方案不能很好地解决稳定性的问题,甚至可能会加剧服务的恶化。
## Let's start hacking!
了解了以上的高可用防护的场景,以及 Sentinel 在云原生方向的一些探索,相信大家对微服务容错与稳定性的手段有了新的体会。欢迎大家动手玩一下 demo,将微服务接入 Sentinel 来享受高可用防护和容错的能力,让服务“稳如磐石”。同时 Sentinel Go 1.0 GA 版本的发布离不开社区的贡献,感谢所有参与贡献的小伙伴们。本次 GA 我们也新加入了两位给力的 committer <a href="/user/sanxun0325" title="@sanxun0325">@sanxun0325</a> @luckyxiaoqiang,两位在 1.0 版本的演进带来了 Warm Up 流控、Nacos 动态数据源以及一系列功能改进和性能优化,非常积极地帮助社区答疑解惑以及 review 代码。恭喜两位!社区在未来版本中也会朝着云原生和自适应智能化的方向不断探索和演进,也欢迎更多的同学加入贡献小组,一起参与 Sentinel 未来的演进,创造无限可能。我们鼓励任何形式的贡献,包括但不限于:
- bug fix
- new features/improvements
- dashboard
- document/website
- test cases
开发者可以在 GitHub 上面的 good first issue 列表上挑选感兴趣的 issue 来参与讨论和贡献。我们会重点关注积极参与贡献的开发者,核心贡献者会提名为 Committer,一起主导社区的发展。我们也欢迎大家有任何问题和建议,都可以通过 GitHub issue、Gitter 或钉钉群(群号:30150716)等渠道进行交流。Now start hacking!
- Sentinel Go repo: https://github.com/alibaba/sentinel-golang
- 企业用户欢迎进行登记:https://github.com/alibaba/Sentinel/issues/18
- Sentinel 阿里云企业版:https://ahas.console.aliyun.com/
有疑问加站长微信联系(非本文作者)