某客户在阿里云上使用 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 这一侧,收到推送的空列表后,无法找到下游,那么调用它的上游(比如网关)就会发生异常。
微服务高可用方案
Cloud Native
虽然看起来坑很多,但我们依然能够很好地保障双十一大促的稳定,背后靠的就是成熟稳健的高可用体系建设。
注册配置中心在微服务体系的核心链路上,牵一发动全身,任何一个抖动都可能会较大范围地影响整个系统的稳定性。
集群高可用
减少上下游依赖
变更可灰度
服务可降级、限流、熔断
-
注册中心异常负载的情况下,降级心跳续约时间、降级一些非核心功能等
-
针对异常流量进行限流,将流量限制在容量范围内,保护部分流量是可用的
-
客户端侧,异常时降级到使用本地缓存(推空保护也是一种降级方案),暂时牺牲列表更新的一致性,以保证可用性
识别 —— 可观测
MSE注册配置中心目前提供的服务等级是 99.95%,并且正在向 4 个 9(99.99%)迈进。
快速处理 —— 应急响应
预案是指不管熟不熟悉你的系统的人,都可以放心执行,这背后需要一套沉淀好有含金量的技术支撑(技术厚度)。
从概率角度来看,无论风险概率有多低,不断尝试,风险发生的联合概率就会无限趋近于 1。
架构升级,改进设计
-
升级数据存储结构,Service 级粒度提升到到 Instance 级分区容错(绕开了 Service 级数据不一致造成的服务挂的问题);
-
升级连接模型(长连接),减少对线程、连接、DNS 的依赖。
提前发现风险
-
这个「提前」是指在设计、研发、测试阶段尽可能地暴露潜在风险;
-
提前通过容量评估预知容量风险水位是在哪里;
-
通过定期的故障演练提前发现上下游环境风险,验证系统健壮性。
服务发现高可用方案
Cloud Native
推空保护
-
Provider 端注册失败(比如网络、SDKbug 等原因)
-
注册中心判断 Provider 心跳过期
-
Consumer 订阅到空列表,业务中断报错
-
同上
-
Consumer 订阅到空列表,推空保护生效,丢弃变更,保障业务服务可用
开启方式
开源的客户端 nacos-client 1.4.2 以上版本支持
-
SpingCloudAlibaba 在 spring 配置项里增加:
spring.cloud.nacos.discovery.namingPushEmptyProtection=true
-
Dubbo 加上 registryUrl 的参数:
namingPushEmptyProtection=true
服务降级
容灾保护
-
突发请求量增加,容量水位较高时,个别 Provider 发生故障;
-
注册中心将故障节点摘除,全量流量会给剩余节点;
-
剩余节点负载变高,大概率也会故障;
-
最后所有节点故障,100% 无法提供服务。
-
同上;
-
故障节点数达到保护阈值,流量平摊给所有机器;
-
最终保障 50% 节点能够提供服务。
这套方案曾经救过不少业务系统。
离群实例摘除
但是在特定情况下,心跳存续并不能完全等同于服务可用。
因为仍然存在心跳正常,但服务不可用的情况,例如:
-
Request 处理的线程池满
-
依赖的 RDS 连接异常或慢 SQL
-
基于异常检测的摘除策略:包含网络异常和网络异常 + 业务异常(HTTP 5xx)
-
设置异常阈值、QPS 下限、摘除比例下限
无损下线
配置管理高可用方案
Cloud Native
客户端高可用
本地目录分为两级,高优先级是容灾目录、低优先级是缓存目录。
容灾目录的设计,是因为有时候不一定会有缓存过的配置,或者业务需要紧急覆盖使用新的内容开启一些必要的预案和配置。
服务端高可用
在配置中心侧,主要是针对读、写的限流。
-
限连接:单机最大连接限流,单客户端 IP 的连接限流
-
限写接口:发布操作&特定配置的秒级分钟级数量限流
控制操作风险
动手实践
Cloud Native
场景取自前面提到的一个高可用方案,在服务提供者所有机器发生注册异常的情况下,看服务消费者在推空保护打开的情况下的表现。
-
部署服务,调整调用关系是网关->A->B->C,查看网关调用成功率。
-
通过模拟网络问题,将应用B与注册中心的心跳链路断开,模拟注册异常的发生。
-
再次查看网关调用成功率,期望服务 A->B 的链路不受注册异常的影响。
最终期望的结果是,推空保护开关开启后,能够帮助应用 A 在发生异常的情况下,继续能够寻址到应用B。
环境准备
部署应用
# A 应用 base 版本---apiVersion: apps/v1kind: Deploymentmetadata:labels:app: spring-cloud-aname: spring-cloud-a-bspec:replicas: 2selector:matchLabels:app: spring-cloud-atemplate:metadata:annotations:msePilotCreateAppName: spring-cloud-alabels:app: spring-cloud-aspec:containers:- env:- name: LANGvalue: C.UTF-8- name: spring.cloud.nacos.discovery.server-addrvalue: mse-xxx-nacos-ans.mse.aliyuncs.com:8848- name: spring.cloud.nacos.config.server-addrvalue: mse-xxx-nacos-ans.mse.aliyuncs.com:8848- name: spring.cloud.nacos.discovery.metadata.versionvalue: base- name: spring.application.namevalue: sc-A- name: spring.cloud.nacos.discovery.namingPushEmptyProtectionvalue: "true"image: mse-demo/demo:1.4.2imagePullPolicy: Alwaysname: spring-cloud-aports:- containerPort: 8080protocol: TCPresources:requests:cpu: 250mmemory: 512Mi---apiVersion: apps/v1kind: Deploymentmetadata:labels:app: spring-cloud-aname: spring-cloud-aspec:replicas: 2selector:matchLabels:app: spring-cloud-atemplate:metadata:annotations:msePilotCreateAppName: spring-cloud-alabels:app: spring-cloud-aspec:containers:- env:- name: LANGvalue: C.UTF-8- name: spring.cloud.nacos.discovery.server-addrvalue: mse-xxx-nacos-ans.mse.aliyuncs.com:8848- name: spring.cloud.nacos.config.server-addrvalue: mse-xxx-nacos-ans.mse.aliyuncs.com:8848- name: spring.cloud.nacos.discovery.metadata.versionvalue: base- name: spring.application.namevalue: sc-Aimage: mse-demo/demo:1.4.2imagePullPolicy: Alwaysname: spring-cloud-aports:- containerPort: 8080protocol: TCPresources:requests:cpu: 250mmemory: 512Mi# B 应用 base 版本---apiVersion: apps/v1kind: Deploymentmetadata:labels:app: spring-cloud-bname: spring-cloud-bspec:replicas: 2selector:matchLabels:app: spring-cloud-bstrategy:template:metadata:annotations:msePilotCreateAppName: spring-cloud-blabels:app: spring-cloud-bspec:containers:- env:- name: LANGvalue: C.UTF-8- name: spring.cloud.nacos.discovery.server-addrvalue: mse-xxx-nacos-ans.mse.aliyuncs.com:8848- name: spring.cloud.nacos.config.server-addrvalue: mse-xxx-nacos-ans.mse.aliyuncs.com:8848- name: spring.application.namevalue: sc-Bimage: mse-demo/demo:1.4.2imagePullPolicy: Alwaysname: spring-cloud-bports:- containerPort: 8080protocol: TCPresources:requests:cpu: 250mmemory: 512Mi# C 应用 base 版本---apiVersion: apps/v1kind: Deploymentmetadata:labels:app: spring-cloud-cname: spring-cloud-cspec:replicas: 2selector:matchLabels:app: spring-cloud-ctemplate:metadata:annotations:msePilotCreateAppName: spring-cloud-clabels:app: spring-cloud-cspec:containers:- env:- name: LANGvalue: C.UTF-8- name: spring.cloud.nacos.discovery.server-addrvalue: mse-xxx-nacos-ans.mse.aliyuncs.com:8848- name: spring.cloud.nacos.config.server-addrvalue: mse-xxx-nacos-ans.mse.aliyuncs.com:8848- name: spring.application.namevalue: sc-Cimage: mse-demo/demo:1.4.2imagePullPolicy: Alwaysname: spring-cloud-cports:- containerPort: 8080protocol: TCPresources:requests:cpu: 250mmemory: 512Mi
在网关注册服务
验证和调整链路
$ curl http://${网关IP}/ipsc-A[192.168.1.194] --> sc-C[192.168.1.195]
验证和调整链路
$ curl http://${网关IP}/ipsc-A[192.168.1.194] --> sc-C[192.168.1.195]
再看一下,这时三个应用的调用关系是 ABC,符合我们之前的计划。
$ curl http://${网关IP}/ipsc-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
观测调用
注入故障
kind: NetworkPolicyapiVersion: networking.k8s.io/v1metadata:name: block-registry-from-bspec:podSelector:matchLabels:app: spring-cloud-bingress:- {}egress:- to:- ipBlock:cidr: 0.0.0.0/0ports:- protocol: TCPport: 8080
再次观测
小结
Cloud Native


分类:
已被围观 










我有话说: