服務(wù)發(fā)現(xiàn)與配置管理高可用最佳實(shí)踐

本篇是微服務(wù)高可用最佳實(shí)踐系列分享的開(kāi)篇,系列內(nèi)容持續(xù)更新中,期待大家的關(guān)注。
引言
Cloud Native
某客戶在阿里云上使用 K8s 集群部署了許多自己的微服務(wù),但是某一天,其中一臺(tái)節(jié)點(diǎn)的網(wǎng)卡發(fā)生了異常,最終導(dǎo)致服務(wù)不可用,無(wú)法調(diào)用下游,業(yè)務(wù)受損。
ECS 故障節(jié)點(diǎn)上運(yùn)行著 K8s 集群的核心基礎(chǔ)組件 CoreDNS 的所有 Pod,它沒(méi)有打散,導(dǎo)致集群 DNS 解析出現(xiàn)問(wèn)題。
該客戶的服務(wù)發(fā)現(xiàn)使用了有缺陷的客戶端版本(nacos-client 的 1.4.1 版本),這個(gè)版本的缺陷就是跟 DNS 有關(guān)——心跳請(qǐng)求在域名解析失敗后,會(huì)導(dǎo)致進(jìn)程后續(xù)不會(huì)再續(xù)約心跳,只有重啟才能恢復(fù)。
這個(gè)缺陷版本實(shí)際上是已知問(wèn)題,阿里云在 5 月份推送了 nacos-client 1.4.1 存在嚴(yán)重 bug 的公告,但客戶研發(fā)未收到通知,進(jìn)而在生產(chǎn)環(huán)境中使用了這個(gè)版本。


Provider 客戶端在心跳續(xù)約時(shí)發(fā)生 DNS 異常; 心跳線程正確地處理這個(gè) DNS 異常,導(dǎo)致線程意外退出了; 注冊(cè)中心的正常機(jī)制是,心跳不續(xù)約,30 秒后自動(dòng)下線。由于 CoreDNS 影響的是整個(gè) K8s 集群的 DNS 解析,所以 Provider 的所有實(shí)例都遇到相同的問(wèn)題,整個(gè)服務(wù)所有實(shí)例都被下線; 在 Consumer 這一側(cè),收到推送的空列表后,無(wú)法找到下游,那么調(diào)用它的上游(比如網(wǎng)關(guān))就會(huì)發(fā)生異常。
微服務(wù)高可用方案
Cloud Native

雖然看起來(lái)坑很多,但我們依然能夠很好地保障雙十一大促的穩(wěn)定,背后靠的就是成熟穩(wěn)健的高可用體系建設(shè)。
注冊(cè)配置中心在微服務(wù)體系的核心鏈路上,牽一發(fā)動(dòng)全身,任何一個(gè)抖動(dòng)都可能會(huì)較大范圍地影響整個(gè)系統(tǒng)的穩(wěn)定性。
集群高可用
減少上下游依賴(lài)
變更可灰度
服務(wù)可降級(jí)、限流、熔斷
注冊(cè)中心異常負(fù)載的情況下,降級(jí)心跳續(xù)約時(shí)間、降級(jí)一些非核心功能等
針對(duì)異常流量進(jìn)行限流,將流量限制在容量范圍內(nèi),保護(hù)部分流量是可用的
客戶端側(cè),異常時(shí)降級(jí)到使用本地緩存(推空保護(hù)也是一種降級(jí)方案),暫時(shí)犧牲列表更新的一致性,以保證可用性

識(shí)別 —— 可觀測(cè)

MSE注冊(cè)配置中心目前提供的服務(wù)等級(jí)是 99.95%,并且正在向 4 個(gè) 9(99.99%)邁進(jìn)。
快速處理 —— 應(yīng)急響應(yīng)
預(yù)案是指不管熟不熟悉你的系統(tǒng)的人,都可以放心執(zhí)行,這背后需要一套沉淀好有含金量的技術(shù)支撐(技術(shù)厚度)。
從概率角度來(lái)看,無(wú)論風(fēng)險(xiǎn)概率有多低,不斷嘗試,風(fēng)險(xiǎn)發(fā)生的聯(lián)合概率就會(huì)無(wú)限趨近于 1。
架構(gòu)升級(jí),改進(jìn)設(shè)計(jì)
升級(jí)數(shù)據(jù)存儲(chǔ)結(jié)構(gòu),Service 級(jí)粒度提升到到 Instance 級(jí)分區(qū)容錯(cuò)(繞開(kāi)了 Service 級(jí)數(shù)據(jù)不一致造成的服務(wù)掛的問(wèn)題); 升級(jí)連接模型(長(zhǎng)連接),減少對(duì)線程、連接、DNS 的依賴(lài)。
提前發(fā)現(xiàn)風(fēng)險(xiǎn)
這個(gè)「提前」是指在設(shè)計(jì)、研發(fā)、測(cè)試階段盡可能地暴露潛在風(fēng)險(xiǎn); 提前通過(guò)容量評(píng)估預(yù)知容量風(fēng)險(xiǎn)水位是在哪里; 通過(guò)定期的故障演練提前發(fā)現(xiàn)上下游環(huán)境風(fēng)險(xiǎn),驗(yàn)證系統(tǒng)健壯性。

服務(wù)發(fā)現(xiàn)高可用方案
Cloud Native
推空保護(hù)

Provider 端注冊(cè)失敗(比如網(wǎng)絡(luò)、SDKbug 等原因)
注冊(cè)中心判斷 Provider 心跳過(guò)期
Consumer 訂閱到空列表,業(yè)務(wù)中斷報(bào)錯(cuò)
同上
Consumer 訂閱到空列表,推空保護(hù)生效,丟棄變更,保障業(yè)務(wù)服務(wù)可用
開(kāi)啟方式
開(kāi)源的客戶端 nacos-client 1.4.2 以上版本支持
SpingCloudAlibaba 在 spring 配置項(xiàng)里增加:
spring.cloud.nacos.discovery.namingPushEmptyProtection=true
Dubbo 加上 registryUrl 的參數(shù):
namingPushEmptyProtection=true
服務(wù)降級(jí)

容災(zāi)保護(hù)

突發(fā)請(qǐng)求量增加,容量水位較高時(shí),個(gè)別 Provider 發(fā)生故障;
注冊(cè)中心將故障節(jié)點(diǎn)摘除,全量流量會(huì)給剩余節(jié)點(diǎn);
剩余節(jié)點(diǎn)負(fù)載變高,大概率也會(huì)故障;
最后所有節(jié)點(diǎn)故障,100% 無(wú)法提供服務(wù)。
同上;
故障節(jié)點(diǎn)數(shù)達(dá)到保護(hù)閾值,流量平攤給所有機(jī)器;
最終保障 50% 節(jié)點(diǎn)能夠提供服務(wù)。
這套方案曾經(jīng)救過(guò)不少業(yè)務(wù)系統(tǒng)。
離群實(shí)例摘除
但是在特定情況下,心跳存續(xù)并不能完全等同于服務(wù)可用。
因?yàn)槿匀淮嬖谛奶#?wù)不可用的情況,例如:
Request 處理的線程池滿
依賴(lài)的 RDS 連接異?;蚵?SQL
基于異常檢測(cè)的摘除策略:包含網(wǎng)絡(luò)異常和網(wǎng)絡(luò)異常 + 業(yè)務(wù)異常(HTTP 5xx)
設(shè)置異常閾值、QPS 下限、摘除比例下限
無(wú)損下線
配置管理高可用方案
Cloud Native

客戶端高可用
本地目錄分為兩級(jí),高優(yōu)先級(jí)是容災(zāi)目錄、低優(yōu)先級(jí)是緩存目錄。

容災(zāi)目錄的設(shè)計(jì),是因?yàn)橛袝r(shí)候不一定會(huì)有緩存過(guò)的配置,或者業(yè)務(wù)需要緊急覆蓋使用新的內(nèi)容開(kāi)啟一些必要的預(yù)案和配置。
服務(wù)端高可用
在配置中心側(cè),主要是針對(duì)讀、寫(xiě)的限流。
限連接:?jiǎn)螜C(jī)最大連接限流,單客戶端 IP 的連接限流
限寫(xiě)接口:發(fā)布操作&特定配置的秒級(jí)分鐘級(jí)數(shù)量限流
控制操作風(fēng)險(xiǎn)



動(dòng)手實(shí)踐
Cloud Native
場(chǎng)景取自前面提到的一個(gè)高可用方案,在服務(wù)提供者所有機(jī)器發(fā)生注冊(cè)異常的情況下,看服務(wù)消費(fèi)者在推空保護(hù)打開(kāi)的情況下的表現(xiàn)。

部署服務(wù),調(diào)整調(diào)用關(guān)系是網(wǎng)關(guān)->A->B->C,查看網(wǎng)關(guān)調(diào)用成功率。 通過(guò)模擬網(wǎng)絡(luò)問(wèn)題,將應(yīng)用B與注冊(cè)中心的心跳鏈路斷開(kāi),模擬注冊(cè)異常的發(fā)生。 再次查看網(wǎng)關(guān)調(diào)用成功率,期望服務(wù) A->B 的鏈路不受注冊(cè)異常的影響。
最終期望的結(jié)果是,推空保護(hù)開(kāi)關(guān)開(kāi)啟后,能夠幫助應(yīng)用 A 在發(fā)生異常的情況下,繼續(xù)能夠?qū)ぶ返綉?yīng)用B。
環(huán)境準(zhǔn)備

部署應(yīng)用

# A 應(yīng)用 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 應(yīng)用 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 應(yīng)用 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

在網(wǎng)關(guān)注冊(cè)服務(wù)


驗(yàn)證和調(diào)整鏈路
$ curl http://${網(wǎng)關(guān)IP}/ipsc-A[192.168.1.194] --> sc-C[192.168.1.195]

$ curl http://${網(wǎng)關(guān)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://${網(wǎng)關(guān)IP}/ip ;done觀測(cè)調(diào)用


注入故障
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
再次觀測(cè)



小結(jié)
Cloud Native
