Net5 Consul、Ocelot、Polly
1 简单介绍
Consul 可以实现服务的注册,服务发现,治理,健康检查等。
Ocelot 作为一个网关应用,主要的功能有路由、请求聚合、服务发现、统一认证、统一鉴权、限流熔断、并内置了负载均衡器等的集成。
Polly 是一个 .NET 弹性和瞬态故障处理库,允许开发人员以线程安全的方式来实现重试、断路、超时、隔离和回退策略。
2 Consul
2.1 安装Consul
# 拉取consul镜像
docker pull consul
# 启动容器
docker run --name consul -d -p 8500:8500 consul
访问8500即可打开Consul Web界面
2.2 编写注册Consul帮助类
引用Consul包
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Consul" Version="1.6.10.4" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
</ItemGroup>
</Project>
新建注册信息相关类 ConsulOption.cs
/// <summary>
/// Consul 注册发现相关参数
/// </summary>
public class ConsulOption
{
/// <summary>
/// 服务 必填
/// </summary>
public Service Service { get; set; }
/// <summary>
/// 服务健康检查地址 默认:/health
/// </summary>
public string ServiceHealthCheck { get; set; } = "/health";
/// <summary>
/// Consul 地址 默认:http://localhost:8500/
/// </summary>
public string ConsulAddress { get; set; } = "http://localhost:8500/";
}
/// <summary>
/// 服务
/// </summary>
public class Service
{
/// <summary>
/// http
/// </summary>
public string ServiceHead { get; set; } = "http";
/// <summary>
/// 服务名称
/// </summary>
public string ServiceName { get; set; }
/// <summary>
/// 服务IP
/// </summary>
public string ServiceIP { get; set; }
/// <summary>
/// 服务端口
/// </summary>
public int ServicePort { get; set; }
}
编写Consul注册扩展方法,包括心跳检查 X22ConsulExtension.cs
public static class X22ConsulExtension
{
/// <summary>
/// 配置文件注册Consul
/// </summary>
/// <param name="app"></param>
/// <param name="configuration"></param>
/// <returns></returns>
public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, IConfiguration configuration)
{
var serviceName = configuration["ServiceName"];
var consulAddress = configuration["ConsulAddress"];
var uri = new Uri(configuration["HealthUrl"]);
var consulOption = new ConsulOption
{
Service = new Service
{
ServiceName = serviceName,
ServiceHead = uri.Scheme,
ServiceIP = uri.Host,
ServicePort = uri.Port,
},
ServiceHealthCheck = uri.PathAndQuery,
ConsulAddress = consulAddress
};
return RegisterConsul(app, consulOption);
}
/// <summary>
/// 委托Consul
/// </summary>
/// <param name="app"></param>
/// <param name="option"></param>
/// <returns></returns>
public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, Action<ConsulOption> option)
{
var consulOption = new ConsulOption();
option.Invoke(consulOption);
return RegisterConsul(app, consulOption);
}
/// <summary>
/// 注册Consul
/// </summary>
/// <param name="app"></param>
/// <param name="consulOption"></param>
/// <returns></returns>
private static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, ConsulOption consulOption)
{
#region 检查配置项
if (string.IsNullOrEmpty(consulOption.Service.ServiceName) || string.IsNullOrEmpty(consulOption.Service.ServiceIP))
{
throw new Exception("请检查配置项!");
}
if (consulOption.Service.ServiceHead != "http" && consulOption.Service.ServiceHead != "https")
{
throw new Exception("请检查配置项!");
}
if (!consulOption.ServiceHealthCheck.StartsWith("/"))
{
throw new Exception("健康检查url必须以 / 开头!");
}
#endregion
var healthUrl = $"{consulOption.Service.ServiceHead}://{consulOption.Service.ServiceIP}:{consulOption.Service.ServicePort}{consulOption.ServiceHealthCheck}";
Console.WriteLine($"健康检查地址:{healthUrl}");
var consulClient = new ConsulClient(x =>
{
x.Address = new Uri(consulOption.ConsulAddress);
});
var registration = new AgentServiceRegistration()
{
ID = Guid.NewGuid().ToString(),
Name = consulOption.Service.ServiceName,// 服务名
Address = consulOption.Service.ServiceIP, // 服务绑定IP
Port = consulOption.Service.ServicePort,//, // 服务绑定端口
Check = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册
Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔
HTTP = healthUrl,//健康检查地址
Timeout = TimeSpan.FromSeconds(5)
}
};
// 服务注册
consulClient.Agent.ServiceRegister(registration).Wait();
// 应用程序终止时,服务取消注册
var lifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStopping.Register(() =>
{
consulClient.Agent.ServiceDeregister(registration.ID).Wait();
});
//健康检查
app.ServiceHealthCheck(consulOption.ServiceHealthCheck);
return app;
}
/// <summary>
/// 健康检查接口
/// </summary>
/// <param name="app"></param>
/// <param name="healthUrl"></param>
private static void ServiceHealthCheck(this IApplicationBuilder app,string healthUrl)
{
app.Map(healthUrl,
app => app.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.OK;
await context.Response.WriteAsync("Hello ServiceHealthCheck OK");
})
);
}
}
2.3 编写WebService Api
新建net5 web api 项目
在Startup.cs 注册Consul(通过上述扩展方法X22ConsulExtension)
#region 注册Consul
////dotnet run --urls="http://*:5728" --ip=192.168.0.111 --port=5728
//var ip = Configuration["ip"];
//var port = Convert.ToInt32(Configuration["port"]);
//var name = "X22ServiceTest";
//app.RegisterConsul(x => x.Service = new Service { ServiceIP = ip, ServicePort = port, ServiceName = name });
//dotnet run --urls="http://*:5728" --ip=192.168.0.111 --port=5728 --HealthUrl="http://192.168.0.111:5728/check" --ServiceName=X22ServiceTest --ConsulAddress=http://192.168.152.128:8500
app.RegisterConsul(Configuration);
#endregion
编写api
//WeatherForecastController.cs
[ApiController]
[Route("[controller]/[action]")]
public class WeatherForecastController : ControllerBase
{
private readonly IConfiguration _configuration;
public WeatherForecastController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpGet]
public string Test(int time = 0)
{
//time 用于测试超时熔断
Thread.Sleep(time * 1000);
return $"{_configuration["ip"]}:{_configuration["port"]}";
}
}
分别运行启动项目 ip 端口 心跳检测地址 服务名称 Consul地址
dotnet run --urls="http://*:5001" --ip=192.168.0.111 --port=5001 --HealthUrl="http://192.168.0.111:5001/check" --ServiceName=X22ServiceTest --ConsulAddress=http://192.168.152.128:8500
dotnet run --urls="http://*:5002" --ip=192.168.0.111 --port=5002 --HealthUrl="http://192.168.0.111:5002/check" --ServiceName=X22ServiceTest --ConsulAddress=http://192.168.152.128:8500
dotnet run --urls="http://*:5003" --ip=192.168.0.111 --port=5003 --HealthUrl="http://192.168.0.111:5003/check" --ServiceName=X22ServiceTest --ConsulAddress=http://192.168.152.128:8500
浏览器可访问,均正确返回信息
http://192.168.0.111:5001/WeatherForecast/Test?time=0
http://192.168.0.111:5001/WeatherForecast/Test?time=0
http://192.168.0.111:5001/WeatherForecast/Test?time=0
eg.
此时访问consul 8500 地址
eg.
3 Ocelot、Polly
3.1 新建网关项目 GetWay ,并引用相关包
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ocelot" Version="17.0.1" />
<PackageReference Include="Ocelot.Provider.Consul" Version="17.0.1" />
<PackageReference Include="Ocelot.Provider.Polly" Version="17.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
</ItemGroup>
</Project>
3.2 注册并使用Ocelot、Consul、Polly
//Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddOcelot().AddConsul().AddPolly();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseOcelot();
}
//Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(c =>
{
//加载ocelot配置文件
c.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
3.3 添加 ocelot.json 配置文件
{
"Routes": [
{
//转发到下游服务地址--url变量
"DownstreamPathTemplate": "/{url}",
//下游http协议
"DownstreamScheme": "http",
//负载方式,
"LoadBalancerOptions": {
"Type": "RoundRobin" // 轮询
//"RoundRobin:轮询机制,循环找到可以用的服务"
//"LeastConnection:最少连接数,跟踪发现现在有最少请求或处理的可用服务",
//"NoLoadBalancer:不使用负载均衡,直接访问config配置或者服务发现的第一个可用服务"
},
//"DownstreamHostAndPorts": [
// {
// "Host": "192.168.0.111",
// "Port": 5001 //服务端口
// }, //可以多个,自行负载均衡
// {
// "Host": "192.168.0.111",
// "Port": 5002 //服务端口
// },
// {
// "Host": "192.168.0.111",
// "Port": 5003 //服务端口
// }
//],
//上游地址
"UpstreamPathTemplate": "/X22/{url}", //网关地址--url变量 //冲突的还可以加权重Priority
"UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ],
"UseServiceDisConvery": true, //使用服务发现
"ServiceName": "X22ServiceTest", //Consul服务名称
////限流配置
//"RateLimitOptions": {
// "ClientWhitelist": [ "admin" ], // 白名单
// "EnableRateLimiting": true, // 是否启用限流
// "Period": "10s", // 统计时间段:1s, 5m, 1h, 1d
// "PeriodTimespan": 10, // 多少秒之后客户端可以重试
// "Limit": 5 // 在统计时间段内允许的最大请求数量
//},
//熔断设置
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, //允许多少个异常请求
"DurationOfBreak": 5000, // 熔断的时间5s,单位为ms
"TimeoutValue": 5000 //单位ms,如果下游请求的处理时间超过多少则自如将请求设置为超时 默认90秒
}
}
],
"GlobalConfiguration": {
//Ocelot应用地址
"BaseUrl": "http://192.168.0.111:5000",
"ServiceDiscoveryProvider": {
//Consul地址
"Host": "192.168.152.128",
//Consul端口
"Port": 8500,
"Type": "Consul" //由Consul提供服务发现,每次请求Consul
} //,
////限流
//"RateLimitOptions": {
// "QuotaExceededMessage": "errr:request fast!", //限流后响应内容
// "HttpStatusCode": 666, //http状态码可以自定义
// "ClientIdHeader": "client_id" // 用来识别客户端的请求头,默认是 ClientId
//}
}
}
3.4 Over
浏览器访问 http://localhost:5000/X22/WeatherForecast/Test?time=0
会自动转发到相应路由地址
eg.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了