.Net Core Ocelot Consul 实现API网关 服务注册 服务发现 负载均衡

转载至@蜗牛丨大神的.net core Ocelot Consul 实现API网关 服务注册 服务发现 负载均衡一文,仅对文中所做部分内容进行更新及修改,版权归属原作者。谢谢

文章内容:

大神张善友 分享过一篇 《.NET Core 在腾讯财付通的企业级应用开发实践》里面就是用.net core 和 Ocelot搭建的可扩展的高性能Api网关。

Ocelot(http://ocelot.readthedocs.io)是一个用.NET Core实现并且开源的API网关,它功能强大,包括了:路由、负载均衡、请求聚合、认证、鉴权、限流熔断等,这些功能只都只需要简单的配置即可完成。

Consul(https://www.consul.io)是一个分布式,高可用、支持多数据中心的服务注册、发现、健康检查和配置共享的服务软件,由 HashiCorp 公司用 Go 语言开发。

Ocelot天生集成对Consul支持,在OcelotGateway项目中Ocelot.json配置就可以开启ocelot+consul的组合使用,实现服务注册、服务发现、健康检查、负载均衡。

软件版本

Asp.net Core:3.0预览5

Ocelot:13.5.0(开发时最新)

Consul:1.5.0(开发时最新)

 本文分开两部分:1、基于Ocelot搭建Api网关;2、Ocelot+Consul 实现下游服务的服务注册、服务发现、健康检查、负载均衡。

项目结构

Snai.Ocelot 网关:

Snai.ApiGateway Asp.net Core 2.0 Api网关(由于在3.0预览5版上面发生了依赖问题,没有找到解决方案,所以我退回到了Core2.2稳定版本)

Snai.ApiServiceA  Asp.net Core 3.0 Api下游服务A

Snai.ApiServiceB  Asp.net Core 3.0 Api下游服务B

ApiServiceA和ApiServiceB其实是一样的,用于负载,为了测试方便,我建了两个项目

Consul:(我从官网下载的1.5.0中只有一个可执行文件,并没有其他的目录及文件,可能是实现的工具比较新的缘故

conf 配置目录

data 缓存数据目录,可清空里面内容

dist Consul UI目录

consul.exe 注册软件

startup.bat 执行脚本

项目实现

一、基于Ocelot搭建Api网关

新建Snai.Ocelot解决方案

1、搭建Api网关

新建 Snai.ApiGateway 基于Asp.net Core 2.2空网站,在 依赖项 右击 管理NuGet程序包 浏览 找到 Ocelot 版本13.5.0安装

1.1、在项目根目录下新建一个 Ocelot.json 文件,打开 Ocelot.json 文件,配置Ocelot参数,Ocelot.json 代码如下

 1 {
 2   "ReRoutes": [
 3     {
 4       "UpstreamPathTemplate": "/apiservice/{controller}",
 5       "UpstreamHttpMethod": [ "Get" ],
 6       "DownstreamPathTemplate": "/apiservice/{controller}",
 7       "DownstreamScheme": "http",
 8       "DownstreamHostAndPorts": [
 9         {
10           "host": "localhost",
11           "port": 5011
12         },
13         {
14           "host": "localhost",
15           "port": 5012
16         }
17       ],
18       "LoadBalancerOptions": {
19         "Type": "LeastConnection"
20       }
21     }
22   ],
23 
24   "GlobalConfiguration": {
25     "BaseUrl": "http://localhost:5000"
26   }
27 }

 如果有多个下游服务,把ReRoutes下 {...} 复制多份,最终如: "ReRoutes":[{...},{...}]

Ocelot参数说明如下,详情查看官网(http://ocelot.readthedocs.io)

ReRoutes 路由配置 

UpstreamPathTemplate 请求路径模板
UpstreamHttpMethod 请求方法数组
DownstreamPathTemplate 下游请求地址模板
DownstreamScheme 请求协议,目前应该是支持http和https
DownstreamHostAndPorts 下游地址和端口
LoadBalancerOptions 负载均衡 RoundRobin(轮询)/LeastConnection(最少连接数)/CookieStickySessions(相同的Sessions或Cookie发往同一个地址)/NoLoadBalancer(不使用负载)

DownstreamHostAndPorts配了两个localhost 5011和localhost 5012用于负载均衡,负载均衡已经可以了,但没有健康检查,当其中一个挂了,负载可能还是会访问这样就会报错,所以我们要加入Consul,我们稍后再讲。

请求聚合,认证,限流,熔错告警等查看官方配置说明

GlobalConfiguration 全局配置
BaseUrl 告诉别人网关对外暴露的域名

1.2、修改 Program.cs 代码,读取Ocelot.json配置,修改网关地址为 http://localhost:5000

代码如下:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.IO;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 using Microsoft.AspNetCore;
 7 using Microsoft.AspNetCore.Hosting;
 8 using Microsoft.Extensions.Configuration;
 9 using Microsoft.Extensions.Logging;
10 
11 namespace Snai.ApiGateway
12 {
13     public class Program
14     {
15         public static void Main(string[] args)
16         {
17             CreateWebHostBuilder(args).Build().Run();
18         }
19 
20         public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
21             WebHost.CreateDefaultBuilder(args)
22             .ConfigureAppConfiguration((context, builder) => 
23             {
24                 builder.SetBasePath(context.HostingEnvironment.ContentRootPath);
25                 builder.AddJsonFile("Ocelot.json");
26             })
27             .UseUrls("http://localhost:5000")
28             .UseStartup<Startup>();
29     }
30 }

1.3、修改Startup.cs代码,注入Ocelot到容器,并使用Ocelot

代码如下:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using Microsoft.AspNetCore.Builder;
 6 using Microsoft.AspNetCore.Hosting;
 7 using Microsoft.AspNetCore.HttpsPolicy;
 8 using Microsoft.AspNetCore.Mvc;
 9 using Microsoft.Extensions.Configuration;
10 using Microsoft.Extensions.DependencyInjection;
11 using Microsoft.Extensions.Logging;
12 using Microsoft.Extensions.Options;
13 using Ocelot.DependencyInjection;
14 using Ocelot.Middleware;
15 
16 namespace Snai.ApiGateway
17 {
18     public class Startup
19     {
20         public Startup(IConfiguration configuration)
21         {
22             Configuration = configuration;
23         }
24 
25         public IConfiguration Configuration { get; }
26 
27         // This method gets called by the runtime. Use this method to add services to the container.
28         public void ConfigureServices(IServiceCollection services)
29         {
30             services.AddOcelot();
31         }
32 
33         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
34         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
35         {
36             if (env.IsDevelopment())
37             {
38                 app.UseDeveloperExceptionPage();
39             }
40             app.UseOcelot().Wait();
41         }
42     }
43 }

 最终项目结构如下:

2、搭建服务Snai.ApiServiceA,Snai.ApiServiceB

新建 Snai.ApiServiceA 基于Asp.net Core 3.0 Api网站

2.1、修改Controllers/ValuesController.cs代码

修改路由为Ocelot 配置的下游地址 apiservice/[controller],注入appsettings.json配置实体,修改Get方法为返回读取配置内容,其他方法可以删除

 

代码如下:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using Microsoft.AspNetCore.Mvc;
 6 using Microsoft.Extensions.Configuration;
 7 
 8 namespace Snai.ApiServiceA.Controllers
 9 {
10     [Route("apiservice/[controller]")]
11     [ApiController]
12     public class ValuesController : ControllerBase
13     {
14         public  IConfiguration _configuration { get; }
15         public ValuesController(IConfiguration configuration)
16         {
17             this._configuration = configuration;
18         }
19 
20         [HttpGet]
21         public string Get()
22         {
23             return HttpContext.Request.Host.Port + " " + _configuration["AppName"] + " " + DateTime.Now.ToString(); 
24         }
25     }
26 }

 2.2、修改appsettings.json配置,加入 "AppName": "ServiceA"

 1 {
 2   "Logging": {
 3     "LogLevel": {
 4       "Default": "Information",
 5       "Microsoft": "Warning",
 6       "Microsoft.Hosting.Lifetime": "Information"
 7     }
 8   },
 9   "AllowedHosts": "*",
10   "AppName": "ServiceA"
11 }

2.3、修改Program.cs代码,修改该服务地址为 http://localhost:5011

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using Microsoft.AspNetCore.Hosting;
 6 using Microsoft.Extensions.Configuration;
 7 using Microsoft.Extensions.Hosting;
 8 using Microsoft.Extensions.Logging;
 9 
10 namespace Snai.ApiServiceA
11 {
12     public class Program
13     {
14         public static void Main(string[] args)
15         {
16             CreateHostBuilder(args).Build().Run();
17         }
18 
19         public static IHostBuilder CreateHostBuilder(string[] args) =>
20             Host.CreateDefaultBuilder(args)
21                 .ConfigureWebHostDefaults(webBuilder =>
22                 {
23                     webBuilder.UseUrls("http://localhost:5011");
24                     webBuilder.UseStartup<Startup>();
25                 });
26     }
27 }

 2.4、新建 Snai.ApiServiceB 基于Asp.net Core 2.0 Api网站,几乎与Snai.ApiServiceA一样,除了 "AppName": "ServiceB",.UseUrls("http://localhost:5012")

到此 基于Ocelot Api网关 搭建完成

3、启动 运行 Snai.ApiServiceA,Snai.ApiServiceB,Snai.ApiGateway项目,在浏览器打开 http://localhost:5000/apiservice/values 地址

 刷新页面负载得到ServiceA,ServiceB返回内容。

5012没有返回任何数据,Ocelot已内置负载均衡,但没有健康检查,不能踢除坏掉的服务,所以加入Consul,Consul提供服务注册发现、健康检查,配合Ocelot负载就能发现坏掉的服务,只负载到正常的服务上,下面介绍加入Consul。

***作者的5012是返回数据的,而我的没有,这里说一下原因,我在Program.cs设置了启动端口,在调试的时候并没有生效,于是我在launchSettings.json中配置了applicationUrl属性并将sslPort属性设置为0关闭SSL,5012的数据就能正常返回了。

二、在Ocelot网关加入Consul,实现服务注册发现、健康检查

1、启动Consul,开启服务注册、服务发现

首先下载Consul:https://www.consul.io/downloads.html,本项目是Windows下进行测试,得到consul.exe(我下载的压缩包里面只有一个可执行文件。

再下载Consul配置文件和Consul UI(配置文件适合本例Demo的,可根据具体项目修改调整):https://github.com/Liu-Alan/Ocelot-Consul/tree/master/Consul

conf:配置文件目录

data:缓存数据目录,可清空里面内容

dist:Consul UI,用于浏览器查看注册的服务情况;如果用Consul默认自带UI,该目录可以删除,Consul 启动脚本 -ui-dir ./dist 改为 -ui 

Consul支持配置文件和Api两种方式服务注册、服务发现,下面主要讲解配置文件方式

在consul.exe同级目录下新建conf,并创建以下json文件放入其中。

Consul 配置文件service.json配置如下:

 1 {
 2   "encrypt": "7TnJPB4lKtjEcCWWjN6jSA==",
 3   "services": [
 4     {
 5       "id": "ApiServiceA",
 6       "name": "ApiService",
 7       "tags": [ "ApiServiceA" ],
 8       "address": "localhost",
 9       "port": 5011,
10       "checks": [
11         {
12           "id": "ApiServiceA_Check",
13           "name": "ApiServiceA_Check",
14           "http": "http://localhost:5011/health",
15           "interval": "10s",
16           "tls_skip_verify": false,
17           "method": "GET",
18           "timeout": "1s"
19         }
20       ]
21     },
22     {
23       "id": "ApiServiceB",
24       "name": "ApiService",
25       "tags": [ "ApiServiceB" ],
26       "address": "localhost",
27       "port": 5012,
28       "checks": [
29         {
30           "id": "ApiServiceB_Check",
31           "name": "ApiServiceB_Check",
32           "http": "http://localhost:5012/health",
33           "interval": "10s",
34           "tls_skip_verify": false,
35           "method": "GET",
36           "timeout": "1s"
37         }
38       ]
39     }
40   ]
41 }

两个服务ApiServiceA和ApiServiceB,跟着两个健康检查ApiServiceA_Check和ApiServiceB_Check

由于ApiServiceA和ApiServiceB做负载均衡,现在 "name": "ApiService" 配置一样

修改Snai.ApiServiceA、Snai.ApiServiceB项目 加入health 健康检查地址

打开ValuesController.cs 加入 health

代码如下:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using Microsoft.AspNetCore.Mvc;
 6 using Microsoft.Extensions.Configuration;
 7 
 8 namespace Snai.ApiServiceB.Controllers
 9 {
10     [Route("apiservice/[controller]")]
11     [ApiController]
12     public class ValuesController : ControllerBase
13     {
14         public IConfiguration _configuration { get; }
15         public ValuesController(IConfiguration configuration)
16         {
17             this._configuration = configuration;
18         }
19         [HttpGet]
20         public string Get()
21         {
22             return HttpContext.Request.Host.Port + " " + _configuration["AppName"] + " " + DateTime.Now.ToString();
23         }
24 
25         [HttpGet("health")]
26         public async Task<IActionResult> Heathle()
27         {
28             return await Task.FromResult(Ok());
29         }
30     }
31 }

重新生成运行项目Snai.ApiServiceA、Snai.ApiServiceB

清除Consul/data 内容,新建startup.bat文件,输入下面代码,双击启动Consul,本项目测试时一台机器,所以把 本机IP 改成 127.0.0.1

consul agent -server -datacenter=dc1 -bootstrap -data-dir ./data -config-file ./conf -ui-dir ./dist -node=n1 -bind 本机IP -client=0.0.0.0
consul agent -server -datacenter=dc1 -bootstrap -data-dir ./data -config-file ./conf -ui -node=n1 -bind 本机IP -client=0.0.0.0

再在Consul目录下启动另一个cmd命令行窗口,输入命令:consul operator raft list-peers 查看状态查看状态,结果如下

打开Consul UI:http://localhost:8500 查看服务情况,可以看到ApiServiceA、ApiServiceB 服务,且健康检查都是正常的。

由于ApiServiceA、ApiServiceB是在一台机器上两个服务做负载 所以在一个Consul里配置了两个name一样的服务。

如果用两个机器做ApiServiceA负载,本机IP是192.168.0.5,另一台IP是192.168.0.6上,以本机上主Consul

 本机【192.168.0.5】 Consul配置如下

 1 {
 2   "encrypt": "7TnJPB4lKtjEcCWWjN6jSA==",
 3   "services": [
 4     {
 5       "id": "ApiServiceA",
 6       "name": "ApiService",
 7       "tags": [ "ApiServiceA" ],
 8       "address": "192.168.0.5",
 9       "port": 5011,
10       "checks": [
11         {
12           "id": "ApiServiceA_Check",
13           "name": "ApiServiceA_Check",
14           "http": "http://192.168.0.5:5011/health",
15           "interval": "10s",
16           "tls_skip_verify": false,
17           "method": "GET",
18           "timeout": "1s"
19         }
20       ]
21     }
22   ]
23 }

把ApiServiceA和Consul拷到另一个【192.168.0.6】机器,修改Consul配置文件

 1 {
 2   "encrypt": "7TnJPB4lKtjEcCWWjN6jSA==",
 3   "services": [
 4     {
 5       "id": "ApiServiceA",
 6       "name": "ApiService",
 7       "tags": [ "ApiServiceA" ],
 8       "address": "192.168.0.6",
 9       "port": 5011,
10       "checks": [
11         {
12           "id": "ApiServiceA_Check",
13           "name": "ApiServiceA_Check",
14           "http": "http://192.168.0.6:5011/health",
15           "interval": "10s",
16           "tls_skip_verify": false,
17           "method": "GET",
18           "timeout": "1s"
19         }
20       ]
21     }
22   ]
23 }

修改启动Consul脚本的IP为192.168.0.6,-node=n2,去掉 -bootstrap,启动Consul,在Consul UI下查看服务是否正常

在192.168.0.5下,把192.168.0.6加到集群中,命令如下

consul join 192.168.0.6

注意,consul集群中,consul配置文件中的encrypt,一定要相同,否则无法放加入同一个集群

用consul operator raft list-peers查看状态,会发现n1,n2在一个集群中了

Node  ID                                    Address             State     Voter  RaftProtocol

n1    d02c3cd0-d9c8-705b-283e-121a9105cf52  192.168.0.5:8300   leader    true   3

n2    efe954ce-9840-5c66-fa80-b9022167d782  192.168.0.6:8300  follower  true   3

2、配置Ocelot,加入Consul,启用服务健康检查,负载均衡

  2.1 打开 Snai.ApiGateway 网关下的Ocelot.json文件,加入下面配置

完整配置信息如下:

 1 {
 2   "ReRoutes": [
 3     {
 4       "UpstreamPathTemplate": "/apiservice/{controller}",
 5       "UpstreamHttpMethod": [ "Get" ],
 6       "DownstreamPathTemplate": "/apiservice/{controller}",
 7       "DownstreamScheme": "http",
 8       "DownstreamHostAndPorts": [
 9         {
10           "host": "localhost",
11           "port": 5011
12         },
13         {
14           "host": "localhost",
15           "port": 5012
16         }
17       ],
18       "LoadBalancerOptions": {
19         "Type": "LeastConnection"
20       },
21       "ServiceName": "ApiService",
22       "UseServiceDiscovery": true
23     }
24   ],
25 
26   "GlobalConfiguration": {
27     "BaseUrl": "http://localhost:5000",
28     "ServiceDiscoveryProvider": {
29       "Host": "localhost",
30       "Port": 8500,
31       "Type": "Consul"
32     }
33   }
34 }

ServiceName 是Cousul配置中服务的name名字

UseServiceDiscovery 是否启用Consul服务发现

ServiceDiscoveryProvider 是Consul服务发现的地址和端口

   2.2修改Startup.cs添加Consul

完整代码如下:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using Microsoft.AspNetCore.Builder;
 6 using Microsoft.AspNetCore.Hosting;
 7 using Microsoft.AspNetCore.HttpsPolicy;
 8 using Microsoft.AspNetCore.Mvc;
 9 using Microsoft.Extensions.Configuration;
10 using Microsoft.Extensions.DependencyInjection;
11 using Microsoft.Extensions.Logging;
12 using Microsoft.Extensions.Options;
13 using Ocelot.DependencyInjection;
14 using Ocelot.Middleware;
15 using Ocelot.Provider.Consul;
16 
17 namespace Snai.ApiGateway
18 {
19     public class Startup
20     {
21         public Startup(IConfiguration configuration)
22         {
23             Configuration = configuration;
24         }
25 
26         public IConfiguration Configuration { get; }
27 
28         // This method gets called by the runtime. Use this method to add services to the container.
29         public void ConfigureServices(IServiceCollection services)
30         {
31             services.AddOcelot().AddConsul();
32         }
33 
34         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
35         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
36         {
37             if (env.IsDevelopment())
38             {
39                 app.UseDeveloperExceptionPage();
40             }
41             app.UseOcelot().Wait();
42         }
43     }
44 }

重新生成启动Ocelot网关,到此Ocelot+Consul配置完成

三、运行测试Ocelot+Consul服务发现、负载均衡

打开 http://localhost:5000/apiservice/values 地址,刷新页面负载得到ServiceA,ServiceB返回内容

当把ApiServiceB服务关掉,再多次刷新页面,只能得到ServiceA的内容

打开Consul UI去看,ServiceB健康检查失败

Ocolot+Consul实现API网关 服务注册、服务发现、健康检查和负载均衡已完成

原作者源码访问地址:https://github.com/Liu-Alan/Ocelot-Consul

原文地址:https://www.cnblogs.com/fanqisoft/p/10870712.html