在上一篇文章中引入了 Ocelot,客户端的调用变得简单的,但是对于网关层来说,每新增一个接口,或者服务实例,我都需要把网关在重新更新发布,这样无疑增加了网关层的负担,如果说我能自动识别服务实例,并且不需要把每个接口都写一遍,那我的网关岂不是只需要更新一次就好,不管你服务实例怎么增加,接口怎么增加,我都能自动识别。
Consul就可以自动做到服务的注册与发现,那怎么注册发现呢,其实Consul是一个单独的进程,启动Consul之后,在服务里向Consul去注册,意思就是告诉Consul,我是用户服务实例1,我的地址是 localhost:5050,我启动了,你把我加入到你的名单中,然后Consul就把用户服务实例1加入名单中了。接着用户实例2,用户实例3都加入到Consul的名单中了。
这时候,网关就不直接去调用服务实例了,网关还不知道有哪些服务实例呢,它需要先去问Consul,我想要用户服务实例,你把你所有的用户服务实例地址都给我,然后Consul把 localhost:5051,localhost:5052,localhost:5053,等地址都给了网关,然后网关在按照一定的策略,比如轮询,随机等算法去获取对应的服务实例地址,然后再去请求。
所以Consul做的事情很简单,就是管理各个服务实例的地址,当网关或者谁要地址了,就通过名字把当前名字下服务实例地址全部返回。
Consul除了可以做服务的发现,还可以做服务的移除,关键字是健康检查,或者说心跳检查,当服务实例注册到Consul时,会同时提供一个健康检查地址,Consul就可以按照用户设定的时间,比如说每5秒向健康检查地址发送一个请求,若你能正常响应了,我就认为你还活着,若没响应,或者没按规定时间内响应,Consul会认为服务已经挂掉了,就把这个服务实例从当前的服务名称下进行移除,下次网关再要地址了,Consul不会把已经挂掉的地址在返回了。
Consul的安装与启动Consul的官网地址地址: https://www.consul.io
下载好之后就是一个Consul.exe文件,这是Windows环境下的,其他环境请自行查找资料。
然后把当前Consul.exe上一级文件夹地址加入到环境变量中,这样不用每次都进入到这个文件夹去启动了。
启动Consul,打开Terminal,输入
consul.exe agent -dev
若能出现Consul agent running,说明Consul 启动成功了。
这时可以打开浏览器,端口号默认8500,若能出现网页,说明Consul 正常了。
服务实例
Consul已经准备好了,那接下来就应该把服务注册到Consul中了,由于多个服务都需要注册,所以单独把注册到Consul的代码封装为一个类库,其他项目去引用即可。新建类库,ConsulBuilder,然后添加两个文件。
添加Consul 的nuget包
ConsulRegistrationExtensions.cs
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Consul;
using ConsulBuilder;
namespace ConsulBuilder
{
public static class ConsulRegistrationExtensions
{
public static IApplicationBuilder AddConsul(this IApplicationBuilder app, ConsulServiceOptions consulServiceOptions)
{
var consulClient = new ConsulClient(x =>
{
// consul服务地址,默认安装consul后端口为8500
x.Address = new Uri(consulServiceOptions.ConsulAddress);
});
var registration = new AgentServiceRegistration()
{
ID = Guid.NewGuid().ToString(),
Name = consulServiceOptions.ServiceName,// 服务名
Address = consulServiceOptions.ServiceIP, // 服务绑定IP(也就是你这个项目运行的ip地址)
Port = consulServiceOptions.ServicePort, // 服务绑定端口(也就是你这个项目运行的端口)
Check = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册
Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔
HTTP = consulServiceOptions.ServiceHealthCheck,//健康检查地址
Timeout = TimeSpan.FromSeconds(5)
}
};
// 服务注册
consulClient.Agent.ServiceRegister(registration).Wait();
return app;
}
}
}
ConsulServiceOptions.cs
namespace ConsulBuilder
{
// Consul配置模型类
public class ConsulServiceOptions
{
///
/// 服务名称
///
public string ServiceName { get; set; }
///
/// 服务IP
///
public string ServiceIP { get; set; }
///
/// 服务端口
///
public int ServicePort { get; set; }
///
/// 服务健康检查地址
///
public string ServiceHealthCheck { get; set; }
///
/// Consul 地址
///
public string ConsulAddress { get; set; }
}
}
接下来,我们以用户服务举例,首先引用ConsulBuilder类库,接着修改Startup.cs文件的Configure方法。
//获取关于Consul的配置节点
var consulSection = Configuration.GetSection("Consul");
var consulOption = new ConsulServiceOptions
{
ServiceName = consulSection["ServiceName"],
ServiceIP = consulSection["ServiceIP"],
ServicePort = Convert.ToInt32(consulSection["ServicePort"]),
ServiceHealthCheck = consulSection["ServiceHealthCheck"],
ConsulAddress = consulSection["ConsulAddress"]
};
//注册consul服务
app.AddConsul(consulOption);
修改appsetting.json
//Consul的配置
"Consul": {
"ServiceName": "UserService",
"ServiceIP": "localhost",
"ServicePort": 5050,
"ServiceHealthCheck": "https://localhost:5050/api/HealthCheck",
"ConsulAddress": "http://127.0.0.1:8500"
},
OK ,这样我们的服务在启动时就会自动注册到Consul了.
至于HealthCheck,代码如下,其实是直接返回了Ok(),即可。
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace EasyShop.UserService.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class HealthCheckController:ControllerBase
{
///
/// 健康检查地址
///
///
public IActionResult HealthCheck()
{
return Ok();
}
}
}
其他服务的配置过程一样,不在赘述,
网关
若服务实例可以正常被Consul发现,接下来就需要修改网关下的Ocelot.json文件,由Consul提供服务实例地址,而不是直接在json文件中配置。
添加nuget包。
Ocelot.json 修改后如下。
{
// 转发路由,数组中的每个元素都是某个服务的一组路由转发规则
"Routes": [
{
"ServiceName": "UserService", //对应consul配置的ServiceName
// Uri方案,http、https
"DownstreamScheme": "https",
// 下游(服务提供方)服务路由模板
"DownstreamPathTemplate": "/api/user/{path}",
// 上游(客户端,服务消费方)请求路由模板
"UpstreamPathTemplate": "/user/{path}",
"UpstreamHttpMethod": [ "Get", "Post" ],
"LoadBalancerOptions": {
"Type": "RoundRobin" //轮询
},
"UseServiceDiscovery": true,
"RateLimitOptions": {
"ClientWhitelist": [ "admin" ], // 白名单
"EnableRateLimiting": true, // 是否启用限流
"Period": "1s", // 统计时间段:1s, 5m, 1h, 1d
"PeriodTimespan": 15, // 多少秒之后客户端可以重试
"Limit": 2 // 在统计时间段内允许的最大请求数量
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 2, // 允许多少个异常请求
"DurationOfBreak": 15000, // 熔断的时间,单位为毫秒
"TimeoutValue": 5000 // 如果下游请求的处理时间超过多少则视如该请求超时
},
"FileCacheOptions": { // cache response data - ttl: 10s 10秒内相同url请求直接返回缓存数据
"TtlSeconds": 10,
"Region": ""
}
//,身份认证
//"AuthenticationOptions": {
// "AuthenticationProviderKey": "ApiGatewayKey",
// "AllowedScopes": [ "UserService" ]
//}
}
],
"GlobalConfiguration": {
"BaseUrl": "https://localhost:44335",
"ReRouteIsCaseSensitive": false,
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8500,
"Type": "Consul" //由Consul提供服务发现
},
"RateLimitOptions": {
"DisableRateLimitHeaders": false, // Http头 X-Rate-Limit 和 Retry-After 是否禁用
"QuotaExceededMessage": "Too many requests, are you OK?", // 当请求过载被截断时返回的消息
"HttpStatusCode": 999, // 当请求过载被截断时返回的http status
"ClientIdHeader": "client_id" // 用来识别客户端的请求头,默认是 ClientId
}
}
}
Routes是一个数组,可以配置多个服务实例。在配置里我们可以看到有这么一个配置。"UseServiceDiscovery": true ,这就表示采用服务发现的地址。而在GlobalConfiguration里,配置了Consul的地址。
Startup.cs 修改如下
public void ConfigureServices(IServiceCollection services)
{
//允许跨域请求
services.AddCors(options =>
{
//this defines a CORS policy called "default"
options.AddPolicy("default", policy =>
{
policy.WithOrigins("http://localhost:8088")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddOcelot(Configuration).AddConsul().AddPolly();
}
和之前的区别在于,之前只引入了 Ocelot,而这次还引入了Consul和Polly。 Configure配置不变。
项目地址:https://gitee.com/limeng66/easy-shop



