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