如何快速構(gòu)建服務(wù)發(fā)現(xiàn)的高可用能力
作者:十眠
背景
Cloud Native
1 一個真實的案例
全篇從一個真實的案例說起,某客戶在阿里云上使用 Kubernetes 集群部署了許多自己的微服務(wù),由于某臺 ECS 的網(wǎng)卡發(fā)生了異常,雖然網(wǎng)卡異常很快恢復了,但是卻出現(xiàn)了大面積持續(xù)的服務(wù)不可用,業(yè)務(wù)受損。
我們來看一下這個問題鏈是如何形成的?
-
ECS 故障節(jié)點上運行著 Kubernetes 集群的核心基礎(chǔ)組件 CoreDNS 的所有 Pod,且低版本 Kubernetes 集群缺少 NodeLocal DNSCache 的特性,導致集群 DNS 解析出現(xiàn)問題。
-
該客戶的服務(wù)發(fā)現(xiàn)使用了有缺陷的客戶端版本(Nacos-client 的 1.4.1 版本),這個版本的缺陷就是跟 DNS 有關(guān)——心跳請求在域名解析失敗后,會導致進程后續(xù)不會再續(xù)約心跳,只有重啟才能恢復。
-
這
個缺陷版本實際上是已知問題,阿里云在 5 月份推送了 Nacos-client 1.4.1 存在嚴重 bug 的公告,但客戶研發(fā)未收到通知,進而在生產(chǎn)環(huán)境中使用了這個版本。
最終導致故障的原因是服務(wù)無法調(diào)用下游,可用性降低,業(yè)務(wù)受損。下圖示意的是客戶端缺陷導致問題的根因:
-
Provider 客戶端在心跳續(xù)約時發(fā)生 DNS 異常;
-
心跳線程未能正確地處理這個 DNS 異常,導致線程意外退出了;
-
注冊中心的正常機制是,心跳不續(xù)約,30 秒后自動下線。由于 CoreDNS 影響的是整個 Kubernetes 集群的 DNS 解析,所以 Provider 的所有實例都遇到相同的問題,整個服務(wù)所有實例都被下線;
- 在 Consumer 這一側(cè),收到推送的空列表后,無法找到下游,那么調(diào)用它的上游(比如網(wǎng)關(guān))就會發(fā)生異常。
回顧整個案例,每一環(huán)每個風險看起來發(fā)生概率都很小,但是一旦發(fā)生就會造成惡劣的影響。服務(wù)發(fā)現(xiàn)高可用是微服務(wù)體系中很重要的一環(huán),當然也是我們時常忽略的點。在阿里內(nèi)部的故障演練中,這一直是必不可少的一個環(huán)節(jié)。
面向失敗的設(shè)計
Cloud Native
服務(wù)發(fā)現(xiàn)高可用-推空保護
- 默認無侵入支持市面上近五年來的 Spring Cloud 與 Dubbo 框架
- 無關(guān)注冊中心實現(xiàn),無需升級 client 版本
服務(wù)發(fā)現(xiàn)高可用-離群實例摘除
因為仍然存在心跳正常,但服務(wù)不可用的情況,例如:
- Request 處理的線程池滿
-
依賴的 RDS 連接異常導致出現(xiàn)大量慢 SQL
-
某幾臺機器由于磁盤滿,或者是宿主機資源爭搶導致 load 很高
- 默認無侵入,支持市面上近五年來的 Spring Cloud 與 Dubbo 框架
- 無關(guān)注冊中心實現(xiàn),無需升級 client 版本
- 基于異常檢測的摘除策略:包含網(wǎng)絡(luò)異常和網(wǎng)絡(luò)異常 + 業(yè)務(wù)異常(HTTP 5xx)
- 設(shè)置異常閾值、QPS 下限、摘除比例下限
- 摘除事件通知、釘釘群告警
離群實例摘除的能力是一個補充,根據(jù)特定接口的調(diào)用異常特征,來衡量服務(wù)的可用性。
03動手實踐
Cloud Native
1 前提條件
- 已創(chuàng)建 Kubernetes 集群,請參見創(chuàng)建 Kubernetes 托管版集群[1]。
- 已開通 MSE 微服務(wù)治理專業(yè)版,請參見開通 MSE 微服務(wù)治理[2]。
開啟 MSE 微服務(wù)治理
1、開通微服務(wù)治理專業(yè)版:
-
單擊開通 MSE 微服務(wù)治理[3]。
- 微服務(wù)治理版本選擇專業(yè)版,選中服務(wù)協(xié)議,然后單擊立即開通。關(guān)于微服務(wù)治理的計費詳情,請參見價格說明[4]。
2、安裝 MSE 微服務(wù)治理組件:
-
在容器服務(wù)控制臺[5]左側(cè)導航欄中,選擇市場 > 應(yīng)用目錄。
-
在應(yīng)用目錄頁面搜索框中輸入 ack-mse-pilot,單擊搜索圖標,然后單擊組件。
- 在詳情頁面選擇開通該組件的集群,然后單擊創(chuàng)建。安裝完成后,在命名空間 mse-pilotmse-pilot-ack-mse-pilot 應(yīng)用,表示安裝成功。
3、為應(yīng)用開啟微服務(wù)治理:
-
登錄 MSE 治理中心控制臺[6]。
-
在左側(cè)導航欄選擇微服務(wù)治理中心 > Kubernetes 集群列表。
-
在 Kubernetes 集群列表頁面搜索目標集群,單擊搜索圖標,然后單擊目標集群操作列下方的管理。
-
在集群詳情頁面命名空間列表區(qū)域,單擊目標命名空間操作列下方的開啟微服務(wù)治理。
- 在開啟微服務(wù)治理對話框中單擊確認。
部署 Demo 應(yīng)用程序
-
在容器服務(wù)控制臺[5]左側(cè)導航欄中,單擊集群。
-
在集群列表頁面中,單擊目標集群名稱或者目標集群右側(cè)操作列下的詳情。
-
在集群管理頁左側(cè)導航欄中,選擇工作負載 > 無狀態(tài)。
-
在無狀態(tài)頁面選擇命名空間,然后單擊使用 YAML 創(chuàng)建資源。
- 對模板進行相關(guān)配置,完成配置后單擊創(chuàng)建。本文示例中部署 sc-consumer、sc-consumer-empty、sc-provider,使用的是開源的 Nacos。
部署示例應(yīng)用(springcloud)
YAML:
# 開啟推空保護的 sc-consumerapiVersion: apps/v1kind: Deploymentmetadata: name: sc-consumerspec: replicas: 1 selector: matchLabels: app: sc-consumer template: metadata: annotations: msePilotCreateAppName: sc-consumer labels: app: sc-consumer spec: containers: - env: - name: JAVA_HOME value: /usr/lib/jvm/java-1.8-openjdk/jre - name: spring.cloud.nacos.discovery.server-addr value: nacos-server:8848 - name: profiler.micro.service.registry.empty.push.reject.enable value: "true" image: registry.cn-hangzhou.aliyuncs.com/mse-demo-hz/demo:sc-consumer-0.1 imagePullPolicy: Always name: sc-consumer ports: - containerPort: 18091 livenessProbe: tcpSocket: port: 18091 initialDelaySeconds: 10 periodSeconds: 30# 無推空保護的sc-consumer-empty---apiVersion: apps/v1kind: Deploymentmetadata: name: sc-consumer-emptyspec: replicas: 1 selector: matchLabels: app: sc-consumer-empty template: metadata: annotations: msePilotCreateAppName: sc-consumer-empty labels: app: sc-consumer-empty spec: containers: - env: - name: JAVA_HOME value: /usr/lib/jvm/java-1.8-openjdk/jre - name: spring.cloud.nacos.discovery.server-addr value: nacos-server:8848 image: registry.cn-hangzhou.aliyuncs.com/mse-demo-hz/demo:sc-consumer-0.1 imagePullPolicy: Always name: sc-consumer-empty ports: - containerPort: 18091 livenessProbe: tcpSocket: port: 18091 initialDelaySeconds: 10 periodSeconds: 30# sc-provider---apiVersion: apps/v1kind: Deploymentmetadata: name: sc-providerspec: replicas: 1 selector: matchLabels: app: sc-provider strategy: template: metadata: annotations: msePilotCreateAppName: sc-provider labels: app: sc-provider spec: containers: - env: - name: JAVA_HOME value: /usr/lib/jvm/java-1.8-openjdk/jre - name: spring.cloud.nacos.discovery.server-addr value: nacos-server:8848 image: registry.cn-hangzhou.aliyuncs.com/mse-demo-hz/demo:sc-provider-0.3 imagePullPolicy: Always name: sc-provider ports: - containerPort: 18084 livenessProbe: tcpSocket: port: 18084 initialDelaySeconds: 10 periodSeconds: 30# Nacos Server---apiVersion: apps/v1kind: Deploymentmetadata: name: nacos-serverspec: replicas: 1 selector: matchLabels: app: nacos-server template: metadata: labels: app: nacos-server spec: containers: - env: - name: MODE value: standalone image: nacos/nacos-server:latest imagePullPolicy: Always name: nacos-server dnsPolicy: ClusterFirst restartPolicy: Always # Nacos Server Service 配置---apiVersion: v1kind: Servicemetadata: name: nacos-serverspec: ports: - port: 8848 protocol: TCP targetPort: 8848 selector: app: nacos-server type: ClusterIP
我們只需在 Consumer 增加一個環(huán)境變量 profiler.micro.service.registry.empty.push.reject.enable=true,開啟注冊中心的推空保護(無需升級注冊中心的客戶端版本,無關(guān)注冊中心的實現(xiàn),支持 MSE 的 Nacos、eureka、zookeeper 以及自建的 Nacos、eureka、console、zookeeper 等)
分別給 Consumer 應(yīng)用增加 SLB 用于公網(wǎng)訪問
以下分別使用 {sc-consumer-empty} 代表 sc-consumer-empty 應(yīng)用的 slb 的公網(wǎng)地址,{sc-consumer} 代表 sc-consumer 應(yīng)用的 slb 的公網(wǎng)地址。
3 應(yīng)用場景
- 編寫測試腳本
vi curl.sh
while :do result=`curl $1 -s` if [[ "$result" == *"500"* ]]; then echo `date +%F-%T` $result else echo `date +%F-%T` $result fi sleep 0.1done
- 測試,分別開兩個命令行,執(zhí)行如下腳本,顯示如下
- 將 coredns 組件縮容至數(shù)量 0,模擬 DNS 網(wǎng)絡(luò)解析異常場景。
發(fā)現(xiàn)實例與 Nacos 的連接斷開且服務(wù)列表為空。
- 模擬 DNS 服務(wù)恢復,將其擴容回數(shù)量 2。
結(jié)果驗證
2022-01-19-12:02:37 {"timestamp":"2022-01-19T04:02:37.597+0000","status":500,"error":"Internal Server Error","message":"com.netflix.client.ClientException: Load balancer does not have available server for client: mse-service-provider","path":"/user/feign"}2022-01-19-12:02:37 {"timestamp":"2022-01-19T04:02:37.799+0000","status":500,"error":"Internal Server Error","message":"com.netflix.client.ClientException: Load balancer does not have available server for client: mse-service-provider","path":"/user/feign"}2022-01-19-12:02:37 {"timestamp":"2022-01-19T04:02:37.993+0000","status":500,"error":"Internal Server Error","message":"com.netflix.client.ClientException: Load balancer does not have available server for client: mse-service-provider","path":"/user/feign"}
- 只有重啟了 Provider,sc-consumer-empty 才恢復正常
相比之下,sc-consumer 應(yīng)用全流程沒有任何報錯
后續(xù)
我們當發(fā)生推空保護后,我們會上報事件、告警至釘釘群,同時建議配合離群實例摘除使用,推空保護可能會導致 Consumer 持有過多的 Provider 地址,當 Provider 地址為無效地址時,離群實例摘除可以對其進行邏輯隔離,保證業(yè)務(wù)的高可用。
04尾
Cloud Native
-
Provider 客戶端在心跳續(xù)約時發(fā)生 DNS 異常;