• .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:workdemocoreSummerCore3.1Summer.WebApiinDebug etcoreapp3.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:workdemocoreSummerCore3.1Summer.ApiGatewayinDebug etcoreapp3.1> dotnet .Summer.ApiGateway.dll --urls "htt
    p://localhost:7000"

    访问http://localhost:7000/Summer/user/userlist

     可以看到,API网关已经正常映射到localhost:58420的Webapi

    其他效果,例如负载均衡,限流熔断可以自行结合配置参数做测试

     
  • 相关阅读:
    css3实现酷炫的3D盒子翻转效果
    Java源码学习:HashMap实现原理
    mac 如何显示隐藏文件和.点开头文件?
    mac 下 安装 mongodb 数据库
    WPF 自定义TextBox
    Appium+Python 安卓APP自动化测试:安装app和卸载app
    Appium+Python 安卓APP自动化测试:环境搭建与基础操作
    DataFrame利用函数或映射进行数据转换map
    TypeError: drop_duplicates() got an unexpected keyword argument 'take_last'
    DataFrame合并:合并重叠数据combine_first
  • 原文地址:https://www.cnblogs.com/netlock/p/14101491.html
Copyright © 2020-2023  润新知