接上一篇:“.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>();
});