序言
[.NET Core微服务架构] -- Ocelot网关路由、缓存、限流、熔断,Polly策略 - 掘金 (juejin.cn)
上篇文章我们讲了网关的一些功能和策略,但是有没有发现一点问题呢?我们配置的下游是不是都是直接写在配置里的,如果同一个服务开启或关闭多个进程的时候,我们怎么去动态的扩展呢?那就是我们今天要说到的服务注册与发现,现在市面上也有比较好的中间件去做这个事情,我们前面用的网关的Oclot也是支持扩展这些中间件。Eureka、Consul和Nacos等等。本篇文章选择Consul来在.NET Core中实现。
产品比较
配置中心
| 配置中心 | |
|---|---|
| eureka | 不支持 |
| nacos | 支持 用起来简单,支持动态刷新 |
| consul | 支持 但用起来偏麻烦,支持动态刷新 |
注册中心
| eureka | nacos | consul | |
|---|---|---|---|
| 应用内/外 | 直接集成到应用中,依赖于应用自身完成服务的注册与发现 | 属于外部应用,侵入性小 | 属于外部应用,侵入性小 |
| ACP原则 | 遵循AP(可用性+分离容忍)原则,有较强的可用性,服务注册快,但牺牲了一定的一致性 | 通知遵循CP原则(一致性+分离容忍) 和AP原则(可用性+分离容忍) | 遵循CP原则(一致性+分离容忍) 服务注册稍慢,由于其一致性导致了在Leader挂掉时重新选举期间真个consul不可用 |
| 版本迭代 | 目前已经不进行升级 | 目前仍然进行版本迭代 | 目前仍然进行版本迭代 |
| 访问协议 | HTTP | HTTP/动态DNS/UDP | HTTP/DNS |
| 雪崩保护 | 支持雪崩保护 | 支持雪崩保护 | 不支持雪崩保护 |
| 界面 | 英文界面 | 中文界面 | 英文界面 |
| 上手 | 容易 | 极易,中文文档 | 复杂一点 |
整体工作流程
我自己画了一个简单的工作流程图,比较乱,大概能看懂什么意思就行,首先我们服务启动的时候会把自身的信息传给注册中心,包括IP端口之类的,注册中心会定时去检查服务是否运行正常,如果异常的会在服务列表中删掉。然后当前端或者外部发起请求时,先经过网关,网关会调用注册中心获取到符合当前策略的地址,然后去调用服务,拿到返回结果后再返回。
Consul
简介
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等)。使用起来也较 为简单。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。
安装
下载Consul
官网下载地址: www.consul.io/downloads.h…
打开cmd控制台,输入consul -v查看Consul版本,确认Consul是否正常。
命令consul agent -dev启动,看到Consul agent running! 启动成功
启动成功后访问http://localhost:8500就能进入Consul的管理控制台
使用
在服务中添加服务注册代码,为了代码的复用,我们单独新建个类库管理所有的注入。
添加依赖包
<ItemGroup>
<PackageReference Include="Consul" Version="1.6.10.4" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
</ItemGroup>
新建Consul注册类
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System.Net;
namespace Extensions
{
public static class ConsulRegister
{
/// <summary>
/// 服务注册到consul
/// </summary>
/// <param name="app"></param>
/// <param name="lifetime"></param>
public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime, int Port = 0)
{
var consulClient = new ConsulClient(c =>
{
//consul地址
c.Address = new Uri(configuration["ConsulSetting:ConsulAddress"]);
});
string ServiceName = configuration["ConsulSetting:ServiceName"];
string ServiceIP = configuration["ConsulSetting:ServiceIP"];
int ServicePort = Port != 0 ? Port : int.Parse(configuration["ConsulSetting:ServicePort"]);
var registration = new AgentServiceRegistration()
{
ID = Guid.NewGuid().ToString(),//服务实例唯一标识
Name = ServiceName,//服务名
Address = ServiceIP, //服务IP
Port = ServicePort,//服务端口 因为要运行多个实例,端口不能在appsettings.json里配置,在docker容器运行时传入
Check = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册
Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔
HTTP = $"http://{ServiceIP}:{ServicePort}/Health",//健康检查地址
Timeout = TimeSpan.FromSeconds(5)//超时时间
}
};
//健康检查,不需要新建Controller
app.MapWhen(context => context.Request.Path.Equals("/Health"), applicationBuilder => applicationBuilder.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.OK;
await context.Response.WriteAsync("OK");
}));
//服务注册
consulClient.Agent.ServiceRegister(registration).Wait();
//应用程序终止时,取消注册
lifetime.ApplicationStopping.Register(() =>
{
consulClient.Agent.ServiceDeregister(registration.ID).Wait();
});
Console.WriteLine($"{ServiceName}服务注册成功");
return app;
}
}
}
更改服务配置文件appsettings.json
"ConsulSetting": {
"ServiceName": "UserService",
"ServiceIP": "localhost",
"ConsulAddress": "http://localhost:8500" //注意,docker容器内部无法使用localhost访问宿主机器,如果是控制台启动的话就用localhost
},
在管道中调用服务注册的方法
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "UserApis v1"));
}
app.UseRouting();
app.UseAuthorization();
//注册Consul服务
app.RegisterConsul(Configuration, lifetime);
//方便调试传输固定端口
//app.RegisterConsul(Configuration, lifetime, 8060);
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
我们启动下看服务能不能注册成功
dotnet run --urls="http://localhost:8001" --ConsulSetting:ServicePort="8001"
dotnet run --urls="http://localhost:8002" --ConsulSetting:ServicePort="8002"
可以看到Consul的管理节目能够看到注册了两个UserService,端口分别是8001和8002
Consul是注册成功了,那如何搭配网关一起使用呢?
搭配网关一起使用
在网关项目中引入Consul依赖包
<PackageReference Include="Ocelot.Provider.Consul" Version="18.0.0" />
修改Ocelot配置
{
"Routes": [
{
"UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ],
"UpstreamPathTemplate": "/api/User/{everything}",
"ServiceName": "UserService", //consul 服务中 Service的名称
"DownstreamPathTemplate": "/User/{everything}",
"DownstreamScheme": "http",
"LoadBalancerOptions": {
"Type": "RoundRobin" //RoundRobin轮询 最少连接数的服务器LastConnection 不负载均衡NoLoadBalance
}
}
],
"GlobalConfiguration": {
"ServiceDiscoveryProvider": { // Consul 服务发现配置
"Host": "localhost", // Consul 地址
"Port": 8500,
"Type": "Consul" //Nacos Consul
}
}
}
容器中添加Consul
services.AddOcelot().AddConsul();
测试下看看效果
可以跟我们原来配置的效果是一样的
Nacos
Nacos官网:home (nacos.io)
由于Nacos是阿里巴巴开源出来的,所以没有官方支持.NET的库,我们只能选择第三方的库来实现。感兴趣的可以去了解一下。有时间会单独写篇文章介绍下.NET Core如何使用Nacos。
Ocelot集成Nacos:Ocelot.Provider.Nacos: Repo for Nacos integration with Ocelot (github.com)
.NET支持Nacos的SDK:nacos-sdk-csharp: This nacos csharp sdk (github.com)