.NET5 API 網關Ocelot+Consul服務注冊


1|0網關介紹

網關其實就是將我們寫好的API全部放在一個統一的地址暴露在公網,提供訪問的一個入口。在 .NET Core下可以使用Ocelot來幫助我們很方便的接入API 網關。與之類似的庫還有ProxyKit,微軟也發布了一個反向代理的庫YARP

關於網關的介紹不多說了,網上文章也挺多的,這些都是不錯的選擇,聽說后期Ocelot將會使用YARP來重寫。本篇主要實踐一下在.NET Core環境下使用Ocelot

2|0接入使用

2|1接口示例

先創建幾個項目用於測試,創建兩個默認的API項目,Api_A和Api_B,在創建一個網關項目Api_Gateway,網關項目可以選擇空的模板。

現在分別在Api_A和Api_B中寫幾個api,將默認的WeatherForecastController中返回模型WeatherForecast添加一個字段Source,用於區分是哪個API返回的數據。

using System;

namespace Api_A
{
    public class WeatherForecast
    {
        public string Source { get; set; } = "Api_A";

        public DateTime Date { get; set; }

        public int TemperatureC { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

        public string Summary { get; set; }
    }
}

using System;

namespace Api_B
{
    public class WeatherForecast
    {
        public string Source { get; set; } = "Api_B";

        public DateTime Date { get; set; }

        public int TemperatureC { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

        public string Summary { get; set; }
    }
}

直接使用WeatherForecastController默認方法,在路由中添加api前綴。

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Api_A.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            }).ToArray();
        }
    }
}

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Api_B.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            }).ToArray();
        }
    }
}

再分別在Api_A和Api_B中添加兩個控制器:ApiAController、ApiBController,然后加上幾個簡單的restful api。

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace Api_A.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ApiAController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        [HttpGet("{id}")]
        public string Get(int id)
        {
            return $"Get:{id}";
        }

        [HttpPost]
        public string Post([FromForm] string value)
        {
            return $"Post:{value}";
        }

        [HttpPut("{id}")]
        public string Put(int id, [FromForm] string value)
        {
            return $"Put:{id}:{value}";
        }

        [HttpDelete("{id}")]
        public string Delete(int id)
        {
            return $"Delete:{id}";
        }
    }
}
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace Api_B.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ApiBController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        [HttpGet("{id}")]
        public string Get(int id)
        {
            return $"Get:{id}";
        }

        [HttpPost]
        public string Post([FromForm] string value)
        {
            return $"Post:{value}";
        }

        [HttpPut("{id}")]
        public string Put(int id, [FromForm] string value)
        {
            return $"Put:{id}:{value}";
        }

        [HttpDelete("{id}")]
        public string Delete(int id)
        {
            return $"Delete:{id}";
        }
    }
}

方便查看接口,這里添加一下swagger組件,這樣我們Api_A和Api_B項目分別就有了6個接口。

接着打包docker鏡像,放在docker中運行這兩個api項目。這一步可以用任何你熟悉的方式,run起來即可。

docker build -t api_a:dev -f ./Api_A/Dockerfile . docker build -t api_b:dev -f ./Api_B/Dockerfile .

build成功后,指定兩個端口運行api項目。

docker run -d -p 5050: 80 - -name api_a api_a:dev docker run -d -p 5051: 80 - -name api_b api_b:dev

Api_A指定了5050端口,通過 http://localhost:5050/swagger打開可以看到swagger文檔界面,Api_B指定了5051端口,通過 http://localhost:5051/swagger打開可以看到swagger文檔界面,這樣就大功告成了,接下來才是重點將兩個api項目配置到Api_Gateway網關項目中。

2|2配置網關

在網關項目Api_Gateway中都添加Ocelot組件包。

Install-Package Ocelot

Ocelot中最關鍵的就是配置路由信息,新建一個ocelot.json配置文件,將我們的兩個API接口匹配規則放進去。

{
  "Routes": [
    //ApiA
    {
      "DownstreamPathTemplate": "/api/WeatherForecast",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5050
        }
      ],
      "UpstreamPathTemplate": "/ApiA/WeatherForecast",
      "UpstreamHttpMethod": [ "Get" ]
    },
    {
      "DownstreamPathTemplate": "/api/ApiA",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5050
        }
      ],
      "UpstreamPathTemplate": "/ApiA",
      "UpstreamHttpMethod": [ "Get", "POST" ]
    },
    {
      "DownstreamPathTemplate": "/api/ApiA/{id}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5050
        }
      ],
      "UpstreamPathTemplate": "/ApiA/{id}",
      "UpstreamHttpMethod": [ "Get", "Put", "Delete" ]
    },
    //ApiB
    {
      "DownstreamPathTemplate": "/api/WeatherForecast",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5051
        }
      ],
      "UpstreamPathTemplate": "/ApiB/WeatherForecast",
      "UpstreamHttpMethod": [ "Get" ]
    },
    {
      "DownstreamPathTemplate": "/api/ApiB",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5051
        }
      ],
      "UpstreamPathTemplate": "/ApiB",
      "UpstreamHttpMethod": [ "Get", "POST" ]
    },
    {
      "DownstreamPathTemplate": "/api/ApiB/{id}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5051
        }
      ],
      "UpstreamPathTemplate": "/ApiB/{id}",
      "UpstreamHttpMethod": [ "Get", "Put", "Delete" ]
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "https://localhost:44335"
  }
}

關於配置文件中的各項具體含義,可以參考官方文檔中的介紹。主要就是將DownstreamPathTemplate模板內容轉換為UpstreamPathTemplate模板內容進行接口的訪問,同時可以指定HTTP請求的方式等等。GlobalConfiguration中的BaseUrl為我們暴漏出去的網關地址。

設置好ocelot.json后,需要在代碼中使用它,在Program.cs中添加配置文件。

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace Api_Gateway
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((context, config) =>
                {
                    config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Startup.cs中使用Ocelot

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;

namespace Api_Gateway
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOcelot();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });

            app.UseOcelot().Wait();
        }
    }
}

完成以上操作后,我們試着去調用接口看看能否正確獲取預期數據。

curl -X GET "https://localhost:44335/ApiA"
curl -X GET "https://localhost:44335/ApiB"

curl -X POST "https://localhost:44335/ApiA" -H "Content-Type: multipart/form-data" -F "value=ApiA"
curl -X POST "https://localhost:44335/ApiB" -H "Content-Type: multipart/form-data" -F "value=ApiB"

curl -X GET "https://localhost:44335/ApiA/12345"
curl -X GET "https://localhost:44335/ApiB/12345"

curl -X PUT "https://localhost:44335/ApiA/12345" -H "Content-Type: multipart/form-data" -F "value=ApiA"
curl -X PUT "https://localhost:44335/ApiB/12345" -H "Content-Type: multipart/form-data" -F "value=ApiB"

curl -X DELETE "https://localhost:44335/ApiA/12345"
curl -X DELETE "https://localhost:44335/ApiB/12345"

curl -X GET "https://localhost:44335/ApiA/WeatherForecast"
curl -X GET "https://localhost:44335/ApiB/WeatherForecast"

可以看到,兩個項目中的接口全部可以通過網關項目暴露的地址進行中轉,是不是很方便?

本篇只是簡單的應用,對於Ocelot的功能遠不止於此,它非常強大,還可以實現請求聚合、服務發現、認證、鑒權、限流熔斷、並內置了負載均衡器,而且這些功能都是只需要簡單的配置即可完成。就不一一描述了,如有實際開發需求和問題,可以查看官方文檔和示例。

 Consul服務注冊

Consul是一個用來實現分布式系統服務發現與配置的開源工具。它內置了服務注冊與發現框架、分布一致性協議實現、健康檢查、Key/Value存儲、多數據中心方案,不再需要依賴其他工具,使用起來也較為簡單。

Consul官網:https://www.consul.io
開源地址:https://github.com/hashicorp/consul、https://github.com/G-Research/consuldotnet
Consul支持各種平台的安裝,安裝文檔:https://www.consul.io/downloads

 Consul+Ocelot

Ocelot是一個用.NET Core實現的開源API網關,它功能強大,包括了:路由、請求聚合、服務發現、認證、鑒權、限流熔斷,緩存等。

接下來,會使用ocelot 結合consul ,polly ,cachemanager 實現負載均衡,服務發現,限流熔斷和緩存功能。(本文只做記錄,就直接貼完整代碼,不分別測試了)

新建ApiGateWay項目,nuget安裝Ocelot,Ocelot.Provider.Consul,Ocelot.Provider.Polly,Ocelot.Cache.CacheManager

新增ocelot.json,配置相關參數

復制代碼
{
  // 轉發路由,數組中的每個元素都是某個服務的一組路由轉發規則
  "ReRoutes": [
    {
      "ServiceName": "Summer.Webapi", //對應consul配置的ServiceName
      // Uri方案,http、https
      "DownstreamScheme": "http",
      // 下游(服務提供方)服務路由模板
      "DownstreamPathTemplate": "/api/{path}",
      // 上游(客戶端,服務消費方)請求路由模板
      "UpstreamPathTemplate": "/Summer/{path}",
      "UpstreamHttpMethod": [ "Get", "Post" ],
      "LoadBalancerOptions": {
        "Type": "RoundRobin" //輪詢     
      },
      "UseServiceDiscovery": true,
      "RateLimitOptions": {
        "ClientWhitelist": [ "admin" ], // 白名單
        "EnableRateLimiting": true, // 是否啟用限流
        "Period": "1m", // 統計時間段: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": ""
      }
    }
  ],
  // 全局配置,此節點的配置會覆蓋ReRoutes,可以在這里設置一些通用的配置
  "GlobalConfiguration": {
    "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
    }
  }
}
復制代碼

在Startup.cs 中新增如下代碼:

復制代碼
public void ConfigureServices(IServiceCollection services)
        {
            var config = new ConfigurationBuilder().AddJsonFile("ocelot.json").Build();
            services.AddOcelot(config)
                .AddCacheManager(x =>
                {
                    x.WithDictionaryHandle();
                })
                .AddConsul().AddPolly();
        }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseOcelot().Wait();
        }
復制代碼

配置完ApiGateway,接下來配置Webapi,每個Webapi都新增服務注冊,通過consul服務發現並定時做健康檢測

新增consul服務注冊擴展類庫 ConsulBuilder,方便各個項目引用,接下來直接貼代碼:

復制代碼
// consul服務注冊擴展類
    public static class ConsulRegistrationExtensions
    {
        public static void AddConsul(this IServiceCollection service)
        {
            // 讀取服務配置文件
            var config = new ConfigurationBuilder().AddJsonFile("consulconfig.json").Build();
            service.Configure<ConsulServiceOptions>(config);
        }

        public static IApplicationBuilder UseConsul(this IApplicationBuilder app)
        {
            // 獲取主機生命周期管理接口
            var lifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();

            // 獲取服務配置項
            var serviceOptions = app.ApplicationServices.GetRequiredService<IOptions<ConsulServiceOptions>>().Value;

            // 服務ID必須保證唯一
            serviceOptions.ServiceId = Guid.NewGuid().ToString();

            var consulClient = new ConsulClient(configuration =>
            {
                //服務注冊的地址,集群中任意一個地址
                configuration.Address = new Uri(serviceOptions.ConsulAddress);
            });

            // 獲取當前服務地址和端口,配置方式
            var uri = new Uri(serviceOptions.ServiceAddress);

            // 節點服務注冊對象
            var registration = new AgentServiceRegistration()
            {
                ID = serviceOptions.ServiceId,
                Name = serviceOptions.ServiceName,// 服務名
                Address = uri.Host,
                Port = uri.Port, // 服務端口
                Check = new AgentServiceCheck
                {
                    // 注冊超時
                    Timeout = TimeSpan.FromSeconds(5),
                    // 服務停止多久后注銷服務
                    DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),
                    // 健康檢查地址
                    HTTP = $"{uri.Scheme}://{uri.Host}:{uri.Port}{serviceOptions.HealthCheck}",
                    // 健康檢查時間間隔
                    Interval = TimeSpan.FromSeconds(10),
                }
            };

            // 注冊服務
            consulClient.Agent.ServiceRegister(registration).Wait();

            // 應用程序終止時,注銷服務
            lifetime.ApplicationStopping.Register(() =>
            {
                consulClient.Agent.ServiceDeregister(serviceOptions.ServiceId).Wait();
            });

            return app;
        }
    }
復制代碼
復制代碼
// Consul配置模型類
    public class ConsulServiceOptions
    {
        // 服務注冊地址(Consul的地址)
        public string ConsulAddress { get; set; }

        // 服務ID
        public string ServiceId { get; set; }

        // 服務名稱
        public string ServiceName { get; set; }

        // 健康檢查地址
        public string HealthCheck { get; set; }

        //站點地址
        public string ServiceAddress { get; set; }
    }
復制代碼

在Webapi 項目中引用ConsulBuilder類庫,然后在Startup.cs 中新增如下代碼:

 

 

 

 新增consulconfig.json ,屬性設置為始終復制

{
  "ConsulAddress": "http://127.0.0.1:8500", //consul 服務器地址
  "ServiceName": "Summer.Webapi", //服務名稱,ApiGateway中配置的ServiceName對應這里
  "HealthCheck": "/Health", //健康檢查地址
  "ServiceAddress": "http://localhost:58420"  //webapi 地址
}

假如要啟動多個webapi,拷貝代碼,將ServiceAddress 改為不同的端口,然后使用命令行啟動

PS D:\work\demo\core\SummerCore3.1\Summer.WebApi\bin\Debug\netcoreapp3.1> dotnet .\Summer.Webapi.dll --urls "http://loca
lhost:58420"

配置好后,開始運行,

先開啟consul:  consul.exe agent -dev

然后啟動多個webapi 

訪問localhost:8500 ,可以看到已經服務發現兩個webapi

 

測試一下是否正常運行,訪問http://localhost:58420/api/user/userlist

 運行ApiGateway 

PS D:\work\demo\core\SummerCore3.1\Summer.ApiGateway\bin\Debug\netcoreapp3.1> dotnet .\Summer.ApiGateway.dll --urls "htt
p://localhost:7000"

訪問http://localhost:7000/Summer/user/userlist

 可以看到,API網關已經正常映射到localhost:58420的Webapi

其他效果,例如負載均衡,限流熔斷可以自行結合配置參數做測試

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM