> 技术文档 > .NetCore下Ocelot + Nacos 实现负载均衡

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


接上一篇:“.NetCore 接入 Nacos,实现配置中心和服务注册”
本篇实现 Nacos对接Ocelot实现负载均衡,依旧基于.NetCore3.1实现,如在.Net6以及之上实现,更新组件版本。

文章目录

      • 一、所需类库
      • 二、封装类库
        • `Nacos`
        • `NacosServiceDiscoveryProviderFactory`
        • `OcelotBuilderExtensions`
      • 三、网关对接
        • 1、使用项目中本地ocelot.json文件
        • 2、ocelot配置存入Nacos,从Nacos中读取配置

一、所需类库

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

二、封装类库

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

Nacos

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

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
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
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配置如下:

\"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进行接入

services.AddOcelot().AddNacos();

完整代码

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配置内容如下:

{ \"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中增加

builder.AddJsonFile(\"ocelot.json\", optional: false, reloadOnChange: true);

完整代码

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中增加

var c = builder.Build();builder.AddNacosV2Configuration(c.GetSection(\"Nacos\"));

完整代码

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>(); });