.net5+nacos+ocelot 配置中心和服務發現實現


 

  最近一段時間 因公司業務需要,需要使用.net5做一套微服務的接口,使用nacos 做注冊中心和配置中心,ocelot做網關。

因為ocelot 支持的是consol和eureka,如果使用nacos做服務發現,需要自己集成,以此記錄下

  Nacos 支持基於 DNS 和基於 RPC 的服務發現(可以作為注冊中心)、動態配置服務(可以做配置中心)、動態 DNS 服務。官網地址:https://nacos.io/en-us/

  ocelot 相信大家都比較熟悉,官網:https://ocelot.readthedocs.io/en/latest/index.html

 環境安裝:

  nacos參考地址:https://blog.csdn.net/ooyhao/article/details/102744904

  基於.net版本的nacos  sdk:nacos-sdk-csharp-unofficial.AspNetCore  (此處有坑 :Nacos.AspNetCore 已經停止更新,代碼已遷移,服務注冊會有問題)

  SDK源碼地址:https://github.com/catcherwong/nacos-sdk-csharp

配置中心:

  1.在nacos添加配置

 

    2.在.net 項目中 配置文件中 添加相關配置

 1  "nacos": {
 2     "ServerAddresses": [ "http://127.0.0.1:8849/" ],
 3     "DefaultTimeOut": 30000,
 4     "Namespace": "",
 5     "ListenInterval": 30000,
 6     "ServiceName": "ServiceName",
    "RegisterEnabled": true,
7 "Weight": 10 8 }, 9 "nacosData": { 10 "DataId": "nacosConfig", 11 "Group": "Pro" 12 }

3.在Startup.cs添加nacos  sdk的DI注冊

1 services.AddNacos(Configuration);

 4.創建AppConfig類,定義構造函數:(從DI中獲取INacosConfigClient對象)

 public AppConfig(IServiceCollection _services, IConfiguration _configuration)
        {
            services = _services;

            configuration = _configuration;
            var serviceProvider = services.BuildServiceProvider();
            _configClient = serviceProvider.GetService<INacosConfigClient>();
        }

5.添加LoadConfig方法,加載配置中心的配置

代碼說明:sectionKey入參 為配置文件中的key(nacosData),responseJson為配置中心的完整配置字符串,可以是json,可以是key=value模式,根據字符串格式,轉為Dictionary中,放入靜態私有對象中

/// <summary>
        /// 加載nacos配置中心
        /// </summary>
        /// <param name="sectionKey"></param>
        private async Task LoadConfig(string sectionKey)
        {
            try
            {
                GetConfigRequest configRequest = configuration.GetSection(sectionKey).Get<GetConfigRequest>();
                if (configRequest == null || string.IsNullOrEmpty(configRequest.DataId))
                {
                    return;
                }
                var responseJson = await _configClient.GetConfigAsync(configRequest);
                Console.WriteLine(responseJson);
                if (string.IsNullOrEmpty(responseJson))
                {
                    return;
                }
                var dic = LoadDictionary(responseJson);
                if (sectionKey == commonNacosKey)
                {
                    commonConfig = dic;
                }
                else
                {
                    dicConfig = dic;
                }

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

6.添加監聽:

ps:

AddListenerRequest對象為nacos sdk的監聽請求對象,通過INacosConfigClient對象的AddListenerAsync方法,可以添加對nacos dataid=nacosConfig 的監聽,監聽的時間間隔設置可以為:ListenInterval
 1 /// <summary>
 2         /// 監控
 3         /// </summary>
 4         private void ListenerConfig()
 5         {
 6             AddListenerRequest request = configuration.GetSection("nacosData").Get<AddListenerRequest>();
 7             request.Callbacks = new List<Action<string>>() {
 8                 x=>{
 9                    var dic = LoadDictionary(x);
10                     foreach (var item in dicConfig.Keys)
11                     {
12                         if (dic.Keys.Any(p=>p==item))
13                         {
14                             if (dic[item] != dicConfig[item])
15                             {
16                                 dicConfig[item]=dic[item].Trim();
17                             }
18                         }else
19                         {
20                             dicConfig.Remove(item);
21                         }
22                     }
23                     foreach (var item in dic.Keys)
24                     {
25                         if (!dicConfig.Keys.Any(p=>p==item)){
26                             dicConfig.Add(item,dic[item]);
27                         }
28                     }
29                 }
30             };
31             var serviceProvider = services.BuildServiceProvider();
32             INacosConfigClient _configClient = serviceProvider.GetService<INacosConfigClient>();
33             _configClient.AddListenerAsync(request);
34         }

7.添加自動注入:

ps:如果需要在同一個項目中,獲取多個配置,需要AppConfig對象的單列模式,這一點 自己注意下

 public static IServiceCollection AddLoadConfig(this IServiceCollection _services, IConfiguration _configuration)
        {
            var config = new AppConfig(_services, _configuration);
            config.Load();
            return _services;
        }

 /******************************************************* 配置中心  end *************************************************************/

服務注冊:

基於上面的配置信息 在自動注入中添加如下代碼即可

services.AddNacosAspNetCore(conf);

啟動之后 在nacos中,就可以看到此服務  如果在nacos 看到多個實列 或者 端口號和項目啟動的端口號不一致,最好添加IP和port。

/***************************************************************服務注冊  end***********************************************************************/

基於ocelot 的服務發現:

新建網關項目,添加ocelot和 nacos sdk 的nuget依賴

查看ocelot的官方文檔就會發現,如果 能夠取到nacos 中 服務的名稱和服務里邊可用實列的ip和port,然后把這些信息放入ocelot里邊的, 就可以通過ocelot訪問到這些服務接口

然后在通過自定義監聽器,就可以實現想要的效果

通過上面的配置中心的配置方式,在nacos中 添加 ocelot 的模板配置

{
    "Routes": [{
            "DownstreamHostAndPorts": [{
                "Host": "localhost",
                "Port": 5000
            }],
            "DownstreamPathTemplate": "/{url}",
            "DownstreamScheme": "http",
            "UpstreamHttpMethod": ["GET", "POST", "DELETE", "PUT"],
            "UpstreamPathTemplate": "/OrderServer/{url}",
            "LoadBalancerOptions": {
                "Type": "RoundRobin"
            }
        }, {
            "DownstreamHostAndPorts": [{
                "Host": "localhost",
                "Port": 5000
            }],
            "DownstreamPathTemplate": "/{url}",
            "DownstreamScheme": "http",
            "UpstreamHttpMethod": ["GET", "POST", "DELETE", "PUT"],
            "UpstreamPathTemplate": "/ProductServer/{url}"
        }
    ],
    "ServiceDiscoveryProvider": {
    },
    "GlobalConfiguration": {}
}

關於ocelot的Startup.cs的相關配置  這里就不贅述了,網上有很多。

這里的關鍵是,從nacos中拉取服務列表,然后根據ocelot的配置模板,生成需要的ocelot的配置信息,然后放入ocelot中

獲取nacos中所有的服務列表 

ps:通過INacosNamingClient對象的ListServicesAsync方法,獲取nacos 的服務

/// <summary>
        /// 獲取所有服務
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <param name="_servicesRequest"></param>
        /// <returns></returns>
        private async Task<List<ListInstancesResult>> GetServerListener(IServiceProvider serviceProvider, object _servicesRequest)
        {

            ListServicesRequest request = (ListServicesRequest)_servicesRequest;
            try
            {
                var _namingClient = serviceProvider.GetService<INacosNamingClient>();

                var res = await _namingClient.ListServicesAsync(request);

                List<ListInstancesResult> resultList = new List<ListInstancesResult>();
                if (res.Count > 0)
                {
                    List<Task<ListInstancesResult >> taskList = new List<Task<ListInstancesResult>>();
                    foreach (var item in res.Doms)
                    {
                        var taskItem = GetListInstancesResult(_namingClient,item);
                        taskList.Add(taskItem);
                    }
                    Task.WaitAll(taskList.ToArray());
                    foreach (var item in taskList)
                    {
                        resultList.Add(item.Result);
                    }
                }
                return resultList;
            }
            catch (Exception ex)
            {

                LoggerLocal.Error(ex.Message, ex);
                return new List<ListInstancesResult>();
            }
        }

 將nacos的服務和配置中心的ocelot模板轉換為ocelot的配置對象

 

/// <summary>
        /// nacos中的服務 轉為ocelot對象的路由
        /// </summary>
        /// <param name="fileConfiguration"></param>
        /// <param name="listInstancesResults"></param>
        /// <returns></returns>
        private FileConfiguration InstancesResultToFileConfiguration(FileConfigurationTemplate fileConfiguration, List<ListInstancesResult> listInstancesResults) {
            
            if (fileConfiguration.RouteTemplate == null || fileConfiguration.RouteTemplate.Count == 0)
            {
                throw new Exception("路由不能為空");
            }
            var result = new FileConfiguration() {
                GlobalConfiguration = fileConfiguration.GlobalConfiguration,
                Aggregates = fileConfiguration.Aggregates,
                DynamicRoutes = fileConfiguration.DynamicRoutes,
                Routes = new List<FileRoute>()
            };
            nacosServerModelList.ServerInfo = new List<ServerInfo>();
            var routeList = fileConfiguration.RouteTemplate;
            var defaultRoute = fileConfiguration.RouteTemplate.Find(p=>p.RoutesTemplateName=="common");
            fileConfiguration.Routes = new List<FileRoute>();
            foreach (var item in listInstancesResults)
            {
                var routeTemp = routeList.Find(p => p.ServiceName.ToLower() == item.Dom.ToLower());
                if (routeTemp == null)
                {
                    routeTemp = defaultRoute;
                }
                var newRouteTmp = CopyTo(routeTemp);
                newRouteTmp.UpstreamPathTemplate = "/" + item.Dom + "/{url}";
                newRouteTmp.DownstreamPathTemplate = "/{url}";
                newRouteTmp.DownstreamHostAndPorts = new List<FileHostAndPort>();
                if (item.Hosts.Count > 0)
                {
                    foreach (var host in item.Hosts)
                    {
                        newRouteTmp.DownstreamHostAndPorts.Add(new FileHostAndPort()
                        {
                            Host = host.Ip,
                            Port = host.Port,
                        });
                    }
                }
                if (newRouteTmp.DownstreamHostAndPorts.Count > 0)
                {
                    result.Routes.Add(newRouteTmp);
                    nacosServerModelList.ServerInfo.Add(new ServerInfo() { Name = item.Dom });
                }
            }

            UpdSwaggerUrlAction(serviceProvider, nacosServerModelList);
            return result;
        }


        private  FileRoute CopyTo(RouteTemplate s)
        {
            var result = new FileRoute() { 
                AddClaimsToRequest=s.AddClaimsToRequest,
                DangerousAcceptAnyServerCertificateValidator=s.DangerousAcceptAnyServerCertificateValidator,
                DelegatingHandlers=s.DelegatingHandlers,
                DownstreamHeaderTransform=s.DownstreamHeaderTransform,
                DownstreamHostAndPorts=s.DownstreamHostAndPorts,
                DownstreamHttpMethod=s.DownstreamHttpMethod,
                DownstreamHttpVersion=s.DownstreamHttpVersion,
                DownstreamPathTemplate=s.DownstreamPathTemplate,
                SecurityOptions=s.SecurityOptions,
                DownstreamScheme=s.DownstreamScheme,
                ChangeDownstreamPathTemplate=s.ChangeDownstreamPathTemplate,
                AddHeadersToRequest=s.AddHeadersToRequest,
                AddQueriesToRequest=s.AddQueriesToRequest,
                AuthenticationOptions=s.AuthenticationOptions,
                FileCacheOptions=s.FileCacheOptions,
                HttpHandlerOptions=s.HttpHandlerOptions,
                Key=s.Key,
                LoadBalancerOptions=s.LoadBalancerOptions,
                Priority=s.Priority,
                QoSOptions=s.QoSOptions,
                RateLimitOptions=s.RateLimitOptions,
                RequestIdKey=s.RequestIdKey,
                RouteClaimsRequirement=s.RouteClaimsRequirement,
                RouteIsCaseSensitive=s.RouteIsCaseSensitive,
                ServiceName=s.ServiceName,
                ServiceNamespace=s.ServiceNamespace,
                Timeout=s.Timeout,
                UpstreamHeaderTransform=s.UpstreamHeaderTransform,
                UpstreamHost=s.UpstreamHost,
                UpstreamHttpMethod=s.UpstreamHttpMethod,
                UpstreamPathTemplate=s.UpstreamPathTemplate,
                
            };
            return result;
        }
View Code

 

將配置信息放入ocelot里邊

ps:這個地方 需要看ocelot的源碼,才知道這中間的對象轉換邏輯

1  private void SetOcelotConfig(FileConfiguration configuration)
2         {
3             
4             var internalConfigCreator = serviceProvider.GetService<IInternalConfigurationCreator>();
5             Task<Response<IInternalConfiguration>> taskResponse = internalConfigCreator.Create(configuration);
6             taskResponse.Wait();
7             IInternalConfigurationRepository internalConfigurationRepository = serviceProvider.GetService<IInternalConfigurationRepository>();
8             internalConfigurationRepository.AddOrReplace(taskResponse.Result.Data);
9         }

自定義監聽器:

ps:isLoadUri 防止處理過慢,監聽服務 多次監聽

routesMd5:判斷監聽到的服務 是否需要放入ocelot

自定義監聽的方式與nacos sdk中,監聽配置中心的方式類似,有興趣可以看看sdk的源碼

/// <summary>
        /// 獲取nacos里邊的所有服務信息,同時自定義服務監聽
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <returns></returns>
        private async Task<List<ListInstancesResult>> GetServerList(IServiceProvider serviceProvider)
        {
            var request = new ListServicesRequest
            {
                PageNo = 1,
                PageSize = 100,
            };
            List<ListInstancesResult> listInstancesResults = await GetServerListener(serviceProvider, request);
            //return listInstancesResults;
            var timeSeconds = 1000 * 10;
            Timer timer = new Timer(async x =>
            {
                //防止重復Timer
                if (isLoadUri)
                {
                    return;
                }
                isLoadUri = true;
                List<ListInstancesResult> listInstancesList = await GetServerListener(serviceProvider, x);
                GetConfigRequest configRequest = configuration.GetSection("nacosData").Get<GetConfigRequest>();


                INacosConfigClient _configClient = serviceProvider.GetService<INacosConfigClient>();
                Task<string> taskResult = _configClient.GetConfigAsync(configRequest);
                taskResult.Wait();
                var responseJson = taskResult.Result;
                if (listInstancesList.Count>0)
                {
                    var fileConfiguration = InstancesResultToFileConfiguration(JsonConvert.DeserializeObject<FileConfigurationTemplate>(responseJson), listInstancesList);
                    responseJson = JsonConvert.SerializeObject(fileConfiguration);
                    var rMd5 = HashUtil.GetMd5(responseJson);
                    if (!rMd5.Equals(routesMd5))
                    {
                        SetOcelotConfig(fileConfiguration);
                        routesMd5 = rMd5;
                    }
                }
                isLoadUri = false;
            }, request, timeSeconds, timeSeconds);
            timers.Add(timer);
            return listInstancesResults;
        }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM