作者:十眠

背景

注册核心作为承当服务注册发现的外围组件,是微服务架构中必不可少的一环。在 CAP 的模型中,注册核心能够就义一点点数据一致性(C),即同一时刻每一个节点拿到的服务地址容许短暂的不统一,但必须要保障可用性(A)。因为一旦因为某些问题导致注册核心不可用,或者服务连不上注册核心,那么想要连贯他的节点可能会因为无奈获取服务地址而对整个零碎呈现灾难性的打击。

一个实在的案例

全篇从一个实在的案例说起,某客户在阿里云上应用 Kubernetes 集群部署了许多本人的微服务,因为某台 ECS 的网卡产生了异样,尽管网卡异样很快复原了,然而却呈现了大面积继续的服务不可用,业务受损。

咱们来看一下这个问题链是如何造成的?

  1. ECS 故障节点上运行着 Kubernetes 集群的外围根底组件 CoreDNS 的所有 Pod,且低版本 Kubernetes 集群短少 NodeLocal DNSCache 的个性,导致集群 DNS 解析呈现问题。
  2. 该客户的服务发现应用了有缺点的客户端版本(Nacos-client 的 1.4.1 版本),这个版本的缺点就是跟 DNS 无关——心跳申请在域名解析失败后,会导致过程后续不会再续约心跳,只有重启能力复原。
  3. 这个缺点版本实际上是已知问题,阿里云在 5 月份推送了 Nacos-client 1.4.1 存在重大 bug 的布告,但客户研发未收到告诉,进而在生产环境中应用了这个版本。

危险环环相扣,缺一不可。

最终导致故障的起因是服务无奈调用上游,可用性升高,业务受损。下图示意的是客户端缺点导致问题的根因:

  1. Provider 客户端在心跳续约时产生 DNS 异样;
  2. 心跳线程未能正确地解决这个 DNS 异样,导致线程意外退出了;
  3. 注册核心的失常机制是,心跳不续约,30 秒后主动下线。因为 CoreDNS 影响的是整个 Kubernetes 集群的 DNS 解析,所以 Provider 的所有实例都遇到雷同的问题,整个服务所有实例都被下线;
  4. 在 Consumer 这一侧,收到推送的空列表后,无奈找到上游,那么调用它的上游(比方网关)就会产生异样。

回顾整个案例,每一环每个危险看起来产生概率都很小,然而一旦产生就会造成顽劣的影响。服务发现高可用是微服务体系中很重要的一环,当然也是咱们时常疏忽的点。在阿里外部的故障演练中,这始终是必不可少的一个环节。

面向失败的设计

因为网络环境的抖动比方 CoreDns 的异样,或者是因为某些因素导致咱们的注册核心不可用等状况,常常会呈现服务批量闪断的状况,但这种状况其实不是业务服务的不可用,如果咱们的微服务能够辨认到这是一种异常情况(批量闪断或地址变空时),应该采取一种激进的策略,免得误推从而导致全副服务呈现"no provider"的问题,会导致所有的微服务不可用的故障,并且继续较长时间难以复原。

站在微服务角度上思考,咱们如何能够切段以上的问题链呢?以上的案例看起来是 Nacos-client 低版本造成的问题,然而如果咱们用的是 zookeeper、eureka 等注册核心呢?咱们能拍着胸脯说,不会产生以上的问题吗?面向失败的设计准则通知咱们,如果注册核心挂掉了,或者咱们的服务连不上注册核心了,咱们须要有一个形式保障咱们的服务失常调用,线上的业务继续一直。

本文介绍的是服务发现过程中的高可用的机制,从服务框架层面思考如何彻底解决以上的问题。

服务发现过程中的高可用原理解析

服务发现高可用-推空爱护

面向失败的设计通知咱们,服务并不能齐全置信注册核心的告诉的地址,当注册核心的推送地址为空时候,服务调用必定会出 no provider 谬误,那么咱们就疏忽此次推送的地址变更。

微服务治理核心提供推空爱护能力

  • 默认无侵入反对市面上近五年来的 Spring Cloud 与 Dubbo 框架
  • 无关注册核心实现,无需降级 client 版本

服务发现高可用-离群实例摘除

心跳续约是注册核心感知实例可用性的根本路径。然而在特定状况下,心跳存续并不能齐全等同于服务可用。
因为依然存在心跳失常,但服务不可用的状况,例如:

  • Request 解决的线程池满
  • 依赖的 RDS 连贯异样导致呈现大量慢 SQL
  • 某几台机器因为磁盘满,或者是宿主机资源争抢导致 load 很高

此时服务并不能齐全置信注册核心的告诉的地址,推送的地址中,可能存在一些服务质量低下的服务提供者,因而客户端须要本人依据调用的后果来判断服务地址的可用性与提供服务质量的好坏,来定向疏忽某些地址。

微服务治理核心提供离群实例摘除

  • 默认无侵入,反对市面上近五年来的 Spring Cloud 与 Dubbo 框架
  • 无关注册核心实现,无需降级 client 版本
  • 基于异样检测的摘除策略:蕴含网络异样和网络异样 + 业务异样(HTTP 5xx)
  • 设置异样阈值、QPS 上限、摘除比例上限
  • 摘除事件告诉、钉钉群告警

离群实例摘除的能力是一个补充,依据特定接口的调用异样特色,来掂量服务的可用性。

入手实际

前提条件

  • 已创立 Kubernetes 集群,请参见创立 Kubernetes 托管版集群 [1]
  • 已开明 MSE 微服务治理专业版,请参见开明 MSE 微服务治理 [2]

筹备工作

开启 MSE 微服务治理

1、开明微服务治理专业版:

  1. 单击开明 MSE 微服务治理 [3]
  2. 微服务治理版本抉择专业版,选中服务协定,而后单击立刻开明。对于微服务治理的计费详情,请参见价格阐明 [4]

2、装置 MSE 微服务治理组件:

  1. 在容器服务控制台 [5] 左侧导航栏中,抉择市场 > 利用目录
  2. 利用目录页面搜寻框中输出 ack-mse-pilot,单击搜寻图标,而后单击组件。
  3. 详情页面抉择开明该组件的集群,而后单击创立。装置实现后,在命名空间 mse-pilotmse-pilot-ack-mse-pilot 利用,示意装置胜利。

3、为利用开启微服务治理:

  1. 登录 MSE 治理核心控制台 [6]
  2. 在左侧导航栏抉择微服务治理核心 > Kubernetes 集群列表
  3. Kubernetes 集群列表页面搜寻指标集群,单击搜寻图标,而后单击指标集群操作列下方的治理
  4. 集群详情页面命名空间列表区域,单击指标命名空间操作列下方的开启微服务治理
  5. 开启微服务治理对话框中单击确认

部署 Demo 应用程序

  1. 在容器服务控制台 [5] 左侧导航栏中,单击集群
  2. 集群列表页面中,单击指标集群名称或者指标集群右侧操作列下的详情
  3. 在集群治理页左侧导航栏中,抉择工作负载 > 无状态
  4. 无状态页面抉择命名空间,而后单击应用 YAML 创立资源
  5. 对模板进行相干配置,实现配置后单击创立。本文示例中部署 sc-consumer、sc-consumer-empty、sc-provider,应用的是开源的 Nacos。

部署示例利用(springcloud)

YAML:

    # 开启推空爱护的 sc-consumer    apiVersion: apps/v1    kind: Deployment    metadata:      name: sc-consumer    spec:      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/v1    kind: Deployment    metadata:      name: sc-consumer-empty    spec:      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/v1    kind: Deployment    metadata:      name: sc-provider    spec:      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/v1    kind: Deployment    metadata:      name: nacos-server    spec:      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: v1    kind: Service    metadata:      name: nacos-server    spec:      ports:      - port: 8848        protocol: TCP        targetPort: 8848      selector:        app: nacos-server      type: ClusterIP

咱们只需在 Consumer 减少一个环境变量 profiler.micro.service.registry.empty.push.reject.enable=true,开启注册核心的推空爱护(无需降级注册核心的客户端版本,无关注册核心的实现,反对 MSE 的 Nacos、eureka、zookeeper 以及自建的 Nacos、eureka、console、zookeeper 等)

别离给 Consumer 利用减少 SLB 用于公网拜访

以下别离应用 {sc-consumer-empty} 代表 sc-consumer-empty 利用的 slb 的公网地址,{sc-consumer} 代表 sc-consumer 利用的 slb 的公网地址。

利用场景

上面通过上述筹备的 Demo 来别离实际以下场景

  • 编写测试脚本

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.1    done
  • 测试,别离开两个命令行,执行如下脚本,显示如下

% sh curl.sh {sc-consumer-empty}:18091/user/rest2022-01-19-11:58:12 Hello from [18084]10.116.0.142!2022-01-19-11:58:12 Hello from [18084]10.116.0.142!2022-01-19-11:58:12 Hello from [18084]10.116.0.142!2022-01-19-11:58:13 Hello from [18084]10.116.0.142!2022-01-19-11:58:13 Hello from [18084]10.116.0.142!2022-01-19-11:58:13 Hello from [18084]10.116.0.142!

% sh curl.sh {sc-consumer}:18091/user/rest2022-01-19-11:58:13 Hello from [18084]10.116.0.142!2022-01-19-11:58:13 Hello from [18084]10.116.0.142!2022-01-19-11:58:13 Hello from [18084]10.116.0.142!2022-01-19-11:58:14 Hello from [18084]10.116.0.142!2022-01-19-11:58:14 Hello from [18084]10.116.0.142!2022-01-19-11:58:14 Hello from [18084]10.116.0.142!

并放弃脚本始终在调用,察看 MSE 控制台别离看到如下状况


  • 将 coredns 组件缩容至数量 0,模仿 DNS 网络解析异样场景。

发现实例与 Nacos 的连贯断开且服务列表为空。

  • 模仿 DNS 服务复原,将其扩容回数量 2。

后果验证

在以上过程中放弃继续的业务流量,咱们发现 sc-consumer-empty 服务呈现大量且继续的报错

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"}

相比之下,sc-consumer 利用全流程没有任何报错

  • 只有重启了 Provider,sc-consumer-empty 才恢复正常

相比之下,sc-consumer 利用全流程没有任何报错

后续

咱们当产生推空爱护后,咱们会上报事件、告警至钉钉群,同时倡议配合离群实例摘除应用,推空爱护可能会导致 Consumer 持有过多的 Provider 地址,当 Provider 地址为有效地址时,离群实例摘除能够对其进行逻辑隔离,保障业务的高可用。

保障云上业务的永远在线,是 MSE 始终在谋求的指标,本文通过面向失败设计的服务发现高可用能力的分享,以及 MSE 的服务治理能力疾速构建起服务发现高可用能力的演示,模仿了线上不可预期的服务发现相干异样产生时的影响以及咱们如何预防的伎俩,展现了一个简略的开源微服务利用应该如何构建起服务发现高可用能力。

相干链接

[1] 创立 Kubernetes 托管版集群

https://help.aliyun.com/docum...

[2] 开明 MSE 微服务治理

https://help.aliyun.com/docum...

[3] 开明 MSE 微服务治理

https://common-buy.aliyun.com...

[4] 价格阐明

https://help.aliyun.com/document_detail/170443.htm#concept-2519524

[5] 容器服务控制台

https://cs.console.aliyun.com

[6] MSE 治理核心控制台

https://mse.console.aliyun.com

点击此处,返回 MSE 官网查看更多!