15 分钟实现企业级应用无损上下线

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
函数计算FC,每月15万CU 3个月
性能测试 PTS,5000VUM额度
简介: 很多用户量大并发度高的应用系统为了避免发布过程中的流量有损,一般选择在流量较小的半夜发布,虽然这样做有效果,但不可控导致背后的研发运维成本对企业来说是一笔不小的负担。基于此,阿里云微服务引擎 MSE 在应用发布过程中,通过应用下线时进行自适应等待+主动通知,应用上线时就绪检查与微服务生命周期对齐+服务预热等技术手段所提供的微服务应用无损上下线功能,能有效帮助企业规避线上发布所出现的流量资损。

很多用户量大并发度高的应用系统为了避免发布过程中的流量有损,一般选择在流量较小的半夜发布,虽然这样做有效果,但不可控导致背后的研发运维成本对企业来说是一笔不小的负担。基于此,阿里云微服务引擎 MSE 在应用发布过程中,通过应用下线时进行自适应等待+主动通知,应用上线时就绪检查与微服务生命周期对齐+服务预热等技术手段所提供的微服务应用无损上下线功能,能有效帮助企业规避线上发布所出现的流量资损。


无损上下线功能设计


常见的流量有损现象出现的原因包括但不限于以下几种:


  • 服务无法及时下线:服务消费者感知注册中心服务列表存在延时,导致应用下线后在一段时间内服务消费者仍然调用已下线应用造成请求报错。


  • 初始化慢:应用刚启动接收线上流量进行资源初始化加载,由于流量太大,初始化过程慢,出现大量请求响应超时、阻塞、资源耗尽从而造成刚启动应用宕机。


  • 注册太早:服务存在异步资源加载问题,当服务还未初始化完全就被注册到注册中心,导致调用时资源未加载完毕出现请求响应慢、调用超时报错等现象。


  • 发布态与运行态未对齐:使用 Kubernetes 的滚动发布功能进行应用发布,由于Kubernetes 的滚动发布一般关联的就绪检查机制,是通过检查应用特定端口是否启动作为应用就绪的标志来触发下一批次的实例发布,但在微服务应用中只有当应用完成了服务注册才可对外提供服务调用。因此某些情况下会出现新应用还未注册到注册中心,老应用实例就被下线,导致无服务可用。


无损下线


其中的服务无法及时下线问题,如下图 1 所示:


1.png

图1. Spring Cloud 应用消费者无法及时感知提供者服务下线


对于 Spring Cloud 应用,当应用的两个实例 A’ 和 A 中的 A 下线时,由于 Spring Cloud 框架为了在可用性和性能方面做平衡,消费者默认是 30s 去注册中心拉取最新的服务列表,因此 A 实例的下线不能被实时感知,此时消费者继续通过本地缓存继续调用 A 就会出现调用已下线实例出现流量有损。


针对该问题,阿里云微服务引擎 MSE 基于 Java Agent 字节码技术设计实现的无损下线功能如下图 2 所示:

2.png

图2. 无损下线方案


在该套无损下线方案中,服务提供者应用仅需接入 MSE,相比一般的有损下线。应用在下线前会有一段自适应等待时期,该时期待下线应用会通过主动通知的方式,向其在自适应等待阶段发送了请求的服务消费者发送下线事件,消费者接收到下线事件后会主动拉取注册中心服务实例列表以便实时感知应用下线事件避免调用已下线实例造成应用下线流量有损。


无损上线


延迟加载是软件框架设计中最常见的一种策略,例如在 Spring Cloud 框架中 Ribbon 组件的拉取服务列表初始化时机默认是要等到服务的第 1 次调用时刻,例如下图 3 是 Spring Cloud 应用中第 1 次和第 2 次通过 RestTemplate 调用远程服务的请求耗时情况:

3.png图3. 应用启动资源初始化与正常运行过程中耗时情况对比


由测试结果可见,第一次调用由于进行了一些资源初始化,耗时是正常情况的数倍之多。因此把新应用发布到线上直接处理大流量极易出现大量请求响应慢,资源阻塞,应用实例宕机的现象。针对该类大流量下应用资源初始化慢问题,MSE 提供的小流量预热功能通过调节刚上线应用所分配的流量帮助其在进行充分预热后再处理正常流量从而对新实例进行保护。小流量预热过程如下图 4 所示:

4.png

图4. 小流量服务预热过程 QPS 与启动时间关系图


除了针对上述应用第一次调用初始化慢所造成的有损上线问题,MSE 还提供了资源预建连接、延迟注册、确保 Kubernetes 就绪检查通过前完成服务注册和确保 Kubernetes 就绪检查通过前完成服务预热等一整套无损上线手段来满足各类不同应用的无损上线需求,整套方案如图 5 所示:


5.png

image.gif图5. MSE 无损上线方案


如何使用 MSE 的无损上下线


接下来将演示阿里云微服务引擎 MSE 在应用发布时提供的无损上下线和服务预热能力最佳实践。假设应用的架构由 Zuul 网关以及后端的微服务应用实例(Spring Cloud)构成。具体的后端调用链路有购物车应用 A,交易中心应用 B,库存中心应用 C,这些应用中的服务之间通过 Nacos 注册中心实现服务注册与发现。


前提条件


开启 MSE 微服务治理


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


准备工作


注意,本实践所使用的 Agent 目前还在灰度中,需要对应用 Agent 进行灰度升级,升级文档:
https://help.aliyun.com/document_detail/392373.html

应用部署在不同的 Region(暂时仅支持国内 Region)请使用对应的 Agent 下载地址:
http://arms-apm-cn-[regionId].oss-cn-[regionId].aliyuncs.com/2.7.1.3-mse-beta/
注意替换地址中的[RegionId],RegionId 是阿里云 RegionId。

例如 Region 北京 Agent 地址为:
http://arms-apm-cn-beijing.oss-cn-beijing.aliyuncs.com/2.7.1.3-mse-beta/


应用部署流量架构图


6.png图6. 演示应用部署架构


流量压力来源


spring-cloud-zuul 应用中,如图 6 所示,其分别向 spring-cloud-a 的灰度版本和正常版本以 QPS 为 100 的速率同时进行服务调用。


部署 Demo 应用程序


将下面的内容保存到一个文件中,假设取名为 mse-demo.yaml,并执行 kubectl apply -f mse-demo.yaml 以部署应用到提前创建好的 Kubernetes 集群中(注意因为 demo 中有 CronHPA 任务,所以请先在集群中安装 ack-kubernetes-cronhpa-controller 组件,具体在容器服务-Kubernetes->市场->应用目录中搜索组件在测试集群中进行安装),这里我们将要部署 Zuul,A, B 和 C 三个应用,其中 A、B 两个应用分别部署一个基线版本和一个灰度版本,B 应用的基线版本关闭了无损下线能力,灰度版本开启了无损下线能力。C 应用开启了服务预热能力,其中预热时长为 120 秒。


# Nacos Server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nacos-server
  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: registry.cn-shanghai.aliyuncs.com/yizhan/nacos-server:latest
        imagePullPolicy: Always
        name: nacos-server
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
      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
#入口 zuul 应用
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-cloud-zuul
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-cloud-zuul
  template:
    metadata:
      annotations:
        msePilotAutoEnable: "on"
        msePilotCreateAppName: spring-cloud-zuul
      labels:
        app: spring-cloud-zuul
    spec:
      containers:
        - env:
            - name: JAVA_HOME
              value: /usr/lib/jvm/java-1.8-openjdk/jre
            - name: LANG
              value: C.UTF-8
          image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-zuul:1.0.1
          imagePullPolicy: Always
          name: spring-cloud-zuul
          ports:
            - containerPort: 20000
# A 应用 base 版本,开启按照机器纬度全链路透传
---
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
        msePilotAutoEnable: "on"
      labels:
        app: spring-cloud-a
    spec:
      containers:
      - env:
        - name: LANG
          value: C.UTF-8
        - name: JAVA_HOME
          value: /usr/lib/jvm/java-1.8-openjdk/jre
        - name: profiler.micro.service.tag.trace.enable
          value: "true"
        image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-a:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-a
        ports:
        - containerPort: 20001
          protocol: TCP
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
        livenessProbe:
          tcpSocket:
            port: 20001
          initialDelaySeconds: 10
          periodSeconds: 30
# A 应用 gray 版本,开启按照机器纬度全链路透传
---            
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: spring-cloud-a-gray
  name: spring-cloud-a-gray
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-cloud-a-gray
  strategy:
  template:
    metadata:
      annotations:
        alicloud.service.tag: gray
        msePilotCreateAppName: spring-cloud -a
        msePilotAutoEnable: "on"
      labels:
        app: spring-cloud-a-gray
    spec:
      containers:
      - env:
        - name: LANG
          value: C.UTF-8
        - name: JAVA_HOME
          value: /usr/lib/jvm/java-1.8-openjdk/jre
        - name: profiler.micro.service.tag.trace.enable
          value: "true"
        image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-a:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-a-gray
        ports:
        - containerPort: 20001
          protocol: TCP
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
        livenessProbe:
          tcpSocket:
            port: 20001
          initialDelaySeconds: 10
          periodSeconds: 30
# 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
        msePilotAutoEnable: "on"
      labels:
        app: spring-cloud-b
    spec:
      containers:
      - env:
        - name: LANG
          value: C.UTF-8
        - name: JAVA_HOME
          value: /usr/lib/jvm/java-1.8-openjdk/jre
        - name: micro.service.shutdown.server.enable
          value: "false"
        - name: profiler.micro.service.http.server.enable
          value: "false"
        image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-b:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-b
        ports:
        - containerPort: 8080
          protocol: TCP
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
        livenessProbe:
          tcpSocket:
            port: 20002
          initialDelaySeconds: 10
          periodSeconds: 30
# B 应用 gray 版本,默认开启无损下线功能
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: spring-cloud-b-gray
  name: spring-cloud-b-gray
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-cloud-b-gray
  template:
    metadata:
      annotations:
        alicloud.service.tag: gray
        msePilotCreateAppName: spring-cloud-b
        msePilotAutoEnable: "on"
      labels:
        app: spring-cloud-b-gray
    spec:
      containers:
      - env:
        - name: LANG
          value: C.UTF-8
        - name: JAVA_HOME
          value: /usr/lib/jvm/java-1.8-openjdk/jre
        image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-b:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-b-gray
        ports:
        - containerPort: 8080
          protocol: TCP
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
        lifecycle:
            preStop:
              exec:
                command:
                  - /bin/sh
                  - '-c'
                  - >-
                    wget http://127.0.0.1:54199/offline 2>/tmp/null;sleep
                    30;exit 0
        livenessProbe:
          tcpSocket:
            port: 20002
          initialDelaySeconds: 10
          periodSeconds: 30
# 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
        msePilotAutoEnable: "on"
      labels:
        app: spring-cloud-c
    spec:
      containers:
      - env:
        - name: LANG
          value: C.UTF-8
        - name: JAVA_HOME
          value: /usr/lib/jvm/java-1.8-openjdk/jre
        image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-c:0.1-SNAPSHOT
        imagePullPolicy: Always
        name: spring-cloud-c
        ports:
        - containerPort: 8080
          protocol: TCP
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
        livenessProbe:
          tcpSocket:
            port: 20003
          initialDelaySeconds: 10
          periodSeconds: 30
#HPA 配置
---
apiVersion: autoscaling.alibabacloud.com/v1beta1
kind: CronHorizontalPodAutoscaler
metadata:
  labels:
    controller-tools.k8s.io: "1.0"
  name: spring-cloud-b
spec:
   scaleTargetRef:
      apiVersion: apps/v1beta2
      kind: Deployment
      name: spring-cloud-b
   jobs:
   - name: "scale-down"
     schedule: "0 0/5 * * * *"
     targetSize: 1
   - name: "scale-up"
     schedule: "10 0/5 * * * *"
     targetSize: 2
---
apiVersion: autoscaling.alibabacloud.com/v1beta1
kind: CronHorizontalPodAutoscaler
metadata:
  labels:
    controller-tools.k8s.io: "1.0"
  name: spring-cloud-b-gray
spec:
   scaleTargetRef:
      apiVersion: apps/v1beta2
      kind: Deployment
      name: spring-cloud-b-gray
   jobs:
   - name: "scale-down"
     schedule: "0 0/5 * * * *"
     targetSize: 1
   - name: "scale-up"
     schedule: "10 0/5 * * * *"
     targetSize: 2
---
apiVersion: autoscaling.alibabacloud.com/v1beta1
kind: CronHorizontalPodAutoscaler
metadata:
  labels:
    controller-tools.k8s.io: "1.0"
  name: spring-cloud-c
spec:
   scaleTargetRef:
      apiVersion: apps/v1beta2
      kind: Deployment
      name: spring-cloud-c
   jobs:
   - name: "scale-down"
     schedule: "0 2/5 * * * *"
     targetSize: 1 
   - name: "scale-up"
     schedule: "10 2/5 * * * *"
     targetSize: 2
# zuul 网关开启 SLB 暴露展示页面   
---     
apiVersion: v1
kind: Service
metadata:
  name: zuul-slb
spec:
  ports:
    - port: 80
      protocol: TCP
      targetPort: 20000
  selector:
    app: spring-cloud-zuul
  type: ClusterIP
# a 应用暴露 k8s service
---
apiVersion: v1
kind: Service
metadata:
  name: spring-cloud-a-base
spec:
  ports:
    - name: http
      port: 20001
      protocol: TCP
      targetPort: 20001
  selector:
    app: spring-cloud-a
---
apiVersion: v1
kind: Service
metadata:
  name: spring-cloud-a-gray
spec:
  ports:
    - name: http
      port: 20001
      protocol: TCP
      targetPort: 20001
  selector:
    app: spring-cloud-a-gray
# Nacos Server SLB Service 配置
---
apiVersion: v1
kind: Service
metadata:
  name: nacos-slb
spec:
  ports:
  - port: 8848
    protocol: TCP
    targetPort: 8848
  selector:
    app: nacos-server
  type: LoadBalancer


结果验证一:无损下线功能


由于我们对 spring-cloud-b 跟 spring-cloud-b-gray 应用均开启了定时 HPA,模拟每 5 分钟进行一次定时的扩缩容。


7.png


8.png


登录 MSE 控制台,进入微服务治理中心->应用列表->spring-cloud-a->应用详情,从应用监控曲线,我们可以看到 spring-cloud-a 应用的流量数据:


9.png


gray 版本的流量在 pod 扩缩容的过程中请求错误数为 0,无流量损失。未打标的版本由于关闭了无损下线功能,在 pod 扩缩容的过程中有 20 个从 spring-cloud-a 发到 spring-cloud-b 的请求出现报错,发生了请求流量损耗。


结果验证二:服务预热功能


我们在 spring-cloud-c 应用开启了定时 HPA 模拟应用上线过程,每隔 5 分钟做一次伸缩,在扩缩容周期内第 2 分钟第 0 秒缩容到 1 个节点,第 2 分钟第 10 秒扩容到 2 个节点。

10.png


在预热应用的消费端 spring-cloud-b 开启服务预热功能。


11.png


在预热应用的服务提供端 spring-cloud-c 开启服务预热功能。预热时长配置为 120 秒。


12.png


观察节点的流量,发现节点流量缓慢上升。并且能看到节点的预热开始和结束时间,以及相关的事件。


13.png


从上图可以看到开启预热功能的应用重启后的流量会随时间缓慢增加,在一些应用启动过程中需要预建连接池和缓存等资源的慢启动场景,开启服务预热能有效保护应用启动过程中缓存资源有序创建保障应用安全启动从而实现应用上线的流量无损。


方案介绍 & 实操


更多方案设计细节,请点击下方链接观看微服务应用如何实现无损上下线主题直播视频回放:

https://yqh.aliyun.com/live/detail/27936


相关链接


[1]创建 Kubernetes 托管版集群https://help.aliyun.com/document_detail/95108.htm#task-skz-qwk-qfb


[2]开通 MSE 微服务治理

https://help.aliyun.com/document_detail/347625.htm#task-2140253

相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
相关文章
|
1月前
|
存储 监控 负载均衡
|
6月前
|
监控 前端开发 关系型数据库
企业级业务系统的360度立体监控
介绍如何使用ARMS实现对部署在阿里云上的业务系统的360度立体监控。
187 0
企业级业务系统的360度立体监控
|
运维 架构师 前端开发
架构地图-业务架构1
架构地图-业务架构1
|
Kubernetes 负载均衡 Dubbo
微服务应用如何实现无损上下线|学习笔记(一)
快速学习微服务应用如何实现无损上下线
微服务应用如何实现无损上下线|学习笔记(一)
|
Kubernetes 算法 Java
微服务应用如何实现无损上下线|学习笔记(二)
快速学习微服务应用如何实现无损上下线
微服务应用如何实现无损上下线|学习笔记(二)
|
运维 Kubernetes Cloud Native
云原生混部最后一道防线:节点水位线设计
由于混部是一个复杂的技术及运维体系,包括 K8s 调度、OS 隔离、可观测性等等各种技术。今天我们要关注的是混部在单机运行时的最后一道防线——单机水位线设计。
443 3
云原生混部最后一道防线:节点水位线设计
|
缓存 容灾 安全
如何构建一个流量无损的在线应用架构 | 专题尾篇
我们将这些年在每一个环节中的相应解决方案,以产品化的方式沉淀到企业级分布式应用服务(EDAS)中。EDAS 致力于解决在线应用的全流程流量无损,经过 6 年的精细打磨,已经在流量接入与流量服务两个关键位置为我们的客户提供了流量无损的关键能力,我们接下来的主要目标也是将这一能力贯穿应用的全流程,让您的应用默认能具备全流程的流量无损,极力保障商业能力的可持续性。
735 10
如何构建一个流量无损的在线应用架构 | 专题尾篇
|
缓存 运维 负载均衡
如何构建流量无损的在线应用架构 | 专题开篇
本篇是整个《如何构建流量无损的在线应用架构》系列的第一篇,这一系列共三篇,旨在使用最为朴素的语言将影响在线应用流量稳定性的技术问题做一个归类,这些问题的解决方案有的只是一些代码层面的细节,有的需要工具进行配合,有的则需要昂贵的解决方案,如果您的应用想在云上有一个【流量无损】的一站式体验,可以关注阿里云的《企业级分布式应用服务(EDAS)》这个云产品,EDAS 也将会持续向默认接入流量无损的方向演进。
1098 8
如何构建流量无损的在线应用架构 | 专题开篇
|
Kubernetes 关系型数据库 微服务
解决微服务架构下流量有损问题的实践和探索
绝⼤多数的软件应⽤⽣产安全事故发⽣在应⽤上下线发布阶段,尽管通过遵守业界约定俗成的可灰度、可观测和可滚回的安全⽣产三板斧,可以最⼤限度的规避发布过程中由于应⽤⾃身代码问题对⽤户造成的影响。但对于⾼并发⼤流量情况下的短时间流量有损问题却仍然⽆法解决。因此,本文将围绕发布过程中如何解决流量有损问题实现应⽤发布过程中的⽆损上下线效果相关内容展开⽅案介绍。
解决微服务架构下流量有损问题的实践和探索
|
缓存 运维 Kubernetes
微服务应用实现无损上下线最佳实践
本文是阿里云微服务引擎MSE在应用发布时提供的无损上下线和服务预热能力最佳实践介绍。
2072 1
下一篇
无影云桌面