• .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;
            }
  • 相关阅读:
    C++中四种类型转换方式
    LeetCode——Move Zeroes
    LeetCode——Construct the Rectangle
    LeetCode——Add Digits
    LeetCode—— Invert Binary Tree
    LeetCode——Sum of Two Integers
    LeetCode——Find the Difference
    LeetCode——Maximum Depth of Binary Tree
    kafka 安装及部署
    apache kafka系列之server.properties配置文件参数说明
  • 原文地址:https://www.cnblogs.com/buruainiaaaa/p/14121176.html
Copyright © 2020-2023  润新知