相关文章:手动造轮子——为Ocelot集成Nacos注册中心
出处:https://www.cnblogs.com/buruainiaaaa/p/14121176.html
作者:唐@
最近一段时间 因公司业务需要,需要使用.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/nacos-group/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": 108 },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方法,加载配置中心的配置
代码说明:pKey入参 为配置文件中的key(nacosData),responseJson为配置中心的完整配置字符串,可以是json,可以是key=value模式,根据字符串格式,转为Dictionary中,放入静态私有对象中
/// <summary>/// 加载nacos配置中心/// </summary>/// <param name="pKey"></param>private async Task LoadConfig(string pKey){try{GetConfigRequest configRequest = configuration.GetSection(pKey).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 (pKey == 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;}
将配置信息放入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 =>{//防止重复Timerif (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;}
相关文章:手动造轮子——为Ocelot集成Nacos注册中心