作者:三辰|阿里云云原生微服务根底架构团队技能专家,担任 MSE 引擎高可用架构
****本篇是微服务高可用最佳实践系列共享的开篇,系列内容继续更新中,期待咱们的重视。
导言
在开端正式内容之前,先给咱们共享一个实在的事例。
某客户在阿里云上运用 K8s 集群布置了许多自己的微服务,可是某一天,其间一台节点的网卡产生了反常,终究导致服务不可用,无法调用下流,事务受损。咱们来看一下这个问题链是怎么形成的?
-
ECS 毛病节点上运转着 K8s 集群的中心根底组件 CoreDNS 的一切 Pod,它没有打散,导致集群 DNS 解析呈现问题。
-
该客户的服务发现运用了有缺点的客户端版别(nacos-client 的 1.4.1 版别),这个版别的缺点就是跟 DNS 有关——心跳恳求在域名解析失败后,会导致进程后续不会再续约心跳,只有重启才能恢复。
-
这个缺点版别实际上是已知问题,阿里云在 5 月份推送了 nacos-client 1.4.1 存在严重 bug 的公告,但客户研制未收到告诉,进而在出产环境中运用了这个版别。
危险环环相扣,缺一不可。
终究导致毛病的原因是服务无法调用下流,可用性下降,事务受损。下图暗示的是客户端缺点导致问题的根因:
-
Provider 客户端在心跳续约时产生 DNS 反常;
-
心跳线程正确地处理这个 DNS 反常,导致线程意外退出了;
-
注册中心的正常机制是,心跳不续约,30 秒后主动下线。由于 CoreDNS 影响的是整个 K8s 集群的 DNS 解析,所以 Provider 的一切实例都遇到相同的问题,整个服务一切实例都被下线;
-
在 Consumer 这一侧,收到推送的空列表后,无法找到下流,那么调用它的上游(比方网关)就会产生反常。
回顾整个事例,每一环每个危险看起来产生概率都很小,可是一旦产生就会形成恶劣的影响。
所以,本篇文章就来讨论,微服务领域的高可用方案怎么规划,细化到服务发现和装备办理领域,都有哪些详细的方案。
微服务高可用方案
首要,有一个事实不容改变:没有任何体系是百分百没有问题的,所以高可用架构方案就是面对失败(危险)规划的。
危险是无处不在的,尽管有许多产生概率很小很小,却都无法彻底防止。
在微服务体系中,都有哪些危险的或许?
这仅仅其间一部分,可是在阿里巴巴内部十几年的微服务实践进程中,这些问题全部都遇到过,并且有些还不止一次。虽然看起来坑许多,但咱们依然能够很好地保证双十一大促的稳定,背面靠的就是成熟稳健的高可用体系建造。
咱们不能彻底防止危险的产生,但咱们能够操控它(的影响),这就是做高可用的实质。
操控危险有哪些战略?
注册装备中心在微服务体系的中心链路上,牵一发起全身,任何一个颤动都或许会较大范围地影响整个体系的稳定性。
战略一:缩小危险影响范围
集群高可用
多副本: 不少于 3 个节点进行实例布置。
多可用区(同城容灾): 将集群的不同节点布置在不同可用区(AZ)中。当节点或可用区产生的毛病时,影响范围仅仅集群其间的一部分,假如能够做到迅速切换,并将毛病节点主动离群,就能尽或许削减影响。
削减上下流依托
体系规划上应该尽或许地削减上下流依托,越多的依托,或许会在被依托体系产生问题时,让全体服务不可用(一般是一个功用块的不可用)。假如有必要的依托,也有必要要求是高可用的架构。
改变可灰度
新版别迭代发布,应该从最小范围开端灰度,按用户、按 Region 分级,逐渐扩大改变范围。一旦呈现问题,也仅仅在灰度范围内形成影响,缩小问题爆破半径。
服务可降级、限流、熔断
-
注册中心反常负载的状况下,降级心跳续约时刻、降级一些非中心功用等
-
针对反常流量进行限流,将流量约束在容量范围内,维护部分流量是可用的
-
客户端侧,反常时降级到运用本地缓存(推空维护也是一种降级方案),暂时牺牲列表更新的一致性,以保证可用性
如图,微服务引擎 MSE 的同城双活三节点的架构,经过精简的上下流依托,每一个都保证高可用架构。多节点的 MSE 实例,经过底层的调度才能,会主动分配到不同的可用区上,组成多副本集群。
战略二:缩短危险产生继续时刻
中心思路就是:尽早辨认、赶快处理
辨认 —— 可观测
例如,依据 Prometheus 对实例进行监控和报警才能建造。
进一步地,在产品层面上做更强的观测才能:包括大盘、告警收敛/分级(辨认问题)、针对大客户的保证、以及服务等级的建造。
MSE注册装备中心目前供给的服务等级是 99.95%,并且正在向 4 个 9(99.99%)迈进。
快速处理 —— 应急呼应
应急呼应的机制要树立,快速有效地告诉到正确的人员范围,快速执行预案的才能(意识到白屏与黑屏的功率差异),常态化地进行毛病应急的演练。
预案是指不管熟不熟悉你的体系的人,都能够放心执行,这背面需求一套沉淀好有含金量的技能支撑(技能厚度)。
战略三:削减触碰危险的次数
削减不必要的发布,例如:添加迭代功率,不随意发布;重要事件、大促期间进行封网。
从概率视点来看,无论危险概率有多低,不断测验,危险产生的联合概率就会无限趋近于 1。
战略四:下降危险产生概率
架构晋级,改进规划
Nacos2.0,不仅是性能做了提高,也做了架构上的晋级:
-
晋级数据存储结构,Service 级粒度提高到到 Instance 级分区容错(绕开了 Service 级数据不一致形成的服务挂的问题);
-
晋级衔接模型(长衔接),削减对线程、衔接、DNS 的依托。
提早发现危险
-
这个「提早」是指在规划、研制、测试阶段尽或许地露出潜在危险;
-
提早经过容量评估预知容量危险水位是在哪里;
-
经过定时的毛病演练提早发现上下流环境危险,验证体系健壮性。
如图,阿里巴巴大促高可用体系,不断做压测演练、验证体系健壮性和弹性、观测追寻体系问题、验证限流、降级等预案的可执行性。
服务发现高可用方案
服务发现包括服务顾客(Consumer)和服务供给者(Provider)。
Consumer 端高可用
经过推空维护、服务降级等手段,达到 Consumer 端的容灾目的。
推空维护
能够应对开头讲的事例,服务空列表推送主动降级到缓存数据。
服务顾客(Consumer)会从注册中心上订阅服务供给者(Provider)的实例列表。
当遇到突发状况(例如,可用区断网,Provider端无法上报心跳) 或 注册中心(变配、重启、升降级)呈现非预期反常时,都有或许导致订阅反常,影响服务顾客(Consumer)的可用性。
无推空维护
-
Provider 端注册失败(比方网络、SDKbug 等原因)
-
注册中心判别 Provider 心跳过期
-
Consumer 订阅到空列表,事务中止报错
敞开推空维护
-
同上
-
Consumer 订阅到空列表,推空维护收效,丢弃改变,保证事务服务可用
敞开方法
敞开方法比较简略
开源的客户端 nacos-client 1.4.2 以上版别支撑
装备项
-
SpingCloudAlibaba 在 spring 装备项里添加:
spring.cloud.nacos.discovery.namingPushEmptyProtection=true
-
Dubbo 加上 registryUrl 的参数:
namingPushEmptyProtection=true
提空维护依托缓存,所以需求耐久化缓存目录,防止重启后丢失,途径为:${user.home}/nacos/naming/${namespaceId}
服务降级
Consumer 端能够依据不同的战略挑选是否将某个调用接口降级,起到对事务恳求流程的维护(将宝贵的下流 Provider 资源保留给重要的事务 Consumer 运用),维护重要事务的可用性。
服务降级的详细战略,包括回来 Null 值、回来 Exception 反常、回来自定义 JSON 数据和自定义回调。
MSE 微服务办理中心中默许就具有该项高可用才能。
Provider 端高可用
Provider 侧经过注册中心和服务办理供给的容灾维护、离群去除、无损下线等方案提高可用性。
容灾维护
容灾维护首要用于防止集群在反常流量下呈现雪崩的场景。
下面咱们来详细看一下:
无容灾维护(默许阈值 =0)
-
突发恳求量添加,容量水位较高时,个别 Provider 产生毛病;
-
注册中心将毛病节点去除,全量流量会给剩余节点;
-
剩余节点负载变高,大概率也会毛病;
-
最后一切节点毛病,100% 无法供给服务。
敞开容灾维护(阈值=0.6)
-
同上;
-
毛病节点数达到维护阈值,流量平摊给一切机器;
-
终究****保证 50% 节点能够供给服务。
容灾维护才能,在紧急状况下,能够保存服务可用性在必定的水平之上,能够说是全体体系的兜底了。
这套方案从前救过不少事务体系。
离群实例去除
心跳续约是注册中心感知实例可用性的基本途径。
可是在特定状况下,心跳存续并不能彻底等同于服务可用。
由于依然存在心跳正常,但服务不可用的状况,例如:
-
Request 处理的线程池满
-
依托的 RDS 衔接反常或慢 SQL
微服务办理中心供给离群实例去除
-
依据反常检测的去除战略:包括网络反常和网络反常 + 事务反常(HTTP 5xx)
-
设置反常阈值、QPS 下限、去除比例下限
离群实例去除的才能是一个补充,依据特定接口的调用反常特征,来衡量服务的可用性。
无损下线
无损下线,又名高雅下线、或许滑润下线,都是一个意思。首要看什么是有损下线:
Provider 实例进行晋级进程中,下线后心跳在注册中心存约以及改变收效都有必定的时刻,在这个期间 Consumer 端订阅列表依然没有更新到下线后的版别,假如鲁莽地将 Provider 停止服务,会形成一部分的流量丢失。
无损下线有许多不同的解决方案,但侵入性最低的仍是服务办理中心默许供给的才能,无感地整合到发布流程中,完结主动执行。免除繁琐的运维脚本逻辑的维护。
装备办理高可用方案
装备办理首要包括装备订阅和装备发布两类操作。
装备办理解决什么问题?
多环境、多机器的装备发布、装备动态实时推送。
依据装备办理做服务高可用
微服务怎么依据装备办理做高可用方案?
发布环境办理
一次办理上百台机器、多套环境,怎么正确无误地推送、误操作或呈现线上问题怎么快速回滚,发布进程怎么灰度。
事务开关动态推送
功用、活动页面等开关。
容灾降级预案的推送
预置的方案经过推送敞开,实时调整流控阈值等。
上图是大促期间装备办理全体高可用解决方案。比方降级非中心事务、功用降级、日志降级、禁用高危险操作。
客户端高可用
装备办理客户端侧同样有容灾方案。
本地目录分为两级,高优先级是容灾目录、低优先级是缓存目录。
缓存目录: 每次客户端和装备中心进行数据交互后,会保存最新的装备内容至本地缓存目录中,当服务端不可用状态下,会运用本地缓存目录中内容。
容灾目录: 当服务端不可用状态下,能够在本地的容灾目录中手动更新装备内容,客户端会优先加载容灾目录下的内容,模仿服务端改变推送的效果。
简略来说,当装备中心不可用时,优先检查容灾目录的装备,否则运用之前拉取到的缓存。
容灾目录的规划,是由于有时候不必定会有缓存过的装备,或许事务需求紧急掩盖运用新的内容敞开一些必要的预案和装备。
全体思路就是,无法产生什么问题,无论怎么,都要能够使客户端能够读取到正确的装备,保证微服务的可用性。
服务端高可用
在装备中心侧,首要是针对读、写的限流。
约束衔接数、约束写:
-
限衔接:单机最大衔接限流,单客户端 IP 的衔接限流
-
限写接口:发布操作&特定装备的秒级分钟级数量限流
操控操作危险
操控人员做装备发布的危险。
装备发布的操作是可灰度、可追溯、可回滚的。
装备灰度
发布前史&回滚
改变对比
着手实践
最后咱们一起来做一个实践。
场景取自前面说到的一个高可用方案,在服务供给者一切机器产生注册反常的状况下,看服务顾客在推空维护翻开的状况下的表现。
实验架构和思路
上图是本次实践的架构,右侧是一个简略的调用场景,外部流量经过网关接入,这儿挑选了 MSE 产品矩阵中的云原生网关,依托它供给的可观测才能,方便咱们调查服务调用状况。
网关的下流有 A、B、C 三个运用,支撑运用装备办理的方法动态地将调用联系衔接起来,后面咱们会实践到。
基本思路:
-
布置服务,调整调用联系是网关->A->B->C,检查网关调用成功率。
-
经过模仿网络问题,将运用B与注册中心的心跳链路断开,模仿注册反常的产生。
-
再次检查网关调用成功率,期望服务 A->B 的链路不受注册反常的影响。
为了方便对照,运用 A 会布置两种版别,一种是敞开推空维护的,一种是没有敞开的状况。终究期望的结果是,推空维护开关敞开后,能够协助运用 A 在产生反常的状况下,继续能够寻址到运用B。
网关的流量打到运用 A 之后,能够调查到,接口的成功率应该正好在 50%。
开端
接下来开端着手实践吧。这儿我选用阿里云 MSE+ACK 组合做完好的方案。
环境预备
首要,购买好一套 MSE 注册装备中心专业版,和一套 MSE 云原生网关。这边不介绍详细的购买流程。
在运用布置前,提早预备好装备。这边咱们能够先装备 A 的下流是 C,B 的下流也是 C。
布置运用
接下来咱们依据 ACK 布置三个运用。能够从下面的装备看到,运用 A 这个版别 spring-cloud-a-b
,推空维护开关现已翻开。
这儿 demo 选用的 nacos 客户端版别是 1.4.2,由于推空维护在这个版别之后才支撑。
装备暗示(无法直接运用):
# A 运用 base 版别
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: spring-cloud-a
name: spring-cloud-a-b
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-a
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-a
labels:
app: spring-cloud-a
spec:
containers:
- env:
- name: LANG
value: C.UTF-8
- name: spring.cloud.nacos.discovery.server-addr
value: mse-xxx-nacos-ans.mse.aliyuncs.com:8848
- name: spring.cloud.nacos.config.server-addr
value: mse-xxx-nacos-ans.mse.aliyuncs.com:8848
- name: spring.cloud.nacos.discovery.metadata.version
value: base
- name: spring.application.name
value: sc-A
- name: spring.cloud.nacos.discovery.namingPushEmptyProtection
value: "true"
image: mse-demo/demo:1.4.2
imagePullPolicy: Always
name: spring-cloud-a
ports:
- containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 250m
memory: 512Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: spring-cloud-a
name: spring-cloud-a
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-a
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-a
labels:
app: spring-cloud-a
spec:
containers:
- env:
- name: LANG
value: C.UTF-8
- name: spring.cloud.nacos.discovery.server-addr
value: mse-xxx-nacos-ans.mse.aliyuncs.com:8848
- name: spring.cloud.nacos.config.server-addr
value: mse-xxx-nacos-ans.mse.aliyuncs.com:8848
- name: spring.cloud.nacos.discovery.metadata.version
value: base
- name: spring.application.name
value: sc-A
image: mse-demo/demo:1.4.2
imagePullPolicy: Always
name: spring-cloud-a
ports:
- containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 250m
memory: 512Mi
# B 运用 base 版别
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: spring-cloud-b
name: spring-cloud-b
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-b
strategy:
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-b
labels:
app: spring-cloud-b
spec:
containers:
- env:
- name: LANG
value: C.UTF-8
- name: spring.cloud.nacos.discovery.server-addr
value: mse-xxx-nacos-ans.mse.aliyuncs.com:8848
- name: spring.cloud.nacos.config.server-addr
value: mse-xxx-nacos-ans.mse.aliyuncs.com:8848
- name: spring.application.name
value: sc-B
image: mse-demo/demo:1.4.2
imagePullPolicy: Always
name: spring-cloud-b
ports:
- containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 250m
memory: 512Mi
# C 运用 base 版别
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: spring-cloud-c
name: spring-cloud-c
spec:
replicas: 2
selector:
matchLabels:
app: spring-cloud-c
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-c
labels:
app: spring-cloud-c
spec:
containers:
- env:
- name: LANG
value: C.UTF-8
- name: spring.cloud.nacos.discovery.server-addr
value: mse-xxx-nacos-ans.mse.aliyuncs.com:8848
- name: spring.cloud.nacos.config.server-addr
value: mse-xxx-nacos-ans.mse.aliyuncs.com:8848
- name: spring.application.name
value: sc-C
image: mse-demo/demo:1.4.2
imagePullPolicy: Always
name: spring-cloud-c
ports:
- containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 250m
memory: 512Mi
布置运用:
在网重视册服务
运用布置好之后,在 MSE 云原生网关中,关联上 MSE 的注册中心,并将服务注册进来。
咱们规划的是网关只调用 A,所以只需求将 A 放进来注册进来即可。
验证和调整链路
依据 curl 指令验证一下链路:
$ curl http://${网关IP}/ip
sc-A[192.168.1.194] --> sc-C[192.168.1.195]
验证一下链路。 能够看到这时候 A 调用的是 C,咱们将装备做一下改变,实时地将 A 的下流改为 B。
再看一下,这时三个运用的调用联系是 ABC,符合咱们之前的方案。
$ curl http://${网关IP}/ip
sc-A[192.168.1.194] --> sc-B[192.168.1.191] --> sc-C[192.168.1.180]
接下来,咱们经过一段指令,接连地调用接口,模仿实在场景下不连续的事务流量。
$ while true; do sleep .1 ; curl -so /dev/null http://${网关IP}/ip ;done
观测调用
经过网关监控大盘,能够调查到成功率。
注入毛病
一切正常,现在咱们能够开端注入毛病。
这儿咱们能够运用 K8s 的 NetworkPolicy 的机制,模仿出口网络反常。
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: block-registry-from-b
spec:
podSelector:
matchLabels:
app: spring-cloud-b
ingress:
- {}
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 8080
这个 8080 端口的意思是,不影响内网调用下流的运用端口,只禁用其它出口流量(比方到达注册中心的 8848 端口就被禁用了)。这儿 B 的下流是 C。
网络切断后,注册中心的心跳续约不上,过一会儿(30 秒后)就会将运用 B 的一切 IP 去除。
再次观测
再调查大盘数据库,成功率开端下降,这时候,在操控台上现已看不到运用 B 的 IP 了。
回到大盘,成功率在 50% 附近不再波动。
小结
经过实践,咱们模仿了一次实在的危险产生的场景,并且经过客户端的高可用方案(推空维护),成功实现了对危险的操控,防止服务调用的产生反常。