# 前言
上一篇使用 Consul 完成了服务的注册与发现,实际中光有服务注册与发现往往是不够的,需要一个统一的入口来连接客户端与服务。
# Ocelot
官网:https://ocelot.readthedocs.io (opens new window) ,Ocelot 正是为.Net微服务体系提供一个统一的入口点,称为:Gateway(网关)。
首先创建一个空的 asp.net core web
项目:
注意:
ocelot.json
是Ocelot的配置文件,设置生成时需要复制到输出目录。ocelot.json
文件名不是固定的可以自己定义
使用 NuGet
安装 Ocelot
,简单修改几处默认代码:
Program.cs:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// 添加ocelot服务
services.AddOcelot();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 启用Ocelot中间件
app.UseOcelot().Wait();
}
2
3
4
5
6
7
8
9
10
11
12
ocelot.json:
{
"Routes": [
{
"DownstreamPathTemplate": "/orders",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "192.168.31.191",
"Port": 80
},
{
"Host": "192.168.31.191",
"Port": 81
},
{
"Host": "192.168.31.191",
"Port": 82
}
],
"UpstreamPathTemplate": "/orders",
"UpstreamHttpMethod": [
"Get"
],
"LoadBalancerOptions": {
"Type": "RoundRobin" //负载均衡,轮询机制 LeastConnection/RoundRobin/NoLoadBalancer/CookieStickySessions
}
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
这里将服务实例的地址写在配置文件中。
Routes
节点用来配置路由:
Downstream
代表下游,也就是服务实例Upstream
代表上游,也就是客户端。这里路径比较简单,只有/orders
路径中如果有不固定参数则使用{}
匹配。
这里配置的意思是:客户端访问网关的 /orders
,网关会转发给服务实例的 /orders
。注意:上游的路径不一定要和下游一致,比如上游路径可以配置成 /api/orders
。
LoadBalancerOptions
节点用来配置负载均衡,Ocelot 内置了 LeastConnection
、RoundRobin
、NoLoadBalancer
、CookieStickySessions
4种负载均衡策略:
LeastConnection
最少连接,跟踪哪些服务正在处理请求,并把新请求发送到现有请求最少的服务上。该算法状态不在整个Ocelot集群中分布RoundRobin
轮询可用的服务并发送请求。 该算法状态不在整个Ocelot集群中分布NoLoadBalancer
不负载均衡,从配置或服务发现提供程序中取第一个可用的下游服务CookieStickySessions
使用cookie关联所有相关的请求到制定的服务
BaseUrl
节点用来配置 Ocelot 网关将要运行的地址。
浏览器访问:
# 客户端
上面实现通过 Ocelot 网关访问服务实例,调整客户端代码:这里选择直接新建 GatewayServiceHelper
:
using RestSharp;
using System;
using System.Threading.Tasks;
namespace Web.Client
{
/// <summary>
/// 通过OcelotGateway调用服务
/// </summary>
public class GatewayServiceHelper : IServiceHelper
{
public async Task<string> GetOrder()
{
var client = new RestClient("http://localhost:5000");
var request = new RestRequest("/orders", Method.GET);
var response = await client.ExecuteAsync(request);
return response.Content;
}
public void GetServices()
{
throw new NotImplementedException();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Startup.cs:修改注入类型
services.AddSingleton<IServiceHelper, GatewayServiceHelper>();
下面获取服务地址的代码也不需要了
// 程序启动时获取服务列表
serviceHelper.GetServices();
2
经过以上调整现在客户端对服务的调用都通过网关进行中转,客户端不再关心服务实例的地址,只需要知道网关地址就可以。另外服务端也避免了服务地址直接暴露给客户端。这样做对客户端,服务都非常友好。但是又出现了一个新的问题:目前服务地址写在 ocelot.json
配置文件中,一旦服务变化,需要人为的修改配置文件,这又显得不太合理。这里比较常用的方案是:结合Consul来实现服务发现。
# 服务发现
NuGet
安装Ocelot.Provider.Consul
后,修改Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// 添加Ocelot服务并添加Consul支持
services.AddOcelot().AddConsul();
}
2
3
4
5
修改ocelot.json配置:
{
"Routes": [
{
"DownstreamPathTemplate": "/orders",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/orders",
"UpstreamHttpMethod": [ "Get" ],
"ServiceName": "order.service",
"LoadBalancerOptions": {
"Type": "RoundRobin"
}
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000",
"ServiceDiscoveryProvider": {
"Scheme": "http",
"Host": "192.168.31.191",
"Port": 8500,
"Type": "Consul"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这个配置很好理解,就是把 DownstreamHostAndPorts
节点去掉然后增加了 ServiceDiscoveryProvider
服务发现相关配置。
注意,Ocelot 除了支持 Consul 服务发现以外,还有 Eureka 也可以,Eureka 也是一个类似的注册中心
浏览器测试:
至此就实现了服务注册与发现和api网关的基本功能。接下来就要提到:服务治理。
# 服务治理
服务治理没有非常明确的定义。它的作用简单来说,就是帮我们更好的管理服务,提升服务的可用性。缓存、限流、熔断、链路追踪等等都属于常用的服务治理手段。之前讲的负载均衡,服务发现也可以算是服务治理。
# 缓存
在 Ocelot 中启用缓存,需要NuGet
安装Ocelot.Cache.CacheManager
,修改Startup.cs
中的 ConfigureServices()
方法:
public void ConfigureServices(IServiceCollection services)
{
services.AddOcelot()
.AddConsul()
.AddCacheManager(p =>
{
p.WithDictionaryHandle();
});
}
2
3
4
5
6
7
8
9
修改 ocelot.json
配置文件:
{
"Routes": [
{
"DownstreamPathTemplate": "/orders",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/orders",
"UpstreamHttpMethod": [ "Get" ],
"ServiceName": "order.service",
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
// 缓存
"FileCacheOptions": {
"TtlSeconds": 5,
"Region": "regionname"
}
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000",
"ServiceDiscoveryProvider": {
"Scheme": "http",
"Host": "192.168.31.191",
"Port": 8500,
"Type": "Consul"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
在 Routes 路由配置中增加 FileCacheOptions
:
TtlSeconds
缓存的过期时间Region
缓冲区名称,目前用不到
代码修改完编译重启一下网关项目,然后打开浏览器测试会发现5秒之内的请求都是同样的缓存数据。Ocelot也支持自定义缓存。
# 限流
限流就是限制客户端一定时间内的请求次数。
修改 ocelot.json
配置文件:
{
"Routes": [
{
"DownstreamPathTemplate": "/orders",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/orders",
"UpstreamHttpMethod": [ "Get" ],
"ServiceName": "order.service",
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
// 缓存
"FileCacheOptions": {
"TtlSeconds": 5,
"Region": "regionname"
},
// 限流
"RateLimitOptions": {
"ClientWhitelist": [ "SuperClient" ],
"EnableRateLimiting": true,
"Period": "2s",
"PeriodTimespan": 2,
"Limit": 1
}
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000",
"ServiceDiscoveryProvider": {
"Scheme": "http",
"Host": "192.168.31.191",
"Port": 8500,
"Type": "Consul"
},
"RateLimitOptions": {
"DisableRateLimitHeaders": false,
"QuotaExceededMessage": "too many requests...",
"HttpStatusCode": 999,
"ClientIdHeader": "Test"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
在 Routes 路由配置中增加 RateLimitOptions
:
ClientWhitelist
客户端白名单(白名单中的客户端不受限流影响)EnableRateLimiting
是否限流Period
限流的单位时间,例如1s、5m、1h、1d等PeriodTimespan
客户端达到请求上限多少秒后可以重试Limit
客户端在定义的时间内可以发出的最大请求数
在 GlobalConfiguration 配置中也增加 RateLimitOptions
:
DisableRateLimitHeaders
是否禁用X-Rate-Limit
和Retry-After
标头(请求达到上限时response header中的限制数和多少秒后能重试)QuotaExceededMessage
:请求达到上限时返回给客户端的消息HttpStatusCode
:请求达到上限时返回给客户端的HTTP状态码
ClientIdHeader
可以允许自定义用于标识客户端的标头。默认情况下为ClientId
代码修改完编译重启一下网关项目,然后打开浏览器测试会发现限制已经生效。
# 超时/熔断
- 超时:网关请求服务时可容忍的最长响应时间
- 熔断:当请求某个服务的异常次数达到一定量时,网关在一定时间内就不再对这个服务发起请求直接熔断
在 Ocelot 中启用超时/熔断,需要 NuGet
安装Ocelot.Provider.Polly
,修改Startup.cs
中的 ConfigureServices()
方法:
public void ConfigureServices(IServiceCollection services)
{
services.AddOcelot()
.AddConsul()
.AddCacheManager(p =>
{
p.WithDictionaryHandle();
}).AddPolly();
}
2
3
4
5
6
7
8
9
修改 ocelot.json
配置文件:
{
"Routes": [
{
"DownstreamPathTemplate": "/orders",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/orders",
"UpstreamHttpMethod": [ "Get" ],
"ServiceName": "order.service",
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
// 缓存
"FileCacheOptions": {
"TtlSeconds": 5,
"Region": "regionname"
},
// 限流
"RateLimitOptions": {
"ClientWhitelist": [ "SuperClient" ],
"EnableRateLimiting": true,
"Period": "2s",
"PeriodTimespan": 2,
"Limit": 1
},
// 超时熔断
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10000,
"TimeoutValue": 5000
}
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000",
"ServiceDiscoveryProvider": {
"Scheme": "http",
"Host": "192.168.201.191",
"Port": 8500,
"Type": "Consul"
},
"RateLimitOptions": {
"DisableRateLimitHeaders": false,
"QuotaExceededMessage": "too many requests...",
"HttpStatusCode": 999,
"ClientIdHeader": "Test"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
ExceptionsAllowedBeforeBreaking
发生错误的次数DurationOfBreak
熔断时间TimeoutValue
超时时间
以上配置意思是当请求服务发生3次错误时,就熔断10秒,期间客户端的请求直接返回错误,10秒后恢复。
← 微服务项目之注册发现 微服务之事件总线 →