.NetCore下Ocelot + Nacos 实现负载均衡

接上一篇:".NetCore 接入 Nacos,实现配置中心和服务注册"

本篇实现 Nacos对接Ocelot实现负载均衡,依旧基于.NetCore3.1实现,如在.Net6以及之上实现,更新组件版本。

文章目录

一、所需类库

nacos-sdk-csharp.AspNetCore 版本1.3.8,.Net6以上,更新到1.3.10
Ocelot 版本16.0.1,.Net6以上可更新到最新版本

二、封装类库

Nacos

此类名称不能修改,因为需要与Ocelot中ServiceDiscoveryProvider的Type匹配

csharp 复制代码
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values;
using Nacos.V2;
using Microsoft.Extensions.Options;
using NacosConstants = Nacos.V2.Common.Constants;
using Nacos.AspNetCore.V2;
using Ocelot.Logging;
using Nacos.V2.Naming.Dtos;
using Service = Ocelot.Values.Service;

namespace Ocelot.Provider.Nacos.AspNetCore
{
	
    public class Nacos : IServiceDiscoveryProvider
    {
        private readonly INacosNamingService _client;
        private readonly string _serviceName;
        private readonly string _groupName;
        private readonly IOcelotLogger _logger;
        private readonly List<string> _clusters;

        public Nacos(string serviceName, INacosNamingService client, IOptions<NacosAspNetOptions> options, IOcelotLoggerFactory factory)
        {
            _serviceName = serviceName;
            _client = client;
            _groupName = string.IsNullOrWhiteSpace(options.Value.GroupName) ?
                NacosConstants.DEFAULT_GROUP : options.Value.GroupName;
            _clusters = (string.IsNullOrWhiteSpace(options.Value.ClusterName) ? NacosConstants.DEFAULT_CLUSTER_NAME : options.Value.ClusterName).Split(",").ToList();
            _logger = factory.CreateLogger<Nacos>();
        }


        public async Task<List<Service>> Get()
        {
            var services = new List<Service>();
            _logger.LogInformation($"Trying to get service instances from Nacos for service: {_serviceName}");
            var instances = await _client.SelectInstances(_serviceName, _groupName, _clusters, true);
            if (instances == null || !instances.Any())
            {
                _logger.LogWarning($"No service instances found in Nacos for service: {_serviceName}");
                return services;
            }
            foreach (var serviceEntry in instances)
            {
                if (IsValid(serviceEntry))
                {
                    services.Add(BuildService(serviceEntry));
                }
                else
                {
                    _logger.LogWarning($"Unable to use service Port: {serviceEntry.Port} as it is invalid.");
                }
            }

            _logger.LogInformation($"Found {services.Count} service instances in Nacos for service: {_serviceName}");
            return services;
        }

        private Service BuildService(Instance instance)
        {
            return new Service(
                instance.ServiceName,
                new ServiceHostAndPort(instance.Ip, instance.Port),
                instance.InstanceId,
                GetVersionFromMetadata(instance.Metadata),
               GetTagFromMetadata(instance.Metadata));
        }

        private bool IsValid(Instance instance)
        {
            if (instance.Port <= 0)
            {
                return false;
            }
            return true;
        }
        private IEnumerable<string> GetTagFromMetadata(Dictionary<string, string> metadata)
        {
            if (metadata == null)
            {
                return Enumerable.Empty<string>();
            }
            return metadata.Select(t => { return $"{t.Key}:{t.Value}"; });
        }
        private string GetVersionFromMetadata(Dictionary<string, string> metadata)
        {
            if (metadata.TryGetValue("version", out var version))
            {
                return version;
            }
            return "";
        }
    }
}
NacosServiceDiscoveryProviderFactory
csharp 复制代码
using Ocelot.ServiceDiscovery;
using Microsoft.Extensions.DependencyInjection;
using Nacos.V2;
using Microsoft.Extensions.Options;
using Nacos.AspNetCore.V2;
using Ocelot.Logging;

namespace Ocelot.Provider.Nacos.AspNetCore
{
    public static class NacosServiceDiscoveryProviderFactory
    {
        public static ServiceDiscoveryFinderDelegate Get = (provider, config, route) =>
        {
            var client = provider.GetService<INacosNamingService>();
            if (config.Type?.ToLower() == "nacos" && client != null)
            {
                var option = provider.GetService<IOptions<NacosAspNetOptions>>();
                var logger = provider.GetRequiredService<IOcelotLoggerFactory>();
                return new Nacos(route.ServiceName, client, option, logger);
            }
            return null;
        };

    }
}
OcelotBuilderExtensions
csharp 复制代码
using Microsoft.Extensions.DependencyInjection;
using Nacos.AspNetCore.V2;
using Ocelot.DependencyInjection;

namespace Ocelot.Provider.Nacos.AspNetCore
{
    public static class OcelotBuilderExtensions
    {
        public static IOcelotBuilder AddNacos(this IOcelotBuilder builder, string section = "nacos")
        {
        	//网关服务注册到Nacos
            builder.Services.AddNacosAspNet(builder.Configuration, section);
            builder.Services.AddSingleton(NacosServiceDiscoveryProviderFactory.Get);
            return builder;
        }
    }
}

三、网关对接

引用Ocelot.Provider.Nacos.AspNetCore
appsettings.json配置如下:

csharp 复制代码
"Nacos": {
  "Listeners": [ //配置监听列表,包含多个监听项
    {
      "Optional": false, //是否为可选配置。false表示如果配置不存在,应用启动会失败;true表示配置不存在时忽略
      "DataId": "ocelot",
      "Group": "DEFAULT_GROUP" //配置所属的分组,默认为DEFAULT_GROUP
    }
  ],
  "ServerAddresses": [ "http://192.168.5.210:8848" ], //Nacos 服务器地址列表
  "DefaultTimeOut": 15000,
  "Namespace": "8f67799f-0eb9-42b1-94e5-080d9b1c56ea", // 命名空间 ID,用于隔离不同环境的配置和服务,Please set the value of Namespace ID !!!!!!!!
  "ListenInterval": 1000, //监听间隔时间,单位为毫秒
  "ServiceName": "NacosGateway", //注册到注册中心的服务名称
  "Weight": 100, //服务权重,用于服务路由时的负载均衡计算
  "RegisterEnabled": true, //是否启用服务注册
  "InstanceEnabled": true, //实例是否启用
  "Ephemeral": true, //是否为临时实例,true表示是临时实例,服务宕机后会被自动摘除
  "Secure": false, //是否使用安全连接
  "UserName": "nacos",
  "Password": "nacos",
  "ConfigUseRpc": false, //是否使用 RPC 协议获取配置
  "NamingUseRpc": false, //是否使用 RPC 协议进行服务发现
  "NamingLoadCacheAtStart": "", //启动时是否加载服务发现缓存
  "LBStrategy": "WeightRandom", //负载均衡策略,WeightRandom表示加权随机,WeightRoundRobin表示加权轮询
  "Metadata": { //服务实例的元数据信息,为键值对形式
    "version": "1.0",
    "feature": "true"
  }
}

以上Listeners配置是因为ocelot配置内容也存放在Nacos中,如是使用本地ocelot.json文件,则无需此项配置。

Startup中使用AddNacos进行接入

csharp 复制代码
services.AddOcelot().AddNacos();

完整代码

csharp 复制代码
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers(config =>
        {
        });
        // 添加网关
        services.AddOcelot().AddNacos(); ;

    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
    {
        app.UseOcelot().Wait();
    }
}

Ocelot配置内容如下:

csharp 复制代码
{
  "Routes": [
    {
      "ServiceName": "NacosWebApi",
      "DownstreamScheme": "http",
      "DownstreamPathTemplate": "/{url}",
      "UpstreamPathTemplate": "/api/{url}",
      "UseServiceDiscovery": true,
      "UpstreamHttpMethod": [ "Get" ],
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      }
    }
  ],
  "GlobalConfiguration": {
    "ServiceDiscoveryProvider": {
      "Type": "Nacos"
    }
  }
}
1、使用项目中本地ocelot.json文件

Program中增加

csharp 复制代码
builder.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);

完整代码

csharp 复制代码
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, builder) =>
        {
            builder.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
        })
         .ConfigureWebHostDefaults(webBuilder =>
         {
             webBuilder.UseStartup<Startup>();
         });
2、ocelot配置存入Nacos,从Nacos中读取配置

Program中增加

csharp 复制代码
var c = builder.Build();
builder.AddNacosV2Configuration(c.GetSection("Nacos"));

完整代码

csharp 复制代码
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, builder) =>
        {
            var c = builder.Build();
            builder.AddNacosV2Configuration(c.GetSection("Nacos"));
        })
         .ConfigureWebHostDefaults(webBuilder =>
         {
             webBuilder.UseStartup<Startup>();
         });
相关推荐
几何心凉21 小时前
云电脑是什么?与普通电脑的区别在哪里?——天翼云电脑体验推荐
运维·负载均衡
一个帅气昵称啊1 天前
NetCoreKevin-DDD-微服务-WebApi-AI智能体、AISK集成、MCP协议服务、SignalR、Quartz 框架-16-部署与基础设施
微服务·云原生·架构·系统架构·.netcore
忧郁的蛋~2 天前
在.NET标准库中进行数据验证的方法
后端·c#·asp.net·.net·.netcore
willhuo2 天前
学生请假就餐系统
运维·服务器·.netcore
一包烟电脑面前做一天3 天前
.NetCore 接入 Nacos,实现配置中心和服务注册
nacos·.netcore·服务注册发现·配置中心
laoma-cloud4 天前
HAProxy 负载均衡全解析:从基础部署、负载策略到会话保持及性能优化指南
运维·负载均衡·haproxy
草履虫建模5 天前
若依微服务一键部署(RuoYi-Cloud):Nacos/Redis/MySQL + Gateway + Robot 接入(踩坑与修复全记录)
redis·mysql·docker·微服务·云原生·nacos·持续部署
切糕师学AI6 天前
.Net Core Web 架构(管道机制)的底层实现
中间件·系统架构·kestrel·mvc·.netcore·路由·请求管道
努力的小不点丶6 天前
详解 外部负载均衡器 → Ingress Controller Pod 这个过程
运维·负载均衡