从Apollo迁移到MSE Nacos

本文介绍自建Apollo配置中心如何迁移到MSE Nacos配置中心。

前提条件

已创建MSE Nacos集群,且Nacos版本为专业版。具体操作,请参见创建Nacos引擎

迁移说明

Apollo的模型和MSE Nacos的模型如下:

Apollo模型

MSE Nacos模型

  • 环境(env)。

    说明

    不同的env对应不同的Apollo实例。

  • 应用(appId)。

  • 集群(cluster)。

  • 命名空间(namespace)。

  • 格式(format)。

  • 实例(instance)。

  • 命名空间(namespace)。

  • 分组(group)。

  • 配置(dataId)。

本迁移文档将单个Apollo环境中的所有配置以文件的方式导入到Nacos中的一个命名空间,将以如下规则进行映射:

  • 不同的Apollo环境可以映射为不同的Nacos实例,也可以映射到同一个Nacos实例下的不同命名空间之中,可自行选择。

  • Apollo的cluster对应Nacos中的group,Apollo中的{appId}.{namespace}.{format}对应Nacos中的dataId

  • Apollo中的appId将映射为Nacos配置中的应用appName

步骤一:导出Apollo配置

导出工具导出zip包

  1. 登录自建Apollo控制台。

    本文以官方Demo地址为例。

  2. 我的应用页面,单击目标应用名称。

  3. 在目标应用详情页的右上方,选择管理员工具 > 配置导出导入。在配置导出导入页面,勾选选择导出的环境,然后单击导出

说明

不同的环境请分独立导出不同文件,文件名以.zip结尾。

SQL导出文件

低版本Apollo没有文件导出功能,对于旧版本Apollo,可以从数据库中直接将配置导出,请按照以下SQL进行配置的导出,可以选择导出为JSON格式,或者Excel格式,文件名分别以.json.xlsx 结尾。

select distinct  a.NamespaceId,b.NamespaceName,c.`Comment` AS 'NamespaceDesc' ,b.AppId,b.ClusterName,a.Key,a.Type ,a.Value,a.Comment,a.LineNum from ITEM a left join Namespace b on a.NamespaceId=b.id  left join AppNamespace c on c.Name=b.NamespaceName where a.IsDeleted=0 order by a.NamespaceId,a.LineNum ;

步骤二:文件转换

对Apollo配置进行了导出,可以获得一个配置导出文件(文件以.zip,.json,.xlsx结尾),需要将文件转换为可以直接在MSE Nacos控制台中进行导入的文件格式。

  1. 下载转换工具:curl -O https://msesync.oss-cn-hangzhou.aliyuncs.com/ApolloConfigTransfer.jar

  2. 执行转换程序:java -DsourceFilePath={apollo导出文件的完整路径} -jar ApolloConfigTransfer.jar。

  3. 程序执行成功后,可以在当前目录下获得一个名为nacos_config_import_{apollo文件名}_{时间戳}.zip的文件。

步骤三:导入Nacos配置

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

  2. 在左侧导航栏,选择注册配置中心 > 实例列表。单击目标实例名称。

  3. 在基础信息页面的左侧导航栏选择配置管理 > 配置列表

  4. 在配置列表页面,选择对应的命名空间,单击导入配置,在导入配置页面上传步骤二的配置文件。

    说明

    至此,我们已经完成配置从Apllo到Nacos的迁移。

步骤四:更改依赖

在应用项目中将Apollo的依赖更改为Spring Cloud Alibaba的依赖。

修改前:

<dependency>
    <groupId>com.ctrip.framework.apollo</groupId>
    <artifactId>apollo-client</artifactId>
    <version>{apollo.version}</version>
</dependency>

修改后:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2.2.10</version>
</dependency>
说明
  • 如果您使用的是Spring Cloud Alibaba 2022.x以及2023.x版本建议升级到2023.0.1.2版本。

  • 如果您使用的是Spring Cloud Alibaba 2021.x版本建议升级到2021.0.6.1版本。

  • 如果您使用的是Spring Cloud Alibaba 2.x版本建议升级到2.2.10版本。

步骤五:改造代码

应用程序使用Apollo动态配置有以下几种用法。

@Value注解

该种用法是在一个SpringBean中通过@Value注解引用一个属性值,spring cloud alibaba框架可以平滑支持,无需代码改动。

@RestController
@RefreshScope
public class DemoController {
    
    @Value("testKey")
    String testKey = "value";

    
    @RequestMapping("/valuekey")
    public String getNacosTestKey() {
        return testKey;
    }
}

@ApolloConfig注解

该种用法是通过@ApolloConfig,注册一个SpringBean,并且将对象的字段对应的属性值自动注入该对象中,可以通过Spring的标准注解@ConfigurationProperties直接替换。

@Configuration
@ConfigurationProperties
public class CNStackInfoConfig {
    
    private String name;

    private int customerCount;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getCustomerCount() {
        return customerCount;
    }

    public void setCustomerCount(int customerCount) {
        this.customerCount = customerCount;
    }
}

Apollo Config API

该种用法是直接调用Apollo的API获取某个指定namespace中的单个属性值。

ConfigService.getAppConfig().getProperty("testkey", "defaulyv1");
ConfigService.getAppConfig("namespace").getProperty("testkey", "defaulyv1");

在Nacos中,可以通过二次封装支持。示例如下:

    @Autowired 
    NacosConfigPropertiesService nacosConfigPropertiesService;

    private void method(){
        String value=nacosConfigPropertiesService.getProperties("appId1.application.properties", "group","key","defaultValue");
    }
package com.example.demo.service;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.client.config.common.GroupKey;
import com.alibaba.nacos.client.config.listener.impl.PropertiesListener;
import com.alibaba.nacos.common.utils.StringUtils;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@Component
public class NacosConfigPropertiesService {
    
    Map<String, Properties> namespacePropertiesMap = new HashMap<>();
    
    @PostConstruct
    private void postConstruct() {
        try {
            initProperties("appId1.application.properties", "group");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    @Autowired
    private NacosConfigManager nacosConfigManager;
    
    public String getProperties(final String dataId, final String group, String key, String defaultValue) {
        return namespacePropertiesMap.get(GroupKey.getKey(dataId, group)).getProperty(key, defaultValue);
    }
    
    private void initProperties(final String dataId, final String group) throws Exception {
        
        String aDefault = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(dataId, group, 3000L, new PropertiesListener() {
                    
                    @Override
                    public void innerReceive(Properties properties) {
                        namespacePropertiesMap.put(GroupKey.getKey(dataId, group), properties);
                    }
                });
        if (StringUtils.isBlank(aDefault)) {
            namespacePropertiesMap.put(GroupKey.getKey(dataId, group), new Properties());
        } else {
            Properties properties = new Properties();
            properties.load(new StringReader(aDefault));
            namespacePropertiesMap.put(GroupKey.getKey(dataId, group), properties);
        }
        
    }
    
}

ApolloConfigChangeListener回调

该种用法支持在Apollo中指定前缀的key发生变更时回调当前方法。

Apollo中的用法:

    /**
     * notify listener
     * @param configChangeEvent
     */
    @ApolloConfigChangeListener(interestedKeyPrefixes = {"data."})
    public void apolloNotify(ConfigChangeEvent configChangeEvent) {
        System.out.println(configChangeEvent.changedKeys());
    }
    

Nacos中的用法:

    private void apolloNotify(ConfigChangeEvent event) {
        //notify.
        System.out.println(event.getChangeItems());
    }
    
    @PostConstruct
    public void registerListener() throws Exception {
        
       interestedPrefix("appId1.application.properties","default","data.");
    }
  
    private void interestedPrefix(String dataId, String group, String prefix) throws Exception {
        NacosPrefixKeyPropertiesListener prefixKeyPropertiesListener = new NacosPrefixKeyPropertiesListener(prefix) {
            
            @Override
            public void configChanged(ConfigChangeEvent event) {
                notifyOnChange(event);
            }
        };
        
        nacosConfigManager.getConfigService().addListener(dataId, group, prefixKeyPropertiesListener);
    }
    

Nacos属性前缀监听器:

public abstract class NacosPrefixKeyPropertiesListener extends AbstractConfigChangeListener {
    private String prefix;
    
    public NacosPrefixKeyPropertiesListener(String prefix) {
        this.prefix = prefix;
    }
    
    @Override
    public final void receiveConfigChange(ConfigChangeEvent event) {
        
        Iterator<ConfigChangeItem> iterator = event.getChangeItems().iterator();
        while (iterator.hasNext()) {
            if (!iterator.next().getKey().startsWith(prefix)) {
                iterator.remove();
            }
        }
        
        if (event.getChangeItems().isEmpty()) {
            return;
        }
        configChanged(event);
    }
    
    public abstract void configChanged(ConfigChangeEvent event);
}

步骤六:修改连接地址

  1. 引入Nacos配置

    ##原有apollo的配置 
    #环境-Dappllo.env={env}
    apollo.meta=http://127.0.0.1:8070
    apollo.bootstrap.enabled=true
    #指定引入的命名空间
    apollo.bootstrap.namespaces=application,namespace1
    #指定集群
    apollo.bootstrap.cluster=default
    #指定应用的
    app.id=app1

    修改为Nacos的地址

    #指定加载的配置dataId及group
    spring.config.import=nacos:app1.application.properties?group=default&refreshEnabled=true,nacos:app1.namespace1.properties?group=default&refreshEnabled=true
    #指定MSE Nacos的地址
    spring.cloud.nacos.config.server-addr=mse-xxx-p.nacos-ans.mse.aliyuncs.com
    #指定MSE Nacos的命名空间
    spring.cloud.nacos.config.namespace={mse nacos中指定环境对应namespaceid,比如5babe1ee-1352-xxxx-bd7b-7e7ce892e2ab}
  2. 重启服务。