.net5+nacos+ocelot 配置中心和服务发现实现

  • A+
所属分类:.NET技术
摘要

   最近一段时间 因公司业务需要,需要使用.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添加配置

.net5+nacos+ocelot 配置中心和服务发现实现

 

    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的配置对象

 

.net5+nacos+ocelot 配置中心和服务发现实现.net5+nacos+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;         }