Apollo 源码更改实现多灰度

因项目线需求多人迭代开发时,环境区分,以及为以后的灰度发布做准备,[Apollo]{.label .primary} 单灰度配置已经不满足当前快速开发业务需求,所以需要对其调整。特此记录。本文将在本地重新拉一套全新的源码进行实现讲解

部署并启动 Apollo

目标:部署并运行至少拥有一个环境的 [Apollo]{.label .info} 服务

需要:拥有 [apolloconfigdb]{.label .primary} 、[apolloportaldb]{.label .primary} 两个数据库。启动 一个 [apollo-configservice]{.label .primary} 和一个 [apollo-adminservice]{.label .primary} 应用组成一个 环境,再启动一个 [apollo-portal]{.label .primary} 应用

[apollo-portal] {.label .info} 是一个浏览器访问的 [web]{.label .info} 界面。用于 [Apollo]{.label .info}上环境配置的可视化管理。主要负责 [Apollo]{.label .info}的账号体系及应用,配置文件权限功能。对应 [apolloconfigdb]{.label .info} 数据库

[apollo-configservice]{.label .info} 是一个后台服务端,应用为客户端提供配置读取等接口,内部集成 [Eureka]{.label .info}并将自身服务注册进去。用于部署多个 [apollo-configservice]{.label .info} 实现负载均衡。对应 [apolloconfigdb]{.label .info}数据库。

[apollo-adminservice]{.label .info} 是一个后台服务器,应用为 [apollo-portal]{.label .info} 应用提供访问集群,配置项,发布历史,提交记录等应用配置操作接口,对应 [apolloconfigdb]{.label .info} 数据库。并自身注册到同环境下的 [apollo-configservice]{.label .info} 服务内的 [Eureka]{.label .info}上。部署多个可实现负载均衡。

下载 Apollo 源码

git clone https://github.com/ctripcorp/apollo.git

创建数据库

在下载的源码根目录下的 scripts/sql 目录下面有两份 sql 。通过 [Mysql]{.label .primary}数据库导入生成 [apolloportaldb]{.label .primary} [apolloconfigdb]{.label .primary} 两个数据库

因为我只需要一个环境的 [apolloconfigdb]{.label .info} 数据库所以我只导入创建了一个

配置启动 apollo-configservice 和 apollo-adminservice

因为数据源信息一样,我这里使用了根目录下的 [apollo-assembly]{.label .primary} 模块下的 [ApolloApplication]{.label .primary} 启动类一起启动,一起占用 [8080]{.label .primary} 端口

配置 [ApolloApplication]{.label .primary} 启动类

image-20210602000414401

[Environment]{.label .primary} 内的配置

# VM options:
-Dspring.datasource.url=jdbc:mysql://localhost:3306/apolloconfigdb?useSSL=false&serverTimezone=Asia/Shanghai
-Dspring.datasource.username=root
-Dspring.datasource.password=123456

# Program arguments:
--configservice --adminservice

启动 [ApolloApplication]{.label .primary}。可访问 [http://你的ip:8080]{.label .primary} 进入 [Eureka]{.label .primary} 查看

image-20210602000958292

至此 [apollo-configservice]{.label .primary} 和 [apollo-adminservice]{.label .primary} 启动完成

配置启动 apollo-portal

配置 [PortalApplication]{.label .primary} 启动类

image-20210602001456234

[Environment]{.label .primary} 内的配置

# VM options:
-Dspring.datasource.url=jdbc:mysql://localhost:3306/apolloportaldb?useSSL=false&serverTimezone=Asia/Shanghai
-Dspring.datasource.username=root
-Dspring.datasource.password=123456
-Ddev_meta=http://127.0.0.1:8080/

更改 [apollo-core]{.label .primary} 模块内的 [com.ctrip.framework.apollo.core.internals.LegacyMetaServerProvider]{.label .primary} 文件的 [initialize]{.label .primary} 方法

image-20210602001845632

保留一条 [DEV]{.label .primary} 环境的。

[getMetaServerAddress]{.label .info} 方法参数中 [dev_meta]{.label .info} 指的是获取环境配置 [dev_meta]{.label .info}的值,[dev.meta]{.label .info} 是指从 [apollo-portal]{.label .info} 模块内的 [resources]{.label .info} 目录下的 [apollo-env.properties]{.label .info} 文件内读取。

启动 [PortalApplication]{.label .primary} 启动类。 访问 [http://你的ip:8090]{.label .primary} 进入 [Apollo]{.label .primary} 管理界面。创建一个应用。

image-20210602002724379

至此 [Apollo]{.label .primary} 部署启动完成

实现多灰度发布

实现多灰度,我们至少需要完成两间事情。1、界面可以展示多灰度列表。2、能继续创建灰度列表。之后如果其他功能无法复用再对其进行调整。

后面涉及大量文件信息地址,阅读困难。可直接到末尾下载成功代码

多灰度版本展示

调整接口

首先创建一个灰度版本,并创建一个配置项在浏览器上 [F12]{.label .primary} 查看异步请求可知道灰度信息主要来源于 [branches]{.label .primary} 的接口

image-20210603084013969

在 [src/main/resources/static]{.label .primary} 目录下全局搜索 [/branches]{.label .primary}。可定位到前端接口在 [apollo-portal/src/main/resources/static/scripts/services/NamespaceBranchService.js]{.label .primary} 文件内编写。是 [GET]{.label .primary} 请求的。

接口写法是前端 [Angular]{.label .primary} 的写法,我们对其转换得 [/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches]{.label .primary} 接口。全局检索查到方法是 [apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/NamespaceBranchController.java]{.label .primary} 类的 [findBranch]{.label .primary} 方法。

通过对这个方法不断的 debug 及实现的理解。最后在[apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/NamespaceService.java]{.label .primary}文件内的 [findChildNamespace]{.label .primary}方法找到根源与办法

// 通过应用id,集群名称,配置空间名字。查找是否存在其他灰度配置空间
public Namespace findChildNamespace(String appId, String parentClusterName, String namespaceName) {
    // 使用 sql 根据应用id 与配置空间名称查询所有灰度配置空间信息
    List<Namespace> namespaces = findByAppIdAndNamespaceName(appId, namespaceName);
    // 配置空间不能低于一个,因为有默认配置空间。区分配置空间主要需要看配置空间的集群名称。默认的配置空间是 default 灰度的是一串规律字符
    if (CollectionUtils.isEmpty(namespaces) || namespaces.size() == 1) {
        return null;
    }
	// 根据应用id 集群名称查询子(灰度)集群配置空间信息。不包含默认配置空间
    List<Cluster> childClusters = clusterService.findChildClusters(appId, parentClusterName);
    if (CollectionUtils.isEmpty(childClusters)) {
        return null;
    }

    Set<String> childClusterNames = childClusters.stream().map(Cluster::getName).collect(Collectors.toSet());
    //the child namespace is the intersection of the child clusters and child namespaces
    for (Namespace namespace : namespaces) {
        // 校验出灰度配置空间
        if (childClusterNames.contains(namespace.getClusterName())) {
            return namespace;
        }
    }
    return null;
}

综上所述,17 行之前实现逻辑与多灰度思路并无冲突。而 17 行之后我们需要改成返回多个。在这里我将从这个方法一路改动到前面定位到的 [findBranch]{.label .primary}方法处。这过程所有方法我将在该方法下面复制一份新的方法,方法名是原方法名加Two

+++info apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/NamespaceService.java 方法findChildNamespaceTwo

public List<Namespace> findChildNamespaceTwo(String appId, String parentClusterName, String namespaceName) {
    List<Namespace> namespaces = findByAppIdAndNamespaceName(appId, namespaceName);
    if (CollectionUtils.isEmpty(namespaces) || namespaces.size() == 1) {
        return null;
    }

    List<Cluster> childClusters = clusterService.findChildClusters(appId, parentClusterName);
    if (CollectionUtils.isEmpty(childClusters)) {
        return null;
    }

    Set<String> childClusterNames = childClusters.stream().map(Cluster::getName).collect(Collectors.toSet());
    //the child namespace is the intersection of the child clusters and child namespaces
    List<Namespace> namespacesEs = new ArrayList<>();
    for (Namespace namespace : namespaces) {
        if (childClusterNames.contains(namespace.getClusterName())) {
            namespacesEs.add(namespace);
        }
    }
    if(!namespacesEs.isEmpty()){
        return namespacesEs;
    }
    return null;
}

+++

+++info apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/NamespaceBranchService.java 方法findBranchTwo

public List<Namespace> findBranchTwo(String appId, String parentClusterName, String namespaceName) {
    return namespaceService.findChildNamespaceTwo(appId, parentClusterName, namespaceName);
}

+++

+++info apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceBranchController.java 方法loadNamespaceBranchTwo

// 根据配置文件查询它的灰度空间(切换环境时触发)
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branchesTwo")
public List<NamespaceDTO> loadNamespaceBranchTwo(@PathVariable String appId, @PathVariable String clusterName,
                                                   @PathVariable String namespaceName) {

    checkNamespace(appId, clusterName, namespaceName);

    List<Namespace> childNamespace = namespaceBranchService.findBranchTwo(appId, clusterName, namespaceName);
    if (childNamespace == null) {
      return null;
    }

    return BeanUtils.batchTransform(NamespaceDTO.class, childNamespace);
}

+++

+++info apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java 方法findBranchTwo

public List<NamespaceDTO> findBranchTwo(String appId, Env env, String clusterName,
                                        String namespaceName) {
    return restTemplate.get(env, "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branchesTwo",new ParameterizedTypeReference<List<NamespaceDTO>>(){}, appId, clusterName, namespaceName).getBody();
}

+++

+++info apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/NamespaceBranchService.java 方法findBranchBaseInfoTwo

public List<NamespaceDTO> findBranchBaseInfoTwo(String appId, Env env, String clusterName, String namespaceName) {
    return namespaceBranchAPI.findBranchTwo(appId, env, clusterName, namespaceName);
}

+++

+++info apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/NamespaceBranchService.java 方法findBranchTwo

public List<NamespaceBO> findBranchTwo(String appId, Env env, String clusterName, String namespaceName) {
    List<NamespaceDTO> namespaceDTO = findBranchBaseInfoTwo(appId, env, clusterName, namespaceName);
    if (namespaceDTO == null) {
        return null;
    }
    List<NamespaceBO> NamespaceList = new ArrayList<>();
    for (NamespaceDTO li:namespaceDTO){
        NamespaceList.add(namespaceService.loadNamespaceBO(appId, env, li.getClusterName(), namespaceName));
    }
    return NamespaceList;
}

+++

+++info apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/NamespaceBranchController.java 方法findBranchTwo

@GetMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branchesTwo")
public List<NamespaceBO> findBranchTwo(@PathVariable String appId,
                                         @PathVariable String env,
                                         @PathVariable String clusterName,
                                         @PathVariable String namespaceName) {
    List<NamespaceBO> namespaceBO = namespaceBranchService.findBranchTwo(appId, Env.valueOf(env), clusterName, namespaceName);

    if (namespaceBO!=null && !namespaceBO.isEmpty() && permissionValidator.shouldHideConfigToCurrentUser(appId, env, namespaceName)) {
        for (NamespaceBO li:namespaceBO){
            li.hideItems();
        }
    }

    return namespaceBO;
}

+++

至此新增一个获取多灰度版本的接口。然后实现前端多个灰度版本按钮刷出功能。

调整前端界面

通过查看发布灰度版本按钮在 [apollo-portal/src/main/resources/static/views/component/namespace-panel-header.html]{.label .primary} 是固定写死的。需要通过 [js]{.label .primary} 实现多灰度按钮的动态新增。而其中我们最需要关心的是 [namespace]{.label .primary}这个对象的数据。

image-20210607204529651

通过前面找到了的 branches 接口。一路找到引用源头 [apollo-portal/src/main/resources/static/scripts/directive/namespace-panel-directive.js]{.label .primary}文件内的 [initNamespaceBranch]{.label .primary}方法。

+++info apollo-portal/src/main/resources/static/scripts/services/NamespaceBranchService.js 接口find_namespace_branch

// 将 branches 结尾接口改为 branchesTwo 接口
find_namespace_branch: {
    method: 'GET',
    isArray: true,
    url: AppUtil.prefixPath() + '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/branchesTwo'
}

+++

+++info apollo-portal/src/main/resources/static/views/component/namespace-panel-header.html 最下面

<header class="panel-heading second-panel-heading" ng-show="namespace.initialized && namespace.hasBranch">
    <div class="row">
        <div class="col-md-8 pull-left"> 
            <!-- 增加一个 data-namespace 键 -->
            <ul class="nav nav-tabs nav-tabs-cluster" data-namespace="{{namespace.baseInfo.env}}.{{namespace.viewName}}.{{namespace.format}}">
                <li role="presentation">
                    <a ng-class="{'node_active': namespace.displayControl.currentOperateBranch == 'master'}"
                        ng-click="switchBranch('master', true)">
                        <img src="img/branch.png">
                        {{'Component.Namespace.Header.Title.Master' | translate }}
                    </a>
                </li>
                <!-- 去掉默认灰度空间
                <li role="presentation">
                    <a ng-class="{'node_active': namespace.displayControl.currentOperateBranch != 'master'}"
                        ng-click="switchBranch(namespace.branchName, true)">
                        <img src="img/branch.png">
                        {{'Component.Namespace.Header.Title.Grayscale' | translate }}
                    </a>
                </li>
                -->
            </ul>
        </div>
    </div>
</header>

+++
+++info apollo-portal/src/main/resources/static/scripts/directive/namespace-panel-directive.js 方法initNamespaceBranch

// 这个方法下原来有很多子方法的,但因为这个文件下 switchBranch 方法也要改动需要这些方法所以全部包括 initNamespaceLock 方法都移动到和initNamespace 方法同级。注意是 initNamespace 方法同级和一个非下面方法内部的子方法 initNamespaceLock 也一并移出 initNamespace
function initNamespaceBranch(namespace) {
    // 用于异步 setTimeout 能拿到参数
    namespace.baseInfo.env = scope.env;
    NamespaceBranchService.findNamespaceBranch(scope.appId, scope.env,
        namespace.baseInfo.clusterName,
        namespace.baseInfo.namespaceName)
        .then(function (result) {
        	// 尽量不改动原来代码,所以如果有取出第一个值
            let resultData = JSON.parse(JSON.stringify(result));
            result = result.length == 0 ? result : result[0];
            if (!result.baseInfo) {
                return;
            }
            //因为这个方法在界面渲染之前执行的,所以这时界面还没有元素。会出现如果第一次打开,界面渲染慢,这个动态html片段将会难以附上
            setTimeout(function (){
                let branchDate = {};
                let ClusterDom = $(".nav-tabs-cluster[data-namespace='" +namespace.baseInfo.env+ "." +namespace.viewName+"."+namespace.format + "']");
                for (let i=0;i<resultData.length;i++){
                    branchDate[resultData[i].baseInfo.clusterName] = resultData[i];
                    let html = $(['<li role="presentation" data-class="false" data-clustername="'+resultData[i].baseInfo.clusterName+'">','<a class="node_active_children"><img src="img/branch.png">','灰度版本'+(i+1)+'</a></li>'].join(""));
                    html.click(function (){
                        ClusterDom.find(".node_active_children").removeClass("node_active");
                        if(!$(this).data("class")){
                            $(this).find(".node_active_children").addClass("node_active");
                            switchBranch($(this).data("clustername"), true);
                        }
                    });
                    ClusterDom.append(html)
                }
                scope.namespaceData = branchDate;
            },500)
            //namespace has branch
            namespace.hasBranch = true;
            namespace.branchName = result.baseInfo.clusterName;
            //init branch
            namespace.branch = result;
            namespace.branch.isBranch = true;
            namespace.branch.parentNamespace = namespace;
            namespace.branch.viewType = namespace_view_type.TABLE;
            namespace.branch.isPropertiesFormat = namespace.format == 'properties';
            namespace.branch.allInstances = [];//master namespace all instances
            namespace.branch.latestReleaseInstances = [];
            namespace.branch.latestReleaseInstancesPage = 0;
            namespace.branch.instanceViewType = namespace_instance_view_type.LATEST_RELEASE;
            namespace.branch.hasLoadInstances = false;
            namespace.branch.displayControl = {
                show: true
            };

            generateNamespaceId(namespace.branch);
            initBranchItems(namespace.branch);
            initRules(namespace.branch);
            loadInstanceInfo(namespace.branch);
            initNamespaceLock(namespace.branch);
            initPermission(namespace);
            initUserOperateBranchScene(namespace);
        });
}

+++
+++info apollo-portal/src/main/resources/static/scripts/directive/namespace-panel-directive.js 方法switchBranch

// 这个方法是切换发布版本时触发的
function switchBranch(branchName, forceShowBody) {
    if (branchName != 'master') {
        // 增加一个判断是否为灰度版本中的某一个,然后初始化数据
        if(scope.namespaceData!=null){
            let clusterId = scope.namespace.branch.baseInfo.clusterName;
            if(clusterId!=null&&clusterId!=""&&clusterId!="default"){
                let namespaceId = JSON.parse(JSON.stringify(scope.namespace.branch.items));
                scope.namespaceData[clusterId].items = namespaceId;
            }
            let namespaceData = JSON.parse(JSON.stringify(scope.namespaceData));
            scope.namespace.branch = namespaceData[branchName];
            scope.namespace.branch.isBranch = true;
            scope.namespace.branch.parentNamespace = scope.namespace;
            scope.namespace.branch.viewType = namespace_view_type.TABLE;
            scope.namespace.branch.isPropertiesFormat = scope.namespace.format == 'properties';
            scope.namespace.branch.allInstances = [];//master namespace all instances
            scope.namespace.branch.latestReleaseInstances = [];
            scope.namespace.branch.latestReleaseInstancesPage = 0;
            scope.namespace.branch.instanceViewType = namespace_instance_view_type.LATEST_RELEASE;
            scope.namespace.branch.hasLoadInstances = false;
            generateNamespaceId(scope.namespace.branch);
            initBranchItems(scope.namespace.branch);
            loadInstanceInfo(scope.namespace.branch);
            initNamespaceLock(scope.namespace.branch);
            initPermission(scope.namespace);
            // for (let i in namespaceData){
            //     scope.namespace.branch[i] = namespaceData[i];
            // }
        }
        initRules(scope.namespace.branch);
    }else{
        //移除选中时产生的选中效果
        $(".nav-tabs-cluster[data-namespace='" +scope.namespace.viewName+"."+scope.namespace.format + "']").find(".node_active_children").removeClass("node_active");
    }
    if (forceShowBody) {
        scope.showNamespaceBody = true;
    }

    scope.namespace.displayControl.currentOperateBranch = branchName;

    //save to local storage
    var operateBranchStorage = JSON.parse(localStorage.getItem(operate_branch_storage_key));
    if (!operateBranchStorage) {
        return;
    }
    var namespaceId = [scope.appId, scope.env, scope.cluster, scope.namespace.baseInfo.namespaceName].join(
        "+");
    operateBranchStorage[namespaceId] = branchName;
    localStorage.setItem(operate_branch_storage_key, JSON.stringify(operateBranchStorage));

}

+++
完整文件 [namespace-panel-directive.js]{.label .primary} 下载地址

展示效果:到这里我们界面上灰度和灰度版本就能同时存在,但不仅如此,我们现在是能显示多个灰度版本的,只是现在点击灰度按钮会出现创建失败,因为有存在灰度版本。

image-20210608213825179

多灰度版本新增

与前面相同一路查找接口调用链路就能找到需要改动的方法。因为 [apollo]{.label .primary} 接口普遍长在搜索时容易眼花,但只要注意请求类型和 [apollo-portal]{.label .primary} 模块接口调用的是 [apollo-adminservice]{.label .primary} 模块就能精准的走在正确的调用链上。

在这里我们对 [apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/NamespaceBranchService.java]{.label .primary} 类的 [createBranch]{.label .primary} 方法进行简单的调整。

@Transactional
public Namespace createBranch(String appId, String parentClusterName, String namespaceName, String operator){
    //查询该表所在空间,再查所有灰度空间,再查灰度空间里面是否有该文件的灰度空间
    Namespace childNamespace = findBranch(appId, parentClusterName, namespaceName);
      // 把这个判断去掉
	/*if (childNamespace != null){
      throw new BadRequestException("namespace already has branch");
    }*/
    //判断是否为主空间
    Cluster parentCluster = clusterService.findOne(appId, parentClusterName);
    if (parentCluster == null || parentCluster.getParentClusterId() != 0) {
      throw new BadRequestException("cluster not exist or illegal cluster");
    }

    //create child cluster
    Cluster childCluster = createChildCluster(appId, parentCluster, namespaceName, operator);
    //新增灰度空间
    Cluster createdChildCluster = clusterService.saveWithoutInstanceOfAppNamespaces(childCluster);

    //create child namespace
    childNamespace = createNamespaceBranch(appId, createdChildCluster.getName(),
                                                        namespaceName, operator);
    //新增该灰度空间的灰度配置
    return namespaceService.save(childNamespace);
}

到现在为止已经实现多灰度版本基本效果了。

image-20210608221201454

完善应用删除功能

应用删除时删除所有的灰度版本

+++info apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/NamespaceService.java 方法findChildNamespaceTwo

//重写上面的查询
public List<Namespace> findChildNamespaceTwo(Namespace parentNamespace) {
  String appId = parentNamespace.getAppId();
  String parentClusterName = parentNamespace.getClusterName();
  String namespaceName = parentNamespace.getNamespaceName();
  return findChildNamespaceTwo(appId, parentClusterName, namespaceName);
}

+++

+++info apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/NamespaceService.java 方法deleteNamespace

@Transactional
public Namespace deleteNamespace(Namespace namespace, String operator) {
  String appId = namespace.getAppId();
  String clusterName = namespace.getClusterName();
  String namespaceName = namespace.getNamespaceName();

  itemService.batchDelete(namespace.getId(), operator);
  commitService.batchDelete(appId, clusterName, namespace.getNamespaceName(), operator);

  // Child namespace releases should retain as long as the parent namespace exists, because parent namespaces' release
  // histories need them
  if (!isChildNamespace(namespace)) {
    releaseService.batchDelete(appId, clusterName, namespace.getNamespaceName(), operator);
  }

  //delete child namespace 这里改成了删除多个的
  List<Namespace> childNamespaceList = findChildNamespaceTwo(namespace);
  if(childNamespaceList!=null&&!childNamespaceList.isEmpty()){
    for (Namespace childNamespace:childNamespaceList){
      if (childNamespace != null) {
        namespaceBranchService.deleteBranch(appId, clusterName, namespaceName,
                childNamespace.getClusterName(), NamespaceBranchStatus.DELETED, operator);
        //delete child namespace's releases. Notice: delete child namespace will not delete child namespace's releases
        releaseService.batchDelete(appId, childNamespace.getClusterName(), namespaceName, operator);
      }
    }
  }

  releaseHistoryService.batchDelete(appId, clusterName, namespaceName, operator);

  instanceService.batchDeleteInstanceConfig(appId, clusterName, namespaceName);

  namespaceLockService.unlock(namespace.getId());

  namespace.setDeleted(true);
  namespace.setDataChangeLastModifiedBy(operator);

  auditService.audit(Namespace.class.getSimpleName(), namespace.getId(), Audit.OP.DELETE, operator);

  Namespace deleted = namespaceRepository.save(namespace);

  //Publish release message to do some clean up in config service, such as updating the cache
  messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
          Topics.APOLLO_RELEASE_TOPIC);

  return deleted;
}

+++

测试灰度版本效果

创建一个普通 [maven]{.label .primary} 包含 [spring-boot]{.label .primary} ,使用[apollo]{.label .primary}的 [spring]{.label .primary}工程方式获取灰度配置。

首先在 [apollo]{.label .primary} 上创建五个灰度版本,存放一个[key=value]{.label .primary}的值。每个版本[value]{.label .primary}值后加 [1]{.label .primary}

image-20210609235059077

[java]{.label .primary}测试代码。

try {
    for (;;){
        Thread.sleep(2000);
        System.out.println(ConfigService.getAppConfig().getProperty("key", "没有"));
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}

不断切换灰度版本,新增删除灰度规则的[ip]{.label .primary}。可以看到控制台打印不同灰度版本的配置

image-20210610002754630

image-20210610002654242

总结

虽然到目前为止已经完成多灰度功能。但时间短暂,还有许多bug,如多灰度版本共同配置灰度规则到一个[ip]{.label .primary}默认走的是第一个配置的灰度版本,有事灰度会有[脏]数据停留。需要刷新才会消失等。总之没有优化界面展示效果及多灰度版本下某些功能的强化。略感遗憾。期待官方的多灰度版本。