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

简介: 很多用户量大并发度高的应用系统为了避免发布过程中的流量有损,一般选择在流量较小的半夜发布,虽然这样做有效果,但不可控导致背后的研发运维成本对企业来说是一笔不小的负担。基于此,阿里云微服务引擎 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

相关实践学习
通过轻量消息队列(原MNS)主题HTTP订阅+ARMS实现自定义数据多渠道告警
本场景将自定义告警信息同时分发至多个通知渠道的需求,例如短信、电子邮件及钉钉群组等。通过采用轻量消息队列(原 MNS)的主题模型的HTTP订阅方式,并结合应用实时监控服务提供的自定义集成能力,使得您能够以简便的配置方式实现上述多渠道同步通知的功能。
相关文章
|
数据库
psc文件文件如何导入数据库
psc文件文件如何导入数据库
455 0
|
9月前
|
安全 Linux 网络安全
Linux系统怎么设置允许root登录
在 Linux 系统中,允许 root 用户 通过 SSH 登录是通过修改 SSH 配置文件 /etc/ssh/sshd_config 中的 PermitRootLogin 参数来实现的。默认情况下,出于安全考虑,很多 Linux 发行版会禁用 root 用户的 SSH 登录。因此,你可以按照以下步骤来允许 root 用户通过 SSH 登录:
2819 0
|
11月前
|
Ubuntu 安全 数据安全/隐私保护
如何在Ubuntu系统下取消sudo的密码输入限制
以上就是如何在Ubuntu系统下取消sudo的密码输入限制的全部内容。探索的旅程是充满乐趣和挑战的,期待下一次与你的相遇,我们将开始新的知识冒险!
898 31
|
10月前
|
人工智能 自然语言处理 JavaScript
专为 Claude Code 设计的基于 YAML 的 Playwright MCP 自动化测试
YAML配置结合Claude Code与Playwright MCP,将自动化测试变得人人可用。通过简洁的YAML语法替代复杂的JavaScript代码,解决传统测试中冗长、硬编码和复用性差等问题。自然语言描述测试步骤,AI解析执行,支持多环境切换与智能报告生成,极大降低技术门槛,提升团队协作效率。无论是开发、QA还是产品经理,都能轻松参与测试流程,真正实现可读、易维护的自动化测试新范式。
3984 3
|
消息中间件 存储 API
微服务间的通信机制
【8月更文第29天】随着微服务架构的普及,服务间的通信变得尤为重要。微服务架构强调将单一应用程序拆分为一组小型服务,每个服务运行在其独立的进程中,并使用轻量级机制(通常是HTTP资源API)进行通信。本文将详细介绍几种流行的微服务间通信方式,包括 RESTful API、gRPC 和消息队列,并探讨它们各自的优缺点。
1022 0
使用pyaudio 录音,停止说话时自动结束
该博客文章介绍了如何使用Python的pyaudio库进行录音,并通过检测声音强度的变化自动结束录音过程。
|
机器学习/深度学习 人工智能 自然语言处理
智能语音交互:AI如何重塑人际沟通###
【10月更文挑战第27天】 一句话 本文将探讨智能语音交互技术如何深刻改变我们的沟通方式,从简单的命令识别到复杂的情感理解和多模态互动,揭示其背后的技术原理与未来趋势。 ###
|
Kubernetes jenkins 持续交付
Kubernetes CI/CD 集成:持续交付的最佳实践
【8月更文第29天】随着微服务架构和容器化的普及,Kubernetes 成为了运行容器化应用的事实标准。为了确保应用能够快速迭代并稳定发布,持续集成/持续部署(CI/CD)流程变得至关重要。本文将介绍如何将 Kubernetes 集成到 CI/CD 流程中,并提供一些最佳实践。
1022 1
|
监控 Java Go
Filebeat的安装和使用(Windows)
Filebeat的安装和使用(Windows)
1640 0
|
缓存 监控 Java
深入剖析JVM的OOM | 内存溢出如何影响JVM运行及应对策略
深入剖析JVM的OOM | 内存溢出如何影响JVM运行及应对策略
107855 1