マイクロソフトがNginxを置き換えてスループットが80%向上!

マイクロソフトがNginxを置き換えてスループットが80%向上!

Azure App ServiceはYARPでNginxを置き換え、80%以上のスループット向上を達成しました。毎日160B以上のリクエスト(1.9 m RPS)を処理しています。これはマイクロソフトの素晴らしい技術革新です。

最終更新 2024/01/14 6:19
tokengo
読了目安 5 分
カテゴリ
.NET
タグ
.NET C# 技術アップデート

大佬消息截图

Azure App Service は YARP で Nginx を置き換え、80% 以上のスループット向上を達成しました。1 日あたり 160B 以上のリクエスト(1.9M RPS)を処理しています。これは Microsoft の素晴らしい技術革新です。

まず Yarp とは何かを紹介します。

Yarp とは?

YARP(Yet Another Reverse Proxy)は、Microsoft が開発したオープンソースの高性能リバースプロキシライブラリで、C# で記述されています。.NET プラットフォーム上でリバースプロキシサーバーを構築するための基盤として設計されています。YARP は主に .NET 5 以降を対象としており、開発者が .NET アプリケーション内で簡単にリバースプロキシ機能を実装できるようにします。

YARP の主な特徴と機能:

  1. モジュール性と拡張性: YARP は高度にモジュール化されて設計されており、HTTP リクエストルーティング、ロードバランシング、ヘルスチェックなどの内部コンポーネントを必要に応じて置き換えたり拡張したりできます。
  2. パフォーマンス: YARP は高パフォーマンス向けに最適化されており、.NET の非同期プログラミングモデルと効率的な IO 操作を活用して、大量の同時接続を処理します。
  3. 設定駆動: YARP の動作は設定によって制御でき、ファイル、データベース、その他のソースから動的に設定をロードすることができます。
  4. ルーティング: パス、ヘッダー、クエリパラメータなどのさまざまなパラメータに基づいてリクエストルーティングルールを設定できます。
  5. ロードバランシング: ラウンドロビン、最小接続数、ランダム選択など、複数のロードバランシング戦略が組み込まれており、カスタム戦略も定義できます。
  6. ヘルスチェック: バックエンドサービスのヘルスチェックをサポートし、正常なバックエンドサービスのインスタンスにのみリクエストが転送されるようにします。
  7. トランスフォーマー: リクエストとレスポンスの変換(ヘッダー、パス、クエリパラメータの変更など)を許可します。
  8. セッションアフィニティ: セッションアフィニティをサポートし、同じクライアントからのリクエストを同じバックエンドサービスのインスタンスに送信します。

YARP を使用するいくつかのシナリオ:

  • リバースプロキシ: クライアントとバックエンドサービスの中間層として、リクエスト転送とロードバランシングを提供します。
  • API ゲートウェイ: マイクロサービスアーキテクチャの API ゲートウェイとして、ルーティング、認証、モニタリングなどの機能を提供します。
  • エッジサービス: アプリケーションと外部の世界の間にセキュリティ層を提供し、SSL 終端、リクエスト制限などのタスクを処理します。

Yarp の簡単な使い方

WebApi プロジェクトを作成します。

NuGet パッケージをインストールします。

<ItemGroup>
 <PackageReference Include="Yarp.ReverseProxy" Version="2.0.0" />
</ItemGroup>

appsettings.json を開きます。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ReverseProxy": {
    "Routes": {
      "route1": {
        "ClusterId": "cluster1",
        "Match": {
          "Path": "{**catch-all}"
        }
      }
    },
    "Clusters": {
      "cluster1": {
        "Destinations": {
          "destination1": {
            "Address": "https://cn.bing.com/"
          }
        }
      }
    }
  }
}

Program.cs を開きます。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();

プロジェクトを起動し、API にアクセスすると bing にプロキシ転送されます。

成功代理到必应

Yarp ツールプロキシの使用

以下では、ミドルウェアで yarp を使用する方法を紹介します。

IHttpForwarder を使用する必要があります。

まず Program.cs を修正します。ここでは HttpForwarder を注入し、Run ミドルウェアを提供し、ミドルウェア内でエンドポイントのアドレス https://cn.bing.com/ を手動で指定します。その後、プロジェクトを起動します。

using Yarp.ReverseProxy.Forwarder;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpForwarder(); // IHttpForwarder を注入
var app = builder.Build();

var httpMessage = new HttpMessageInvoker(new HttpClientHandler());

app.Run((async context =>
{
    var httpForwarder = context.RequestServices.GetRequiredService<IHttpForwarder>();
    var destinationPrefix = "https://cn.bing.com/";

    await httpForwarder.SendAsync(context, destinationPrefix, httpMessage);
}));

app.Run();

同様にプロキシされますが、簡単な使い方と異なり、コードレベルでプロキシを制御しています。

代码控制反代效果一样

yarp を使用して Bing の応答内容を変更する

上記のプロキシ使用を基にして、Bing の応答内容を変更してみましょう。

Program.cs を開きます。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpForwarder(); // IHttpForwarder を注入
var app = builder.Build();

var httpMessage = new HttpMessageInvoker(new HttpClientHandler()
{
    // https エラーを無視
    ServerCertificateCustomValidationCallback = (_, _, _, _) => true,
    AllowAutoRedirect = false,
    AutomaticDecompression = DecompressionMethods.GZip,
    UseCookies = false,
    UseProxy = false,
    UseDefaultCredentials = true,
});
var destinationPrefix = "https://cn.bing.com/";

var bingTransformer = new BingTransformer();

app.Run((async context =>
{
    var httpForwarder = context.RequestServices.GetRequiredService<IHttpForwarder>();
    await httpForwarder.SendAsync(context, destinationPrefix, httpMessage, new ForwarderRequestConfig(),
        bingTransformer);
}));

app.Run();

BingTransformer.cs を作成します。

public class BingTransformer : HttpTransformer
{
    public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest,
        string destinationPrefix,
        CancellationToken cancellationToken)
    {
        var uri = RequestUtilities.MakeDestinationAddress(destinationPrefix, httpContext.Request.Path,
            httpContext.Request.QueryString);
        proxyRequest.RequestUri = uri;
        proxyRequest.Headers.Host = uri.Host;
        await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken);
    }

    public override async ValueTask<bool> TransformResponseAsync(HttpContext httpContext,
        HttpResponseMessage? proxyResponse,
        CancellationToken cancellationToken)
    {
        await base.TransformResponseAsync(httpContext, proxyResponse, cancellationToken);

        if (httpContext.Request.Method == "GET" &&
            httpContext.Response.Headers["Content-Type"].Any(x => x.StartsWith("text/html")))
        {
            var encoding = proxyResponse.Content.Headers.FirstOrDefault(x => x.Key == "Content-Encoding").Value;
            if (encoding?.FirstOrDefault() == "gzip")
            {
                var content = proxyResponse?.Content.ReadAsByteArrayAsync(cancellationToken).Result;
                if (content != null)
                {
                    var result = Encoding.UTF8.GetString(GZipDecompressByte(content));
                    result = result.Replace("国内版", "Token Bing 搜索 - 国内版");
                    proxyResponse.Content = new StringContent(GZipDecompressString(result));
                }
            }
            else if (encoding.FirstOrDefault() == "br")
            {
                var content = proxyResponse?.Content.ReadAsByteArrayAsync(cancellationToken).Result;
                if (content != null)
                {
                    var result = Encoding.UTF8.GetString(BrDecompress(content));
                    result = result.Replace("国内版", "Token Bing 搜索 - 国内版");
                    proxyResponse.Content = new ByteArrayContent(BrCompress(result));
                }
            }
            else
            {
                var content = proxyResponse?.Content.ReadAsStringAsync(cancellationToken).Result;
                if (content != null)
                {
                    content = content.Replace("国内版", "Token Bing 搜索 - 国内版");
                    proxyResponse.Content = new StringContent(content);
                }
            }
        }

        return true;
    }

    /// <summary>
    /// GZip 解凍
    /// </summary>
    /// <param name="bytes"></param>
    /// <returns></returns>
    public static byte[] GZipDecompressByte(byte[] bytes)
    {
        using var targetStream = new MemoryStream();
        using var compressStream = new MemoryStream(bytes);
        using var zipStream = new GZipStream(compressStream, CompressionMode.Decompress);
        using (var decompressionStream = new GZipStream(compressStream, CompressionMode.Decompress))
        {
            decompressionStream.CopyTo(targetStream);
        }

        return targetStream.ToArray();
    }

    /// <summary>
    /// GZip 解凍
    /// </summary>
    /// <param name="str"></param>
    /// <returns></returns>
    public static string GZipDecompressString(string str)
    {
        using var compressStream = new MemoryStream(Encoding.UTF8.GetBytes(str));
        using var zipStream = new GZipStream(compressStream, CompressionMode.Decompress);
        using var resultStream = new StreamReader(new MemoryStream(compressStream.ToArray()));
        return resultStream.ReadToEnd();
    }

    /// <summary>
    /// Br 圧縮
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public static byte[] BrCompress(string str)
    {
        using var outputStream = new MemoryStream();
        using (var compressionStream = new BrotliStream(outputStream, CompressionMode.Compress))
        {
            compressionStream.Write(Encoding.UTF8.GetBytes(str));
        }

        return outputStream.ToArray();
    }

    /// <summary>
    /// Br 解凍
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public static byte[] BrDecompress(byte[] input)
    {
        using (var inputStream = new MemoryStream(input))
        using (var outputStream = new MemoryStream())
        using (var decompressionStream = new BrotliStream(inputStream, CompressionMode.Decompress))
        {
            decompressionStream.CopyTo(outputStream);
            return outputStream.ToArray();
        }
    }
}

得られた効果: 国内版Token Bing 搜索 - 国内版 に変更されました。

修改响应内容结果

Yarp 関連資料

技術交流グループ:737776595

公式ドキュメント:https://microsoft.github.io/reverse-proxy/articles/getting-started.html

Token からの共有

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2026/04/22

各OSバージョンの.NETサポート状況(250707更新)

仮想マシンとテストマシンを使用して、各OSバージョンの.NETサポート状況を確認します。OSインストール後、対応するランタイムをインストールし、Stardustエージェントを実行できることを確認します(合格条件)。

続きを読む
同じカテゴリ / 同じタグ 2026/02/07

AOTの使用経験のまとめ

プロジェクト作成当初から、新機能を追加したり新しい構文を使用したりした場合には、すぐにAOT公開テストを実施するという良い習慣を身につけるべきです。

続きを読む