AspnetCore接入Nacos配置中心

发布时间 2023-07-22 19:28:27作者: yswenli

一、什么是nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。详细架构图如下:

官网地址:https://nacos.io/zh-cn/index.html。

二、AspnetCore快速接入

1.引用sdk nuget package

nacos-sdk-csharp.AspNetCore
nacos-sdk-csharp.Extensions.Configuration

2.配置中心配置如下:

{
  "NacosConfig": {
    "Listeners": [
      {
        "Optional": false,
        "DataId": "common",
        "Group": "DEFAULT_GROUP" 
      },
      {
        "Optional": false,
        "DataId": "bussiness",
        "Group": "DEFAULT_GROUP" 
      }
    ],
    "Namespace": "abcyswenli",
    "ServerAddresses": [ "http://10.10.1.90:8848/" ],
    "UserName": "yswenli",
    "Password": "walle",
    "ConfigUseRpc": false,
    "NamingUseRpc": false
  }
}

a、必须要有 ConfigUseRpc和NamingUseRpc这2个参数,若用的是http协议,则都是false ,若用grpc协议则为true.(这个官方提供的demo没有写,就会报错)
b、Listeners 对应配置文件。DataId是配置名称,Tenant是命名空间名称。Group组名。
c、ServerAddresses是Nacos的服务器地址,可以添加多个。
d、若是新的命名空间,则 Namespace对应的是命名空间的Id.
e、Listeners里添加的配置文件一定要存在,不要有多余的节点,可以少,但是不可以多。Group也不能填错。
f、Namespace处填写的是配置的id,不是名称

3.服务注册Naming的配置如下: 

"nacos": {
    "EndPoint": "sub-domain.aliyun.com:8080",
    "ServerAddresses": [ "http://localhost:8848" ],
    "DefaultTimeOut": 15000,
    "Namespace": "cs", // Please set the value of Namespace ID !!!!!!!!
    "ListenInterval": 1000,
    "ServiceName": "App1",
    "GroupName": "DEFAULT_GROUP",
    "ClusterName": "DEFAULT",
    "Ip": "",
    "PreferredNetworks": "", // select an IP that matches the prefix as the service registration IP
    "Port": 0,
    "Weight": 100,
    "RegisterEnabled": true,
    "InstanceEnabled": true,
    "Ephemeral": true,
    "Secure": false,
    "AccessKey": "",
    "SecretKey": "",
    "UserName": "",
    "Password": "",
    "ConfigUseRpc": true,
    "NamingUseRpc": true,
    "NamingLoadCacheAtStart": "",       
    "LBStrategy": "WeightRandom", //WeightRandom WeightRoundRobin
    "Metadata": {
      "aa": "bb",
      "cc": "dd"
    }
  }

4.在aspnetcore中,用中间件实现对接nacos配置中心

/// <summary>
    /// nacos集成中间件工具类
    /// </summary>
    internal static class NacosUtil
    {
        /// <summary>
        /// 初始化nacos
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        public static void GetNacosConfigs(this IServiceCollection services, IConfiguration configuration)
        {
            var nacosConfig = configuration.GetSection("NacosConfig").Get<NacosConfig>();
            services.AddNacosV2Config((n) =>
            {
                n.ConfigUseRpc = nacosConfig.ConfigUseRpc;
                n.NamingUseRpc = nacosConfig.NamingUseRpc;
                n.ServerAddresses = nacosConfig.ServerAddresses;
                n.ListenInterval = 3000;
                n.Namespace = nacosConfig.Namespace;
            });
            var serviceProvider = services.BuildServiceProvider();
            var configSvc = serviceProvider.GetService<INacosConfigService>();
            if (configSvc == null) throw new NotImplementedException("nacos初始化失败");

            if (nacosConfig != null && nacosConfig.Listeners != null && nacosConfig.Listeners.Count > 0)
            {
                foreach (var item in nacosConfig.Listeners)
                {
                    //初始化配置变化监听
                    configSvc.AddListener(item.DataId, item.Group, new NacosConfigListener(nacosConfig.Namespace, item.DataId, item.Group));
                    //初始化数据
                    var content = configSvc.GetConfig(item.DataId, item.Group, 3000).Result;
                    if (content.IsNotNullOrEmpty())
                        NacosConfigUtil.Set(nacosConfig.Namespace, item.Group, item.DataId, content);
                }
            }
        }
        //

    }

    /// <summary>
    /// 配置变化监听
    /// </summary>
    internal class NacosConfigListener : IListener
    {
        string _nameSpace, _dataId, _group;

        /// <summary>
        /// 配置变化监听
        /// </summary>
        /// <param name="nameSpace"></param>
        /// <param name="dataId"></param>
        /// <param name="group"></param>
        public NacosConfigListener(string nameSpace, string dataId, string group)
        {
            _nameSpace = nameSpace;
            _dataId = dataId;
            _group = group;
        }
        /// <summary>
        /// 接收配置方法
        /// </summary>
        /// <param name="configInfo"></param>
        public void ReceiveConfigInfo(string configInfo)
        {
            Console.WriteLine("recieve:" + configInfo);
            NacosConfigUtil.Set(_nameSpace, _dataId, _group, configInfo);
        }
    }

在上述代码中services在目前新版本中可以直接初始化配置中心、服务注册、OpenApi等功能,但是需要注意的是services.AddNacosV2Config((n) =>这个不直接传入configuration参数。

 5.成功接入nacos配置中心后,可以在本地缓存来接收配置,以方便后续业务中使用


    /// <summary>
    /// Nacos配置工具类
    /// </summary>
    public static class NacosConfigUtil
    {
        static ConcurrentDictionary<string, IConfigurationRoot> _cache;

        static ConcurrentDictionary<string, ConcurrentDictionary<string, ConcurrentDictionary<string, string>>> _configDic;

        //namaspace用于环境,在运行中一般不变
        static string _nameSpace;

        /// <summary>
        /// Nacos配置工具类
        /// </summary>
        static NacosConfigUtil()
        {
            _configDic = new ConcurrentDictionary<string, ConcurrentDictionary<string, ConcurrentDictionary<string, string>>>();
            _cache = new ConcurrentDictionary<string, IConfigurationRoot>();
        }

        /// <summary>
        /// GetRoot
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        static IConfigurationRoot GetRoot(string content)
        {
            return new ConfigurationBuilder().AddJsonStream(content.ToStream()).Build();
        }


        /// <summary>
        /// 设置配置
        /// </summary>
        /// <param name="nameSpace"></param>
        /// <param name="groupName"></param>
        /// <param name="dataId"></param>
        /// <param name="content"></param>
        public static void Set(string nameSpace, string groupName, string dataId, string content)
        {
            _nameSpace = nameSpace;
            if (!_configDic.ContainsKey(nameSpace))
            {
                _configDic[nameSpace] = new ConcurrentDictionary<string, ConcurrentDictionary<string, string>>();
            }
            if (!_configDic[nameSpace].ContainsKey(groupName))
            {
                _configDic[nameSpace][groupName] = new ConcurrentDictionary<string, string>();
            }
            _configDic[nameSpace][groupName][dataId] = content;
        }

        /// <summary>
        /// 获取配置
        /// </summary>
        /// <param name="groupName"></param>
        /// <param name="dataId"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public static string Get(string groupName, string dataId)
        {
            if (!_configDic.ContainsKey(_nameSpace))
            {
                throw new NotImplementedException("当前配置还未初始化");
            }
            if (!_configDic[_nameSpace].ContainsKey(groupName))
            {
                return string.Empty;
            }
            if (!_configDic[_nameSpace][groupName].ContainsKey(dataId))
            {
                return string.Empty;
            }
            return _configDic[_nameSpace][groupName][dataId];
        }

        /// <summary>
        /// 获取配置
        /// </summary>
        /// <param name="groupName"></param>
        /// <param name="dataId"></param>
        /// <param name="sectionName"></param>
        /// <returns></returns>
        public static string? Get(string groupName, string dataId, string sectionName)
        {
            var content = Get(groupName, dataId);
            if (content.IsNullOrEmpty()) return string.Empty;
            return GetRoot(content)[sectionName];
        }

        /// <summary>
        /// 获取配置
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="groupName"></param>
        /// <param name="dataId"></param>
        /// <param name="sectionName"></param>
        /// <returns></returns>
        public static T? Get<T>(string groupName, string dataId, string sectionName)
        {
            var content = Get(groupName, dataId);
            if (content.IsNullOrEmpty()) return default;
            return GetRoot(content).GetSection(sectionName).Get<T>();
        }

        /// <summary>
        /// 读取配置字符串
        /// </summary>
        /// <param name="dataId"></param>
        /// <param name="sectionName"></param>
        /// <returns></returns>
        public static string Read(string dataId,string sectionName)
        {
            return Get("DEFAULT_GROUP", dataId, sectionName);
        }

        /// <summary>
        /// 读取配置
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dataId"></param>
        /// <param name="sectionName"></param>
        /// <returns></returns>
        public static T Read<T>(string dataId, string sectionName)
        {
            return Get<T>("DEFAULT_GROUP", dataId, sectionName);
        }
    }

6.在业务处可以使用上述接收类来读取配置

    /// <summary>
    /// nacos配置测试
    /// </summary>
    [AllowAnonymous]
    public class NacosTestController : BaseApiController
    {
        public NacosTestController(IWebHostEnvironment env) : base(env)
        {
        }


        /// <summary>
        /// nacos配置测试
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public Result Test()
        {
            return SuccessResult(NacosConfigUtil.Get("BussinessService", "WALLE"));
        }
    }

动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。Nacos 提供了一个简洁易用的UI (控制台样例 Demo) 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险。