基于消息队列RocketMQ版实现全链路灰度

通过消息队列这类异步场景下的流量控制,您可以在不需要修改任何业务代码的情况下,实现全链路灰度。本文介绍MSE基于消息队列RocketMQ版实现全链路灰度。

前提条件

Demo架构

本文以容器服务控制台部署应用为例进行说明,您也可以使用kubectl方式部署应用,模拟一个真实的调用链路。Demo应用的结构图如下图所示,应用之间的调用既包含了Spring Cloud的调用,也包含了Dubbo的调用,覆盖了当前最常用的两种微服务框架。其中C应用会生产出RocketMQ消息,由A应用进行消费,A应用在消费消息时,也会发起新的调用。这些应用都是基于Spring Cloud、Dubbo和RocketMQ的标准用法。

调用关系图

步骤一:将应用接入MSE微服务治理

  • 将ACK微服务应用接入MSE治理中心,您可以选择您需要的方式实现应用接入。更多信息,请参见ACK微服务应用接入MSE治理中心

    为ACK命名空间中的应用开启MSE微服务治理

    1. 登录MSE治理中心控制台,并在顶部菜单栏选择地域。

    2. 在左侧导航栏,选择治理中心 > 应用治理

    3. 应用列表页面,单击ACK应用接入

    4. ACK应用接入对话框中,进行配置,配置完成后,单击确定

      image

      配置项

      说明

      集群类型

      选择ACK集群ACK Serverless集群ACS集群

      说明

      如果您尚未授权容器服务调用微服务引擎,则需要单击请授权进行授权。

      集群名称/ID

      选择接入MSE微服务治理的集群名称/ID,可通过关键词搜索。

      ack-onepilot

      显示ack-onepilot接入状态。

      • 如果您未安装ack-onepilot,单击ack-onepilot右侧的点击安装,安装完成后状态会显示为已安装

      • 如果您使用子账号接入,提示没有权限使用时,您可以登录容器服务管理控制台进入目标集群,然后单击运维管理>组件管理,然后找到ack-onepilot,点击安装。

      说明
      • 该步骤接入的组件为ack-onepilot,您可以登录容器服务管理控制台进入目标集群,然后单击运维管理>组件管理查看详情。

      • ack-onepilot安装后会自动注入探针,可能会导致应用启动耗时增加(10s内)。

      接入类型

      选择命名空间接入

      容器集群命名空间

      选择容器集群命名空间

      治理命名空间

      选择治理命名空间。在对应命名空间下重新部署现有应用或新创建的应用,均会接入到MSE微服务治理中。关于命名空间的相关信息,请参见微服务命名空间管理

    为单个应用开启MSE微服务治理

    1. 登录MSE治理中心控制台,并在顶部菜单栏选择地域。

    2. 在左侧导航栏,选择治理中心 > 应用治理

    3. 应用列表页面,单击ACK应用接入

    4. ACK应用接入对话框中,进行配置,配置完成后,单击确定

      image

      配置项

      说明

      集群类型

      选择ACK集群ACK Serverless集群ACS集群

      说明

      如果您尚未授权容器服务调用微服务引擎,则需要单击请授权进行授权。

      集群名称/ID

      选择接入MSE微服务治理的集群名称/ID,可通过关键词搜索。

      ack-onepilot

      显示ack-onepilot接入状态。

      • 如果您未安装ack-onepilot,单击ack-onepilot右侧的点击安装,安装完成后状态会显示为已安装

      • 如果您使用子账号接入,提示没有权限使用时,您可以登录容器服务管理控制台进入目标集群,然后单击运维管理>组件管理,然后找到ack-onepilot,点击安装。

      说明
      • 该步骤接入的组件为ack-onepilot,您可以登录容器服务管理控制台进入目标集群,然后单击运维管理>组件管理查看详情。

      • ack-onepilot安装后会自动注入探针,可能会导致应用启动耗时增加(10s内)。

      接入类型

      选择单个应用接入

      接入步骤

      按照接入步骤进行操作。

      Step 1:进入集群工作负载-无状态应用页面,切换到应用的命名空间下

      Step 2:找到所接入的应用,点击「查看Yaml」

      Step 3:按以下格式编辑Labels,完成后点击「更新」

      spec:
        template:
          metadata:
            labels:
              # 填写“on”表示开启接入,需加上双引号
              msePilotAutoEnable: "on"
              # 填写接入到的治理命名空间,值不存在可自动新建
              mseNamespace: 202401
              # 填写接入MSE的实际应用名称,需加上双引号
              msePilotCreateAppName: "your-deployment-name"

步骤二:部署应用

部署spring-cloud-zuul、spring-cloud-a、spring-cloud-b、spring-cloud-c这四个业务应用,以及注册中心Nacos Server和消息服务RocketMQ Server,您也可以直接在Demo中获取对应的源码。

spring-cloud-zuul应用在收到/A/dubbo的请求时,会把请求转发给spring-cloud-a,然后spring-cloud-a通过Dubbo协议去访问spring-cloud-b,spring-cloud-b也通过Dubbo协议去访问spring-cloud-c,spring-cloud-c在收到请求后,会生产一个消息,并返回自己的环境标签和IP。这些生产出来的消息会由spring-cloud-a应用消费,spring-cloud-a应用在消费消息的时候,会通过spring cloud去调用spring-cloud-b,spring-cloud-b进而通过spring cloud去调用spring-cloud-c,并且将结果输出到自己的日志中。

# 当访问/A/dubbo的时候,返回值为:A[10.25.xx.xx] -> B[10.25.xx.xx] -> C[10.25.xx.xx]
# 同时,A应用在接收到消息之后,输出的日志如下:

2021-12-28 10:58:50.301  INFO 1 --- [essageThread_15] c.a.mse.demo.service.MqConsumer
          : topic:TEST_MQ,producer:C[10.25.xx.xx],invoke result:A[10.25.xx.xx] -> B[10.25.xx.xx] -> C[10.25.xx.xx]
  1. 登录容器服务管理控制台,在左侧导航栏选择集群

  2. 集群列表页面,单击目标集群名称或者目标集群右侧操作列下的详情

  3. 在集群管理页左侧导航栏,选择工作负载 > 无状态

  4. 无状态页面选择命名空间,然后单击使用YAML创建资源。使用如下YAML部署应用:

    展开查看YAML文件

    # 部署Nacos Server
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nacos-server
    spec:
      selector:
        matchLabels:
          app: nacos-server
      template:
        metadata:
          labels:
            app: nacos-server
        spec:
          containers:
            - env:
                - name: MODE
                  value: "standalone"
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/nacos-server:v2.1.2
              imagePullPolicy: IfNotPresent
              name: nacos-server
              ports:
                - containerPort: 8848
    
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: nacos-server
    spec:
      type: ClusterIP
      selector:
        app: nacos-server
      ports:
        - name: http
          port: 8848
          targetPort: 8848
    
    # 部署业务应用
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-zuul
    spec:
      selector:
        matchLabels:
          app: spring-cloud-zuul
      template:
        metadata:
          labels:
            app: spring-cloud-zuul
            msePilotCreateAppName: spring-cloud-zuul
        spec:
          containers:
            - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
                - name: enable.mq.invoke
                  value: 'true'
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-zuul:3.0.1
              imagePullPolicy: Always
              name: spring-cloud-zuul
              ports:
                - containerPort: 20000
    
    ---
    apiVersion: v1
    kind: Service
    metadata:
      annotations:
        service.beta.kubernetes.io/alibaba-cloud-loadbalancer-spec: slb.s1.small
        service.beta.kubernetes.io/alicloud-loadbalancer-address-type: internet
      name: zuul-slb
    spec:
      ports:
        - port: 80
          protocol: TCP
          targetPort: 20000
      selector:
        app: spring-cloud-zuul
      type: LoadBalancer
    status:
      loadBalancer: {}
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-a
    spec:
      selector:
        matchLabels:
          app: spring-cloud-a
      template:
        metadata:
          labels:
            app: spring-cloud-a
            msePilotCreateAppName: spring-cloud-a
        spec:
          containers:
            - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1
              imagePullPolicy: Always
              name: spring-cloud-a
              ports:
                - containerPort: 20001
              livenessProbe:
                tcpSocket:
                  port: 20001
                initialDelaySeconds: 10
                periodSeconds: 30
    
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-b
    spec:
      selector:
        matchLabels:
          app: spring-cloud-b
      template:
        metadata:
          labels:
            app: spring-cloud-b
            msePilotCreateAppName: spring-cloud-b
        spec:
          containers:
            - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-b:3.0.1
              imagePullPolicy: Always
              name: spring-cloud-b
              ports:
                - containerPort: 20002
              livenessProbe:
                tcpSocket:
                  port: 20002
                initialDelaySeconds: 10
                periodSeconds: 30
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-c
    spec:
      selector:
        matchLabels:
          app: spring-cloud-c
      template:
        metadata:
          labels:
            app: spring-cloud-c
            msePilotCreateAppName: spring-cloud-c
        spec:
          containers:
            - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1
              imagePullPolicy: Always
              name: spring-cloud-c
              ports:
                - containerPort: 20003
              livenessProbe:
                tcpSocket:
                  port: 20003
                initialDelaySeconds: 10
                periodSeconds: 30
    ---
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: rockectmq-broker
    spec:
      selector:
        matchLabels:
          app: rockectmq-broker
      template:
        metadata:
          labels:
            app: rockectmq-broker
        spec:
          containers:
            - command:
                - sh
                - mqbroker
                - '-n'
                - 'mqnamesrv:9876'
                - '-c /home/rocketmq/rocketmq-4.9.7/conf/broker.conf'
              env:
                - name: ROCKETMQ_HOME
                  value: /home/rocketmq/rocketmq-4.9.7
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/rocketmq:4.9.7
              imagePullPolicy: Always
              name: rockectmq-broker
              ports:
                - containerPort: 9876
                  protocol: TCP
                - containerPort: 10911
                  protocol: TCP
                - containerPort: 10912
                  protocol: TCP
                - containerPort: 10909
    
    ---
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: rocketmq-name-server
    spec:
      selector:
        matchLabels:
          app: rocketmq-name-server
      template:
        metadata:
          labels:
            app: rocketmq-name-server
        spec:
          containers:
            - command:
                - sh
                - mqnamesrv
              env:
                - name: ROCKETMQ_HOME
                  value: /home/rocketmq/rocketmq-4.9.7
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/rocketmq:4.9.7
              imagePullPolicy: Always
              name: rocketmq-name-server
              ports:
                - containerPort: 9876
                  protocol: TCP
                - containerPort: 10911
                  protocol: TCP
                - containerPort: 10912
                  protocol: TCP
                - containerPort: 10909
                  protocol: TCP
    
    ---
    
    apiVersion: v1
    kind: Service
    metadata:
      name: mqnamesrv
    spec:
      type: ClusterIP
      selector:
        app: rocketmq-name-server
      ports:
        - name: mqnamesrv-9876-9876
          port: 9876
          targetPort: 9876
                            
  5. 执行以下命令,查看应用是否部署成功。

    kubectl get svc,deploy

    预期输出:

    NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
    service/kubernetes     ClusterIP      192.168.xx.xx    <none>         4xx/TCP        7d
    service/mqnamesrv      ClusterIP      192.168.xx.xx    <none>         98xx/TCP       47h
    service/nacos-server   ClusterIP      192.168.xx.xx    <none>         88xx/TCP       47h
    service/zuul-slb       LoadBalancer   192.168.xx.xx   123.56.xx.xx   80:302xxx/TCP   47h
    
    NAME                                   READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/nacos-server           1/1     1            1           4m
    deployment.apps/rockectmq-broker       1/1     1            1           4m
    deployment.apps/rocketmq-name-server   1/1     1            1           5m
    deployment.apps/spring-cloud-a         1/1     1            1           5m
    deployment.apps/spring-cloud-b         1/1     1            1           5m
    deployment.apps/spring-cloud-c         1/1     1            1           5m
    deployment.apps/spring-cloud-zuul      1/1     1            1           5m

步骤三:为应用开启RocketMQ消息灰度

假设spring-cloud-c、spring-cloud-a应用分别为消息的生产者和消费者,为其开启RocketMQ消息灰度。

说明
  • RocketMQ消息灰度功能开启和关闭需要容器服务控制台重新部署应用后才能生效。

  • 消息的生产者和消息的消费者,需要同时开启RocketMQ消息灰度,消息的灰度功能才能生效。

  • 消息类型目前只支持RocketMQ,包含开源版本和阿里云商业版。

    • 如果您使用开源RocketMQ,则RocketMQ Server和RocketMQ Client都需要使用4.5.0及以上版本。

    • 如果您使用阿里云RocketMQ的4.x系列版本,SQL92的过滤方式需要使用铂金版(使用客户端过滤的方式无该限制,使用阿里云RocketMQ的5.x系列版本也无该限制)。

    • 如果您使用的客户端为 Ons Client ,需要使用1.8.0.Final及以上版本。

  • 开启RocketMQ消息灰度后,MSE会修改消息的Consumer Group。例如原来的Consumer Group为group1,环境标签为gray,开启RocketMQ消息灰度后,则group会被修改成group1_gray,如果您使用的是阿里云RocketMQ,请提前创建好group。

  • MSE默认使用SQL92的过滤方式,如果您使用开源RocketMQ,需要在服务端开启SQL92过滤功能(即在broker.conf中配置enablePropertyFilter=true)。

  • 如果您的应用场景不满足支持SQL92过滤的条件,那么可以使用通过FilterMessageHook在消费者过滤的方式,此方式需要在所有的应用中打开消息灰度并且选择客户端过滤方式。因为消费者过滤的方式会在每个环境都处理全量的消息,对消息的生产者和消费者压力都比较大,不推荐在生产中使用此模式。

  1. 登录MSE治理中心控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择治理中心 > 应用治理

  3. 应用列表页面,单击目标应用的资源卡片,然后在左侧导航栏单击流量治理

  4. 单击消息灰度页签,打开开启消息灰度开关,单击确定

    image

  5. 容器服务管理控制台容器服务控制台重新部署应用使配置生效。

步骤四:部署新版本应用

部署新版本的spring-cloud-a-gray、spring-cloud-b-gray和spring-cloud-c-gray应用。

  1. 登录容器服务管理控制台,在左侧导航栏选择集群

  2. 集群列表页面,单击目标集群名称或者目标集群右侧操作列下的详情

  3. 在集群管理页左侧导航栏,选择工作负载 > 无状态

  4. 无状态页面选择命名空间,然后单击使用YAML创建资源。使用如下YAML部署应用:

    展开查看YAML文件

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-a-gray
    spec:
      selector:
        matchLabels:
          app: spring-cloud-a-gray
      template:
        metadata:
          labels:
            alicloud.service.tag: gray
            app: spring-cloud-a-gray
            msePilotCreateAppName: spring-cloud-a
        spec:
          containers:
            - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1
              imagePullPolicy: Always
              name: spring-cloud-a-gray
              ports:
                - containerPort: 20001
              livenessProbe:
                tcpSocket:
                  port: 20001
                initialDelaySeconds: 10
                periodSeconds: 30
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-b-gray
    spec:
      selector:
        matchLabels:
          app: spring-cloud-b-gray
      template:
        metadata:
          labels:
            alicloud.service.tag: gray 
            app: spring-cloud-b-gray
            msePilotCreateAppName: spring-cloud-b
        spec:
          containers:
            - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-b:3.0.1
              imagePullPolicy: Always
              name: spring-cloud-b-gray
              ports:
                - containerPort: 20002
              livenessProbe:
                tcpSocket:
                  port: 20002
                initialDelaySeconds: 10
                periodSeconds: 30
    
    ---
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-c-gray
    spec:
      selector:
        matchLabels:
          app: spring-cloud-c-gray
      template:
        metadata:
          labels:
            alicloud.service.tag: gray
            app: spring-cloud-c-gray
            msePilotCreateAppName: spring-cloud-c
        spec:
          containers:
            - env:
                - name: JAVA_HOME
                  value: /usr/lib/jvm/java-1.8-openjdk/jre
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1
              imagePullPolicy: Always
              name: spring-cloud-c-gray
              ports:
                - containerPort: 20003
              livenessProbe:
                tcpSocket:
                  port: 20003
                initialDelaySeconds: 10
                periodSeconds: 30

步骤五:引入流量并进行验证

  1. 登录MSE治理中心控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择治理中心 > 应用治理

  3. 单击目标应用spring-cloud-a资源卡片。在应用概览页面,可以通过QPS数据观察到所有的流量请求都是去往spring-cloud-a应用未打标的版本,即稳定版本。

  4. 在左侧导航栏,单击流量治理,然后单击标签路由页签,在gray标签的流量规则列下单击添加

    image

  5. 创建标签路由面板,配置流量规则,然后单击确定

    本文示例中设置的流量规则条件为name=xiaoming。关于流量规则的配置,请参见配置标签路由

    image

    流量规则生效后,在应用概览页面可以查看流量分布结果。

  6. 标签路由页签,单击gray标签的目标规则名称。在规则详情面板,单击编辑。在修改标签路由面板,打开是否链路传递的开关,然后单击确定

    说明

    打开链路传递开关后,name=xiaoming的灰度流量能在全链路里进行透传,且无需重复地配置规则。

  7. 登录容器服务管理控制台,在左侧导航栏选择集群

  8. 集群列表页面,单击目标集群名称,然后在左侧导航栏,选择网络 > 服务

  9. 单击zuul-slb服务,然后在zuul-slb的基本信息区域,单击外部 IP 地址(External IP)右侧的地址。

  10. 在服务调用页面输入/A/a?name=xiaoming,然后单击开始调用

    全链路金丝雀发布已经生效。服务调用结果

    此时,spring-cloud-b应用的请求数据如下:

    spring cloud b应用流量曲线

步骤六:调整消息的标签过滤规则,并进行验证

  1. 登录MSE治理中心控制台

  2. 在左侧导航栏,选择治理中心 > 应用治理,然后单击目标应用spring-cloud-a资源卡片。

  3. 在左侧导航栏,单击流量治理,然后单击消息灰度页签,找到未打标环境忽略的标签参数,在标签列表中选择gray,单击确定

    在未打标环境忽略的标签中,选择gray,意味着带着gray环境标签的消息,只能由spring-cloud-a-gray消费,不能由spring-cloud-a来消费。未打标环境忽略的标签参数配置完成后动态生效,您不需要进行重启的操作。

    说明

    默认情况下,未打标节点将消费所有环境的消息,如果需要指定某个未打标节点不消费某个标签环境生产出来的消息,只需配置未打标环境忽略的标签参数即可。

  4. 登录容器服务控制台,查看spring-cloud-a应用的日志,验证配置。

    从下图日志中,可以看到,此时基线环境可以同时消费gray和基线环境生产出来的消息,当配置完未打标环境忽略的标签为 gray 后,基线环境就只消费基线环境生产出来的消息了。

    日志