MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C#中的API网关与Ocelot框架应用

2022-12-112.4k 阅读

1. C# 与 API 网关概述

1.1 C# 编程语言基础

C# 是一种简洁、类型安全的面向对象编程语言,由微软开发,作为 .NET 平台的一部分。它融合了 C 和 C++ 的强大功能与 Visual Basic 的易用性,被广泛应用于各种软件开发场景,从桌面应用、Web 应用到游戏开发等。C# 具有以下特点:

  • 强类型检查:在编译时,C# 严格检查变量类型,有助于发现早期错误,提高代码的稳定性和可维护性。例如,声明一个整数变量 int num = 10;,如果试图将一个字符串赋值给它 num = "abc";,编译器会立即报错。
  • 自动内存管理:通过垃圾回收(Garbage Collection, GC)机制,C# 自动管理内存的分配和释放,开发人员无需手动释放内存,减少了内存泄漏的风险。例如,当一个对象不再被引用时,垃圾回收器会在适当的时候回收该对象占用的内存。
  • 面向对象特性:C# 支持封装、继承和多态等面向对象编程概念。例如,通过封装可以将数据和操作数据的方法包装在一起,提高代码的安全性和可维护性。

1.2 API 网关的概念与作用

API 网关是一种微服务架构中的关键组件,它充当了外部客户端与内部微服务之间的中间层。其主要作用包括:

  • 统一入口:为所有微服务提供一个单一的入口点,简化了客户端的调用逻辑。客户端无需了解每个微服务的具体地址和端口,只需与 API 网关进行通信。例如,在一个包含用户服务、订单服务等多个微服务的系统中,客户端只需要与 API 网关交互,由 API 网关负责将请求转发到相应的微服务。
  • 请求路由:根据请求的 URL、HTTP 方法等信息,将请求准确地路由到对应的微服务。例如,对于 http://api.gateway.com/user/get 请求,API 网关可以将其路由到用户服务的 Get 方法。
  • 请求过滤与验证:在请求到达微服务之前,对请求进行过滤和验证,如检查请求头中的身份验证信息、参数的合法性等。例如,验证请求头中的 Authorization 字段是否有效,防止非法请求访问微服务。
  • 服务聚合:可以将多个微服务的响应进行聚合,返回给客户端一个完整的响应。例如,一个订单详情页面可能需要从订单服务获取订单基本信息,从商品服务获取商品详情信息,API 网关可以将这两个微服务的响应合并后返回给客户端。

2. Ocelot 框架简介

2.1 Ocelot 框架的特点与优势

Ocelot 是一个基于 .NET 平台的开源 API 网关框架,它具有以下特点和优势:

  • 功能丰富:支持请求路由、负载均衡、身份验证、缓存等多种功能,能够满足复杂的微服务架构需求。例如,通过配置可以轻松实现对后端微服务的负载均衡,提高系统的可用性和性能。
  • 易于配置:使用 JSON 格式的配置文件进行配置,简单直观,开发人员可以快速上手。例如,以下是一个简单的 Ocelot 路由配置示例:
{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/user/{id}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5001
                }
            ],
            "UpstreamPathTemplate": "/user/{id}",
            "UpstreamHttpMethod": [ "Get" ]
        }
    ]
}
  • 可扩展性:可以通过插件机制进行扩展,满足不同项目的特定需求。例如,可以开发自定义的中间件来实现特殊的请求处理逻辑。

2.2 Ocelot 框架的架构组成

Ocelot 框架主要由以下几个部分组成:

  • 配置模块:负责读取和解析 JSON 格式的配置文件,将配置信息提供给其他模块使用。
  • 路由模块:根据配置信息进行请求路由,确定请求应该转发到哪个下游微服务。
  • 负载均衡模块:如果配置了多个下游微服务实例,负载均衡模块负责选择一个合适的实例来处理请求,常见的负载均衡算法如轮询、随机等。
  • 中间件模块:Ocelot 提供了一系列中间件,用于处理请求和响应,如身份验证中间件、日志记录中间件等。开发人员也可以自定义中间件来满足特定需求。

3. 在 C# 项目中集成 Ocelot 框架

3.1 创建 .NET 项目

首先,我们需要创建一个 .NET 项目来集成 Ocelot 框架。可以使用 Visual Studio 或者 .NET CLI 来创建项目。

  • 使用 Visual Studio:打开 Visual Studio,选择“创建新项目”,在模板中选择“ASP.NET Core Web 应用程序”,命名项目并点击“创建”。在创建项目的对话框中,选择“API”模板,然后点击“创建”。
  • 使用 .NET CLI:打开命令行工具,执行以下命令创建一个新的 ASP.NET Core Web 应用项目:
dotnet new webapi -n OcelotDemo
cd OcelotDemo

3.2 安装 Ocelot 相关 NuGet 包

在项目根目录下,执行以下命令安装 Ocelot NuGet 包:

dotnet add package Ocelot

这个命令会将 Ocelot 框架及其依赖项添加到项目中。

3.3 配置 Ocelot

在项目的根目录下创建一个 ocelot.json 文件,用于配置 Ocelot 的各项功能。以下是一个基本的配置示例:

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/{service}/{id}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5001
                },
                {
                    "Host": "localhost",
                    "Port": 5002
                }
            ],
            "UpstreamPathTemplate": "/{service}/{id}",
            "UpstreamHttpMethod": [ "Get" ]
        }
    ],
    "GlobalConfiguration": {
        "RequestIdKey": "OcRequestId",
        "ServiceDiscoveryProvider": {
            "Host": "localhost",
            "Port": 8500,
            "Type": "Consul"
        }
    }
}

在这个配置中:

  • Routes:定义了路由规则。DownstreamPathTemplate 表示下游微服务的路径模板,DownstreamScheme 表示协议,DownstreamHostAndPorts 列出了下游微服务的地址和端口,UpstreamPathTemplate 是上游客户端请求的路径模板,UpstreamHttpMethod 限制了允许的 HTTP 方法。
  • GlobalConfiguration:包含全局配置信息。RequestIdKey 用于设置请求 ID 的键名,ServiceDiscoveryProvider 配置了服务发现提供程序的相关信息,这里以 Consul 为例。

3.4 在项目中启用 Ocelot

打开项目的 Startup.cs 文件,在 ConfigureServices 方法中添加以下代码:

public void ConfigureServices(IServiceCollection services)
{
    services.AddOcelot();
}

Configure 方法中添加以下代码:

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

    await app.UseOcelot();
}

这样就完成了在项目中启用 Ocelot 的配置。

4. Ocelot 的请求路由功能

4.1 基本路由配置

如前面的配置示例所示,Ocelot 通过 Routes 节点来配置请求路由。基本的路由配置包括定义上游和下游路径模板、协议、下游微服务地址等信息。例如,下面的配置将客户端对 /user/{id}Get 请求路由到 http://localhost:5001/api/user/{id}

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/user/{id}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5001
                }
            ],
            "UpstreamPathTemplate": "/user/{id}",
            "UpstreamHttpMethod": [ "Get" ]
        }
    ]
}

4.2 多路由配置与优先级

当存在多个路由配置时,Ocelot 会按照配置文件中路由的顺序来匹配请求。可以通过合理安排路由顺序来设置优先级。例如,以下配置中有两个路由:

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/admin/{id}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5003
                }
            ],
            "UpstreamPathTemplate": "/admin/{id}",
            "UpstreamHttpMethod": [ "Get" ]
        },
        {
            "DownstreamPathTemplate": "/api/user/{id}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5001
                }
            ],
            "UpstreamPathTemplate": "/{service}/{id}",
            "UpstreamHttpMethod": [ "Get" ]
        }
    ]
}

在这个例子中,对于 /admin/123 的请求,会优先匹配第一个路由,因为它的路径模板更具体。而对于 /user/123 的请求,会匹配第二个路由。

4.3 动态路由配置

除了静态的路由配置,Ocelot 还支持动态路由。可以通过服务发现机制,如 Consul、Eureka 等,动态获取下游微服务的地址和端口信息。例如,结合 Consul 服务发现的配置如下:

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/{service}/{id}",
            "DownstreamScheme": "http",
            "ServiceName": "user-service",
            "UpstreamPathTemplate": "/{service}/{id}",
            "UpstreamHttpMethod": [ "Get" ]
        }
    ],
    "GlobalConfiguration": {
        "ServiceDiscoveryProvider": {
            "Host": "localhost",
            "Port": 8500,
            "Type": "Consul"
        }
    }
}

在这个配置中,ServiceName 指定了要发现的微服务名称,Ocelot 会从 Consul 中获取该微服务的实例地址,并动态更新路由配置。

5. Ocelot 的负载均衡功能

5.1 内置负载均衡算法

Ocelot 内置了多种负载均衡算法,包括:

  • 轮询(Round Robin):依次将请求分配到每个下游微服务实例。例如,假设有三个微服务实例 A、B、C,请求 1 会被分配到 A,请求 2 分配到 B,请求 3 分配到 C,请求 4 又回到 A,以此类推。
  • 随机(Random):随机选择一个下游微服务实例来处理请求。这种算法在某些情况下可以更均匀地分散请求,但可能会导致某些实例处理请求的频率较高或较低。
  • 最少连接(Least Connections):优先将请求分配给当前连接数最少的下游微服务实例。这有助于确保每个实例的负载相对均衡,适用于处理长连接请求的场景。

5.2 配置负载均衡

ocelot.json 文件中,可以通过 LoadBalancerOptions 节点来配置负载均衡算法。例如,配置使用轮询算法:

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/user/{id}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5001
                },
                {
                    "Host": "localhost",
                    "Port": 5002
                }
            ],
            "UpstreamPathTemplate": "/user/{id}",
            "UpstreamHttpMethod": [ "Get" ],
            "LoadBalancerOptions": {
                "Type": "RoundRobin"
            }
        }
    ]
}

通过修改 Type 的值,可以切换不同的负载均衡算法。

5.3 负载均衡的效果验证

为了验证负载均衡的效果,可以启动多个下游微服务实例,并通过工具如 Postman 发送多次请求。例如,启动两个用户服务实例,端口分别为 5001 和 5002,发送对 /user/123 的多次 Get 请求,观察请求是否均匀地分配到两个实例上。可以在每个微服务实例中记录接收到的请求信息,如在控制器中添加如下代码:

[ApiController]
[Route("api/user")]
public class UserController : ControllerBase
{
    private readonly ILogger<UserController> _logger;

    public UserController(ILogger<UserController> logger)
    {
        _logger = logger;
    }

    [HttpGet("{id}")]
    public IActionResult Get(int id)
    {
        _logger.LogInformation($"Received request for user {id} on port {HttpContext.Connection.LocalPort}");
        return Ok($"User {id} from port {HttpContext.Connection.LocalPort}");
    }
}

通过查看日志,可以确认负载均衡是否正常工作。

6. Ocelot 的身份验证与授权功能

6.1 基于 JWT 的身份验证

JSON Web Token(JWT)是一种常用的身份验证机制,Ocelot 可以集成 JWT 进行身份验证。首先,需要安装 Microsoft.AspNetCore.Authentication.JwtBearer NuGet 包:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Startup.cs 文件中配置 JWT 身份验证:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
       .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = "yourIssuer",
                ValidAudience = "yourAudience",
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("yourSecretKey"))
            };
        });
    services.AddOcelot();
}

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

    app.UseAuthentication();
    app.UseAuthorization();
    await app.UseOcelot();
}

ocelot.json 文件中配置需要进行身份验证的路由:

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/user/{id}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5001
                }
            ],
            "UpstreamPathTemplate": "/user/{id}",
            "UpstreamHttpMethod": [ "Get" ],
            "AuthenticationOptions": {
                "AuthenticationProviderKey": "TestKey",
                "AllowedScopes": []
            }
        }
    ]
}

这里 AuthenticationProviderKey 对应 Startup.cs 中配置的身份验证方案。

6.2 授权配置

在进行身份验证后,可以进一步配置授权规则。例如,只有特定角色的用户才能访问某些路由。在 Startup.cs 文件中添加授权策略:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AdminOnly", policy =>
            policy.RequireRole("Admin"));
    });
    // 其他配置...
}

ocelot.json 文件中配置需要授权的路由:

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/admin/{id}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5003
                }
            ],
            "UpstreamPathTemplate": "/admin/{id}",
            "UpstreamHttpMethod": [ "Get" ],
            "AuthorizationOptions": {
                "PolicyName": "AdminOnly"
            }
        }
    ]
}

这样,只有具有 Admin 角色的用户才能访问 /admin/{id} 路由。

7. Ocelot 的缓存功能

7.1 启用缓存功能

Ocelot 支持对请求的响应进行缓存,以提高性能。在 ocelot.json 文件中配置缓存:

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/user/{id}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5001
                }
            ],
            "UpstreamPathTemplate": "/user/{id}",
            "UpstreamHttpMethod": [ "Get" ],
            "FileCacheOptions": {
                "TtlSeconds": 60,
                "Region": "userCache"
            }
        }
    ]
}

在这个配置中,TtlSeconds 表示缓存的生存时间(秒),Region 用于指定缓存区域。

7.2 缓存策略配置

除了基本的缓存时间和区域配置,还可以配置更复杂的缓存策略。例如,根据请求头中的信息来决定是否缓存响应:

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/user/{id}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5001
                }
            ],
            "UpstreamPathTemplate": "/user/{id}",
            "UpstreamHttpMethod": [ "Get" ],
            "FileCacheOptions": {
                "TtlSeconds": 60,
                "Region": "userCache",
                "CacheIfHeaderPresent": "Cache-Control",
                "CacheKeyGenerator": {
                    "KeyPrefix": "userCachePrefix",
                    "UseHttpMethod": true,
                    "UsePath": true,
                    "UseQueryString": true
                }
            }
        }
    ]
}

CacheIfHeaderPresent 表示只有当请求头中存在指定的头信息(这里是 Cache-Control)时才进行缓存。CacheKeyGenerator 配置了缓存键的生成规则,包括前缀、是否使用 HTTP 方法、路径和查询字符串等。

7.3 缓存的清理与更新

在实际应用中,需要考虑缓存的清理和更新。例如,当用户数据发生变化时,需要清除相应的缓存。可以通过代码来实现缓存的清理,如下所示:

using Ocelot.Cache.CacheManager;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/user")]
public class UserController : ControllerBase
{
    private readonly ICacheManager _cacheManager;

    public UserController(ICacheManager cacheManager)
    {
        _cacheManager = cacheManager;
    }

    [HttpPut("{id}")]
    public IActionResult UpdateUser(int id)
    {
        // 更新用户数据的逻辑

        // 清除缓存
        _cacheManager.Remove($"userCachePrefix|Get|/user/{id}");
        return Ok($"User {id} updated and cache cleared");
    }
}

这样,当用户数据更新时,对应的缓存也会被清除,确保客户端获取到最新的数据。

8. Ocelot 的自定义中间件

8.1 自定义中间件的创建

有时候,内置的中间件无法满足项目的特定需求,这时可以创建自定义中间件。首先,创建一个中间件类,例如:

public class CustomMiddleware
{
    private readonly RequestDelegate _next;

    public CustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        // 在请求处理前执行的逻辑
        context.Items.Add("CustomKey", "CustomValue");
        await _next(context);
        // 在响应处理后执行的逻辑
        var customValue = context.Items["CustomKey"];
        if (customValue != null)
        {
            // 可以根据 customValue 进行一些操作
        }
    }
}

然后,在 Startup.cs 文件中注册自定义中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMiddleware<CustomMiddleware>();
    // 其他中间件配置
    await app.UseOcelot();
}

8.2 在 Ocelot 中使用自定义中间件

可以将自定义中间件集成到 Ocelot 的请求处理流程中。在 ocelot.json 文件中配置:

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/user/{id}",
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 5001
                }
            ],
            "UpstreamPathTemplate": "/user/{id}",
            "UpstreamHttpMethod": [ "Get" ],
            "Middleware": [
                {
                    "Name": "CustomMiddleware",
                    "Options": {}
                }
            ]
        }
    ]
}

这里 Middleware 节点指定了要使用的自定义中间件及其配置选项。通过这种方式,可以在 Ocelot 的请求路由、负载均衡等功能基础上,添加自定义的请求处理逻辑。

9. Ocelot 框架的监控与日志

9.1 监控指标的获取

Ocelot 可以通过集成 Prometheus 等监控工具来获取各种监控指标,如请求响应时间、请求成功率等。首先,安装 Ocelot.Provider.Prometheus NuGet 包:

dotnet add package Ocelot.Provider.Prometheus

Startup.cs 文件中配置 Prometheus 集成:

public void ConfigureServices(IServiceCollection services)
{
    services.AddOcelot()
       .AddPrometheus();
}

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

    app.UseMetricServer();
    app.UseHttpMetrics();
    await app.UseOcelot();
}

这样,启动项目后,可以通过访问 /metrics 端点获取 Prometheus 格式的监控指标数据,然后可以将这些数据接入 Grafana 等可视化工具进行展示。

9.2 日志记录与配置

Ocelot 支持多种日志记录方式,如使用内置的日志记录器、Serilog 等。以使用 Serilog 为例,首先安装相关 NuGet 包:

dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File

在项目根目录下创建 serilog.json 文件,配置日志记录:

{
    "Serilog": {
        "MinimumLevel": {
            "Default": "Information",
            "Override": {
                "Microsoft": "Warning",
                "Microsoft.Hosting.Lifetime": "Information"
            }
        },
        "WriteTo": [
            {
                "Name": "Console"
            },
            {
                "Name": "File",
                "Args": {
                    "path": "logs\\ocelot-.log",
                    "rollingInterval": "Day"
                }
            }
        ]
    }
}

Program.cs 文件中初始化 Serilog:

using Serilog;

public class Program
{
    public static int Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
           .ReadFrom.Configuration(new ConfigurationBuilder()
           .AddJsonFile("serilog.json")
           .Build())
           .CreateLogger();

        try
        {
            Log.Information("Starting web host");
            CreateHostBuilder(args).Build().Run();
            return 0;
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Host terminated unexpectedly");
            return 1;
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
           .UseSerilog()
           .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

通过这样的配置,Ocelot 的日志会同时输出到控制台和按天滚动的日志文件中,方便进行故障排查和系统监控。