Welcome to YARP - 2.1配置功能 - 配置文件

目录

Welcome to YARP - 1.认识YARP并搭建反向代理服务

Welcome to YARP - 2.配置功能

Welcome to YARP - 3.负载均衡

Welcome to YARP - 4.限流

Welcome to YARP - 5.身份验证和授权

Welcome to YARP - 6.压缩、缓存

Welcome to YARP - 7.目标健康检查

Welcome to YARP - 8.分布式跟踪

哈哈哈,第一篇文章还说,只规划了8篇文章,写到配置功能的时候发现东西还是挺多的,还是拆分成小章节来说吧。目前成了10篇了—_—。写之前觉得配置功能应该没什么东西可讲,结果写着写着都想讲一嘴。然后就越写越多,大家看的时候可以选择性的跳过。

介绍

如果有同学不知道YARP是什么,YARP有哪些功能的同学请移步第一篇文章Welcome to YARP - 1.认识YARP并搭建反向代理服务。接下来这篇文章主要讲解YARP的配置功能,包含:配置文件配置提供程序配置筛选器

配置文件(Configuration Files)

YARP可以使用 Microsoft.Extensions 中的 IConfiguration 从文件加载路由和群集的配置。这就说明了不只是JSON文件,任何支持 IConfiguration 源都有效。你可以基于IConfiguration扩展出XMLYAML等格式的配置。而且如果源文件发生更改,配置也将更新,无需重新启动代理服务。

1.加载配置

要从 IConfiguration 加载代理配置,请在启动中添加以下代码(LoadFromConfig)

builder.Services.AddReverseProxy()//添加ReverseProxy相关服务到DI
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));//从配置文件中加载ReverseProxy的设置

LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))这段代码会把配置文件中ReverseProxy节点下的路由(Routes)配置和集群(Clusters)配置都加载到程序中。

2.多个配置源

从 1.1 开始,YARP 支持从多个源加载代理配置。LoadFromConfig 可以多次调用,引用不同的 IConfiguration 节点,也可以与不同的配置源(如InMemory)结合使用。路由可以引用来自其他源的群集。

请注意,对于给定的路由或集群,不支持合并来自不同源的节点配置。

思考1:YARP是怎么做到可以多配置源或加载不同的配置源呢?(只是使用,可略过此部分)
源码解读:

这时候我们就要扒一扒他的源码了

看源码也是为了看优秀的代码怎么写,学习其设计,其次就是看源码的技巧,怎么一层一层的去解剖、去找你想要的东西。

我们先从入口开始:

builder.Services.AddReverseProxy()//添加ReverseProxy相关服务到DI
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));//从配置文件中加载ReverseProxy的设置

接下来我们在看一下 AddReverseProxy 这个扩展方法

	/// <summary>
    /// Adds ReverseProxy's services to Dependency Injection.
    /// </summary>
    public static IReverseProxyBuilder AddReverseProxy(this IServiceCollection services)
    {
        var builder = new ReverseProxyBuilder(services);
        builder
            .AddConfigBuilder()
            .AddRuntimeStateManagers()
            .AddConfigManager()//关键就在这里
            .AddSessionAffinityPolicies()
            .AddActiveHealthChecks()
            .AddPassiveHealthCheck()
            .AddLoadBalancingPolicies()
            .AddHttpSysDelegation()
            .AddDestinationResolver()
            .AddProxy();

        services.TryAddSingleton<ProxyEndpointFactory>();

        services.AddDataProtection();
        services.AddAuthorization();
        services.AddCors();
        services.AddRouting();

        return builder;
    }

我们看到有一个 AddConfigManager ,这个就是注入 ProxyConfigManager 的,关键代码就在这个类里,我们直接看这个类,代码太多了,我们只看关键性的代码片段

internal sealed class ProxyConfigManager : EndpointDataSource, IProxyStateLookup, IDisposable
{
    private static readonly IReadOnlyDictionary<string, ClusterConfig> _emptyClusterDictionary = new ReadOnlyDictionary<string, ClusterConfig>(new Dictionary<string, ClusterConfig>());

    private readonly object _syncRoot = new();
    private readonly ILogger<ProxyConfigManager> _logger;
    private readonly IProxyConfigProvider[] _providers;//关键接口
}

IProxyConfigProvider 接口是获取配置的关键,他里面只有一个 返回类型为 IProxyConfigGetConfig 方法。IProxyConfig 里面定义了当前路由和集群的列表,以及一个用于信息过期,并需要通知YARP 重新加载配置的令牌IChangeToken ,而他们就是获取配置的核心,我们甚至可以实现IProxyConfigProvider接口去自定义一个配置提供者,比如LoadFromRedis()LoadFromSqlServer()完全可以的(等有时间了,我会写个demo示例,先把规划的文章写完了再去搞这个)。

/// <summary>
/// A data source for proxy route and cluster information.
/// </summary>
public interface IProxyConfigProvider
{
    /// <summary>
    /// Returns the current route and cluster data.
    /// </summary>
    /// <returns></returns>
    IProxyConfig GetConfig();
}

ProxyConfigManager类就是定义了 IProxyConfigProvider[] 数组去获取多个 IProxyConfigProvider 实例,然后在调用 GetConfig() 方法去获取每个实例的配置。具体实现在这个类的 InitialLoadAsync方法里,代码片段如下:

internal async Task<EndpointDataSource> InitialLoadAsync()
    {
        try
        {
            var routes = new List<RouteConfig>();
            var clusters = new List<ClusterConfig>();

            var resolvedConfigs = new List<(int Index, IProxyConfigProvider Provider, ValueTask<IProxyConfig> Config)>(_providers.Length);
            
            //这里循环_providers去调用LoadConfigAsync方法去拿配置,LoadConfigAsync方法内部就是调用的provider.GetConfig()方法获取配置,只不过内部还做了一写其他操作(配置校验等)
            for (var i = 0; i < _providers.Length; i++)
            {
                var provider = _providers[i];
                var configLoadTask = LoadConfigAsync(provider, cancellationToken: default);
                resolvedConfigs.Add((i, provider, configLoadTask));
            }

            foreach (var (i, provider, configLoadTask) in resolvedConfigs)
            {
                var config = await configLoadTask;
                _configs[i] = new ConfigState(provider, config);
                routes.AddRange(config.Routes ?? Array.Empty<RouteConfig>());
                clusters.AddRange(config.Clusters ?? Array.Empty<ClusterConfig>());
            }

        return this;
    }

然后我们在验证下,既然官方说了1.1支持的多配置那么1.0版本这里是不是就不是数组呢?答案是肯定的:

1698774926330.png

好了现在已经知道是通过多个IProxyConfigProvider 循环去调用每个实例的GetConfig方法拿配置了。那么IProxyConfigProvider是接口,他的实例从哪来?哈哈哈,其实这是一个很蠢的问题。那不就是通过 LoadFromXXX 来注入不同的 ConfigProvider 吗?本着既然写了就刨到底的态度,我们还是看一眼吧,哈哈哈

LoadFromConfig方法是通过配置文件获取配置,注入的是ConfigurationConfigProvider ,而这个类则是继承了IProxyConfigProvider 接口:

	/// <summary>
    /// Loads routes and endpoints from config.
    /// </summary>
    public static IReverseProxyBuilder LoadFromConfig(this IReverseProxyBuilder builder, IConfiguration config)
    {
        if (config is null)
        {
            throw new ArgumentNullException(nameof(config));
        }

        builder.Services.AddSingleton<IProxyConfigProvider>(sp =>
        {
            // This is required because we're capturing the configuration via a closure
            return new ConfigurationConfigProvider(sp.GetRequiredService<ILogger<ConfigurationConfigProvider>>(), config);
        });

        return builder;
    }

internal sealed class ConfigurationConfigProvider : IProxyConfigProvider, IDisposable
{
    public IProxyConfig GetConfig()
    {
        // First time load
        if (_snapshot is null)
        {
            _subscription = ChangeToken.OnChange(_configuration.GetReloadToken, UpdateSnapshot);
            UpdateSnapshot();
        }

        return _snapshot;
    }
}

LoadFromMemory 也是同样的,注入的是InMemoryConfigProvider

	/// <summary>
    /// Adds an InMemoryConfigProvider
    /// </summary>
    public static IReverseProxyBuilder LoadFromMemory(this IReverseProxyBuilder builder, IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
    {
        builder.Services.AddSingleton(new InMemoryConfigProvider(routes, clusters));
        builder.Services.AddSingleton<IProxyConfigProvider>(s => s.GetRequiredService<InMemoryConfigProvider>());
        return builder;
    }

public sealed class InMemoryConfigProvider : IProxyConfigProvider
{
    // Marked as volatile so that updates are atomic
    private volatile InMemoryConfig _config;
    
    /// <summary>
    /// Implementation of the IProxyConfigProvider.GetConfig method to supply the current snapshot of configuration
    /// </summary>
    /// <returns>An immutable snapshot of the current configuration state</returns>
    public IProxyConfig GetConfig() => _config;
}

说的有点多了,其实是一个很简单的功能(对于使用者来说),快速入手可以忽略这部分。

3.配置约定(可略过)

基于文件的配置通过 IProxyConfigProvider 实现在应用程序启动时和每次配置更改时进行转换,动态映射到 Yarp.ReverseProxy.Configuration 命名空间中的类型。

说人话就是:Yarp.ReverseProxy.Configuration这个命名空间下有很多关于YARP[配置的类]( Namespace Yarp.ReverseProxy.Configuration (microsoft.github.io) ),如ActiveHealthCheckConfig.csClusterConfig.cs等等,然后这些类都是通过IProxyConfigProvider进行映射的,我们拿到配置后会对这些类的属性进行配置映射。

4.配置结构

LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))这段代码会把配置文件中ReverseProxy节点下的 Routes 配置节和 Clusters 配置节都加载到程序中。

例:

{
  "ReverseProxy": {
    "Routes": {
      "route1" : {
        "ClusterId": "cluster1",
        "Match": {
          "Path": "{**catch-all}",
          "Hosts" : [ "www.aaaaa.com", "www.bbbbb.com"],
        },
      }
    },
    "Clusters": {
      "cluster1": {
        "Destinations": {
          "cluster1/destination1": {
            "Address": "https://example.com/"
          }
        }
      }
    }
  }
}

5.Routes

路由配置最小集:

​ 路由节点是路由匹配及其相关配置的无序集合。路径至少需要以下字段:

  • (RouteId)路由 ID - 唯一名称 (示例中的route1

  • (ClusterId)群集 ID - 指 集群(Clusters) 节点中 某个集群的名称 (示例中的cluster1

  • (Match)匹配 - 包含 Hosts 数组或 Path 模式字符串。PathASP.NET Core 路由模板,可以按ASP.NET Core 路由模板文档进行定义。

    路由匹配中越具体的路由匹配优先级越高。也可以使用 Order 字段实现显式排序,值越低优先级越高。

其他配置:

当然你还可以在路由节点下配置以下内容

  • AuthorizationPolicy
    • 要应用于此路由的授权策略的名称。如果未设置,则仅应用回退策略。设置为 Default 以启用应用程序默认策略的授权。设置为 Anonymous 以禁用此路由的所有授权检查。
  • Headers
    • 要匹配的标头(未指定)为任意
  • CorsPolicy
    • 要应用于此路由的CorsPolicy 的名称。如果未设置,则不会自动匹配 cors 预检请求的路由。设置为 Default 以启用具有默认策略的 cors。设置为 Disable 以拒绝此路由的 cors 请求。

还有很多支持的配置这里就不一 一列举了,可以查看官方的 RouteConfig 文档。

思考2:令我好奇的是,我查看了官方的 RouteConfig 文档竟然没有发现关于针对路由限流的?难道限流不是在这里配置?方便的是每页的API文档上有对应的源码链接,点看查看,果然是由限流配置的。只不过要.NET 7.0才支持这个功能。

1698682731505.png

然后我又打开限流的文档查看,发现文档上也是有配置的。哈哈哈,这里算是来了一波剧透了吧。

1698682839917.png

6.Clusters

群集节点是命名群集的无序集合。集群主要包含命名目标及地址的集合,其中任何一个都被认为能够处理给定路由的请求。代理将根据路由和群集配置处理请求,以便选择目标。

说人话:YARP会根据路由节点的集群标识(ClusterId)去集群节点中匹配相应的集群目标地址(Address)

相关其他字段配置,请参阅 ClusterConfig

7.所有属性配置(可以暂时略过,后续在具体功能模块会拿出来单独讲)

{
  // 代理服务的URL,必须和下面路由定义的Url区分开
  "Urls": "http://localhost:5000;https://localhost:5001",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      // 取消注释以对运行时和代理隐藏诊断消息
      // "Microsoft": "Warning",
      // "Yarp" : "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "ReverseProxy": {
    // Routes 告诉代理要转发的请求
    "Routes": { 
      "minimumroute" : {
        // 匹配任何内容并将其路由到www.example.com
        "ClusterId": "minimumcluster",
        "Match": {
          "Path": "{**catch-all}"
        }
      },
      "allrouteprops" : {
        // 匹配 /something/* 并路由到“allclusterrops”
        "ClusterId": "allclusterprops", // 其中一个群集的名称
        "Order" : 100, // 数字越低,优先级越高
        "MaxRequestBodySize" : 1000000, // 以字节为单位。服务器请求体大小的限制(默认为30MB)。设置为-1可禁用。
        "Authorization Policy" : "Anonymous", // 策略名称 "Default", "Anonymous"
        "CorsPolicy" : "Default", // 要应用到此路由的CorsPolicy的名称 "Default", "Disable"
        "Match": {
          "Path": "/something/{**remainder}", // 这里的Path是 ASP.NET Core语法 
          "Hosts" : [ "www.aaaaa.com", "www.bbbbb.com"], // 要匹配的主机名(未指定)为任意
          "Methods" : [ "GET", "PUT" ], // 匹配的HTTP方法, uspecified 是所有
          "Headers": [ // 要匹配的标头(未指定)为任意
            {
              "Name": "MyCustomHeader", // 标头的名称
              "Values": [ "value1", "value2", "another value" ],
              "Mode": "ExactHeader", // "ExactHeader(完全匹配)","HeaderPrefix(前缀匹配)", "Exists" , "Contains", "NotContains"
              "IsCaseSensitive": true
            }
          ],
          "QueryParameters": [ // 要匹配的查询参数(未指定)为任意
            {
              "Name": "MyQueryParameter", // 查询参数名称
              "Values": [ "value1", "value2", "another value" ], 
              "Mode": "Exact", // "Exact" "Prefix", "Exists" , "Contains", "NotContains"
              "IsCaseSensitive": true
            }
          ]
        },
        "MetaData" : { // 可由自定义扩展使用的键值对列表
          "MyName" : "MyValue"
        },
        "Transforms" : [ // 转换列表。有关更多详细信息,请参阅Transforms文章
          {
            "RequestHeader": "MyHeader",
            "Set": "MyValue",
          } 
        ]
      }
    },
    // Clusters 告诉代理在哪里以及如何转发请求
    "Clusters": {
      "minimumcluster": {
        "Destinations": {
          "example.com": {
            "Address": "http://www.example.com/"
          }
        }
      },
      "allclusterprops": {
        "Destinations": {
          "first_destination": {
            "Address": "https://contoso.com"
          },
          "another_destination": {
            "Address": "https://10.20.30.40",
            "Health" : "https://10.20.30.40:12345/test" // 覆盖活动的运行状况检查
          }
        },
        "LoadBalancingPolicy" : "PowerOfTwoChoices", // "PowerOfTwoChoices", "FirstAlphabetical", "Random", "RoundRobin", "LeastRequests"
        "SessionAffinity": {
          "Enabled": true, // 默认 'false'
          "Policy": "Cookie", // 默认值"Cookie", 可选 "CustomHeader"
          "FailurePolicy": "Redistribute", // 默认值"Redistribute", 可选 "Return503Error"
          "Settings" : {
              "CustomHeaderName": "MySessionHeaderName" // 默认为 'X-Yarp-Proxy-Affinity`
          }
        },
        "HealthCheck": {
          "Active": { // 进行API调用以验证运行状况
            "Enabled": "true",
            "Interval": "00:00:10",
            "Timeout": "00:00:10",
            "Policy": "ConsecutiveFailures",
            "Path": "/api/health" // 要查询运行状况状态的API终结点
          },
          "Passive": { // 基于HTTP响应代码禁用目标
            "Enabled": true, // 默认值 false
            "Policy" : "TransportFailureRateHealthPolicy", 
            "ReactivationPeriod" : "00:00:10" // 10s
          }
        },
        "HttpClient" : { // 用于关联指定的HttpClient实例的配置
          "SSLProtocols" : "Tls13",
          "DangerousAcceptAnyServerCertificate" : false,
          "MaxConnectionsPerServer" : 1024,
          "EnableMultipleHttp2Connections" : true,
          "RequestHeaderEncoding" : "Latin1" // 如何解释标头值中的非ASCII字符
        },
        "HttpRequest" : { // 将请求发送到目的地的选项
          "ActivityTimeout" : "00:02:00",
          "Version" : "2",
          "VersionPolicy" : "RequestVersionOrLower",
          "AllowResponseBuffering" : "false"
        },
        "MetaData" : { 
          "TransportFailureRateHealthPolicy.RateLimit": "0.5", // 被动健康策略使用
          "MyKey" : "MyValue"
        }
      }
    }
  }
}

总结

至此配置文件部分结束,说的有点多了,对于使用者而言 我们知道怎么用的,支持哪些配置源,各个配置代表什么就可以了。但是本着学习的态度结果罗嗦了一堆,大家看的时候可以选择性忽略,能忽略的部分已经做了标记。

本章节没有代码示例

下个小章节我们快速介绍下:Configuration Providers(多种方式提供配置)

posted @ 2023-11-02 01:44  coding-y  阅读(1314)  评论(0编辑  收藏  举报