zoukankan      html  css  js  c++  java
  • .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

  • 相关阅读:
    SQL 连接 JOIN 例解。(左连接,右连接,全连接,内连接,交叉连接,自连接)
    HttpWatch工具简介及使用技巧
    橙色在网页设计运用:36个启发灵感的案例
    JS Date格式化为yyyyMMdd类字符串
    60款很酷的 jQuery 幻灯片演示和下载
    浅谈SQL Server中统计对于查询的影响
    C#创建Windows Service(Windows 服务)基础教程
    使用分页方式读取超大文件的性能试验
    240多个jQuey插件
    ASP.NET性能优化之负载均衡
  • 原文地址:https://www.cnblogs.com/fanqisoft/p/10870712.html
Copyright © 2011-2022 走看看