找回密码
 立即注册
首页 业界区 业界 VKProxy 集成 OpenTelemetry

VKProxy 集成 OpenTelemetry

枢覆引 2025-9-28 12:20:26
OpenTelemetry

OpenTelemetry 是各类 API、SDK 和工具形成的集合。可用于插桩、生成、采集和导出遥测数据(链路、指标和日志),帮助你分析软件的性能和行为。

VKProxy 已集成OpenTelemetry,所以现在可以非常简单采集和导出遥测数据(链路、指标和日志)。
简单回顾asp.net core中如何使用

遥测数据分为链路、指标和日志 ,dotnet中使用可参考OpenTelemetry文档
简单的示例
  1. using Microsoft.Extensions.Options;
  2. using OpenTelemetry;
  3. using OpenTelemetry.Resources;
  4. using OpenTelemetry.Trace;
  5. Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT", "http://127.0.0.1:4317/"); // 配置OpenTelemetry收集器
  6. var builder = WebApplication.CreateBuilder(args);
  7. // Add services to the container.
  8. builder.Services.AddControllers();
  9. // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
  10. builder.Services.AddOpenApi();
  11. builder.Services.AddOpenTelemetry()
  12.                     .ConfigureResource(resource => resource.AddService("TestApi", "").AddContainerDetector())
  13.                     .WithTracing(tracing => tracing.AddAspNetCoreInstrumentation())
  14.                     .WithMetrics(builder =>
  15.                     {
  16.                         builder.AddMeter("System.Runtime", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.MemoryPool");
  17.                     })
  18.                     .WithLogging()
  19.                     .UseOtlpExporter();  // 示例使用 Otlp协议
  20. var app = builder.Build();
  21. // Configure the HTTP request pipeline.
  22. if (app.Environment.IsDevelopment())
  23. {
  24.     app.MapOpenApi();
  25. }
  26. app.UseHttpsRedirection();
  27. app.UseAuthorization();
  28. app.MapControllers();
  29. app.Run();
复制代码
日志

这个其实没什么特别,由于已经提供非常抽象的 ILogger, 所以只需大家按照自己记录log所需正常使用就好,
log 大家使用非常多,这里就不详细示例了,可参考文档Logging in .NET and ASP.NET Core
而OpenTelemetry 对于log,主要是如何在log 结构化并记录分布式追踪的信息,以方便关联。
OpenTelemetry sdk 已经内置支持,只需配置好 .WithLogging(),对应log和分布式追踪的信息都会写入收集器中。
指标

dotnet 中已提供统一的抽象 Meter, 大家不必再关注是为 Prometheus 还是其他方案提供对应性能指标方案
详细文档可参考ASP.NET Core 指标 和 ASP.NET 核心内置指标
这里举个简单例子说明 如何自定义指标
  1. public class ProxyMetrics
  2. {
  3.     private readonly Meter? metrics;
  4.     private readonly Counter<long>? requestsCounter;
  5.     private readonly Histogram<double>? requestDuration;
  6.     public ProxyMetrics(IMeterFactory meterFactory)
  7.     {
  8.         var f = serviceProvider.GetService<IMeterFactory>();
  9.         metrics = f == null ? null : f.Create("VKProxy.ReverseProxy");
  10.         if (metrics != null)
  11.         {
  12.             // 计数器
  13.             requestsCounter = metrics.CreateCounter<long>("vkproxy.requests", unit: "{request}",    "Total number of (HTTP/tcp/udp) requests processed by the reverse proxy.");
  14.             // 直方图
  15.             requestDuration = metrics.CreateHistogram(
  16.                 "vkproxy.request.duration",
  17.                 unit: "s",
  18.                 description: "Proxy handle duration of (HTTP/tcp/udp) requests.",
  19.                 advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300] });
  20.         }
  21.     }
  22.     public void ProxyBegin(IReverseProxyFeature feature)  // 在请求开始调用
  23.     {
  24.         string routeId = GetRouteId(feature);
  25.         GeneralLog.ProxyBegin(generalLogger, routeId);
  26.         if (requestsCounter != null && requestsCounter.Enabled)
  27.         {
  28.             var tags = new TagList
  29.             {
  30.                 { "route", routeId }  // 设置 指标 tag,让其粒度到 route 级别
  31.             };
  32.             requestsCounter.Add(1, in tags); // +1 记录总共接受了多少个请求
  33.         }
  34.     }
  35.      public void ProxyEnd(IReverseProxyFeature feature) // 在请求结束调用
  36.     {
  37.         string routeId = GetRouteId(feature);
  38.         GeneralLog.ProxyEnd(generalLogger, routeId);
  39.         if (requestDuration != null && requestDuration.Enabled)
  40.         {
  41.             var endTimestamp = Stopwatch.GetTimestamp();
  42.             var t = Stopwatch.GetElapsedTime(feature.StartTimestamp, endTimestamp);
  43.             var tags = new TagList
  44.                 {
  45.                     { "route", routeId }  // 设置 指标 tag,让其粒度到 route 级别
  46.                 };
  47.             requestDuration.Record(t.TotalSeconds, in tags); // 记录请求耗时
  48.         }
  49.     }
  50. }
复制代码
接着在 Program.cs 中向 DI 注册指标类型:
  1. var builder = WebApplication.CreateBuilder(args);
  2. builder.Services.AddSingleton<ProxyMetrics>();
复制代码
然后在具体地方使用
  1. private async Task DoHttp(HttpContext context, ListenEndPointOptions? options)
  2. {
  3.     try
  4.     {
  5.         logger.ProxyBegin(proxyFeature);
  6.         ///......
  7.     }
  8.     finally
  9.     {
  10.         logger.ProxyEnd(proxyFeature);
  11.     }
  12. }
复制代码
链路

对于分布式链路追踪,其实dotnet现在已有内置抽象 Activity
这里举个简单例子说明 如何自定义链路
在 Program.cs 中向 DI 注册指标类型:
  1. var builder = WebApplication.CreateBuilder(args);
  2. builder.Services.TryAddSingleton(sp => new ActivitySource("VKProxy"));
  3. builder.Services.TryAddSingleton(DistributedContextPropagator.Current);
复制代码
使用  Activity 埋点信息
  1. internal class ListenHandler : ListenHandlerBase
  2. {
  3.     internal const string ActivityName = "VKProxy.ReverseProxy";
  4.     private readonly DistributedContextPropagator propagator;
  5.     private readonly ActivitySource activitySource;
  6.      public ListenHandler(...,
  7.      DistributedContextPropagator propagator, ActivitySource activitySource)
  8.     {
  9.         this.propagator = propagator;
  10.         this.activitySource = activitySource;
  11.     }
  12.     private async Task DoHttp(HttpContext context, ListenEndPointOptions? options)
  13.     {
  14.         Activity activity;
  15.         if (activitySource.HasListeners())
  16.         {
  17.             var headers = context.Request.Headers;
  18.             Activity.Current = activity = ActivityCreator.CreateFromRemote(activitySource, propagator, headers,
  19.                 static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
  20.             {
  21.                 fieldValues = default;
  22.                 var headers = (IHeaderDictionary)carrier!;
  23.                 fieldValue = headers[fieldName];
  24.             },
  25.             ActivityName,
  26.             ActivityKind.Server,
  27.             tags: null,
  28.             links: null, false);
  29.         }
  30.         else
  31.         {
  32.             activity = null;
  33.         }
  34.         if (activity != null)
  35.         {
  36.             activity.Start();
  37.             context.Features.Set<IHttpActivityFeature>(new HttpActivityFeature(activity));
  38.             context.Features.Set<IHttpMetricsTagsFeature>(new HttpMetricsTagsFeature()
  39.             {
  40.                 Method = context.Request.Method,
  41.                 Protocol = context.Request.Protocol,
  42.                 Scheme = context.Request.Scheme,
  43.                 MetricsDisabled = true,
  44.             });
  45.             activity.DisplayName = $"{context.Request.Method} {context.Request.Path.Value}";
  46.             activity.SetTag("http.request.method", context.Request.Method);
  47.             activity.SetTag("network.protocol.name", "http");
  48.             activity.SetTag("url.scheme", context.Request.Scheme);
  49.             activity.SetTag("url.path", context.Request.Path.Value);
  50.             activity.SetTag("url.query", context.Request.QueryString.Value);
  51.             if (ProtocolHelper.TryGetHttpVersion(context.Request.Protocol, out var httpVersion))
  52.             {
  53.                 activity.SetTag("network.protocol.version", httpVersion);
  54.             }
  55.             activity.SetTag("http.request.host", context.Request.Host);
  56.             activity.SetTag("http.request.content_type", context.Request.ContentType);
  57.             var l = context.Request.ContentLength;
  58.             if (l.HasValue)
  59.                 activity.SetTag("http.request.content_length", l.Value);
  60.         }
  61.         try
  62.         {
  63.             logger.ProxyBegin(proxyFeature);
  64.             ///......
  65.         }
  66.         finally
  67.         {
  68.             if (activity != null)
  69.             {
  70.                 var statusCode = context.Response.StatusCode;
  71.                 activity.SetTag("http.response.status_code", statusCode);
  72.                 activity.Stop();
  73.                 Activity.Current = null;
  74.             }
  75.             logger.ProxyEnd(proxyFeature);
  76.         }
  77.     }
复制代码
仪表盘

遥测数据收集到哪儿,用什么展示,业界有各种方案, 比如

  • 将 OpenTelemetry 与 OTLP 和独立 Aspire 仪表板配合使用
  • 将 OpenTelemetry 与 Prometheus、Grafana 和 Jaeger 结合使用
  • 将 OpenTelemetry 与 SkyWalking ui  结合使用
  • 等等
大家可以根据自己喜好和实际选择
不过对应效果大致如 Aspire 一般
2.png

在VKProxy中如何使用?

默认情况,OpenTelemetry 已经启用,并且配置为 otlp 协议,大家只需配置otlp收集器,
相关配置如下:
Environment variableOtlpExporterOptions propertyOTEL_EXPORTER_OTLP_ENDPOINTEndpointOTEL_EXPORTER_OTLP_HEADERSHeadersOTEL_EXPORTER_OTLP_TIMEOUTTimeoutMillisecondsOTEL_EXPORTER_OTLP_PROTOCOLProtocol (grpc or http/protobuf)(更多详细配置参见OpenTelemetry.Exporter.OpenTelemetryProtocol)
这里我们用 Aspire 仪表盘举例
因为它有个独立模式,只需启动一个镜像就可以尝试一下,当然真实产线还是需要配置其他存储等等
  1. docker run --rm -it -p 18888:18888 -p 4317:18889 -d --name aspire-dashboard \
  2.     mcr.microsoft.com/dotnet/aspire-dashboard:9.0
复制代码
前面的 Docker 命令:

  • 从 mcr.microsoft.com/dotnet/aspire-dashboard:9.0 映像启动容器。
  • 公开两个端口的容器实例:

    • 将仪表板的 OTLP 端口 18889 映射到主机的端口 4317。 端口 4317 从应用接收 OpenTelemetry 数据。 应用使用 OpenTelemetry 协议 (OTLP)发送数据。
    • 将仪表板的端口 18888 映射到主机的端口 18888。 端口 18888 具有仪表板 UI。 导航到浏览器中 http://localhost:18888 以查看仪表板。

  1. // 设置收集器环境变量
  2. set OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4317/  
  3. // 启动vkproxy (具体配置可参见之前的性能测试 https://www.cnblogs.com/fs7744/p/18978275 )
  4. vkproxy proxy -c D:\code\github\VKProxy\samples\CoreDemo\test.json
复制代码
访问一下
  1. curl --location 'https://localhost:5001/WeatherForecast'
复制代码
可以在 Aspire 中看到相关链路信息
3.jpeg

指标信息
4.jpeg

日志信息
5.jpeg

当然你还可以通多如下命令调整过滤记录的信息
  1.      --telemetry (Environment:VKPROXY_TELEMETRY)
  2.          Allow export telemetry data (metrics, logs, and traces) to help you analyze your software’s performance and behavior.
  3.      --meter (Environment:VKPROXY_TELEMETRY_METER)
  4.          Subscribe meters, default is System.Runtime,Microsoft.AspNetCore.Server.Kestrel,Microsoft.AspNetCore.Server.Kestrel.Udp,Microsoft.AspNetCore.MemoryPool,VKProxy.ReverseProxy
  5.      --drop_instrument (Environment:VKPROXY_TELEMETRY_DROP_INSTRUMENT)
  6.          Drop instruments
  7.      --exporter (Environment:VKPROXY_TELEMETRY_EXPORTER)
  8.          How to export telemetry data (metrics, logs, and traces), support prometheus,console,otlp , default is otlp, please set env like `OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4317/`
复制代码
测一测性能
  1. .\vegeta.exe attack -insecure -rate=10000/s -duration=60s -format=http -targets=http2proxy -output=http2proxyresults -http2
  2. // http2proxy content:
  3. // GET https://127.0.0.1:5001/WeatherForecast
复制代码
6.jpeg

7.jpeg

汇总
  1. Requests      [total, rate, throughput]         599999, 10000.94, 9992.64
  2. Duration      [total, attack, wait]             59.994s, 59.994s, 0s
  3. Latencies     [min, mean, 50, 90, 95, 99, max]  0s, 3.428ms, 2.015ms, 5.405ms, 6.882ms, 32.941ms, 301.44ms
  4. Bytes In      [total, mean]                     231889817, 386.48
  5. Bytes Out     [total, mean]                     0, 0.00
  6. Success       [ratio]                           99.92%
  7. Status Codes  [code:count]                      0:498  200:599501
  8. Error Set:
  9. Get "https://127.0.0.1:5001/WeatherForecast": dial tcp 0.0.0.0:0->127.0.0.1:5001: connectex: No connection could be made because the target machine actively refused it.
复制代码
之前没有遥测的性能测试汇总
  1. Requests      [total, rate, throughput]         599930, 9998.35, 9998.35
  2. Duration      [total, attack, wait]             1m0s, 1m0s, 0s
  3. Latencies     [min, mean, 50, 90, 95, 99, max]  0s, 676.024µs, 0s, 2.56ms, 3.705ms, 5.367ms, 26.437ms
  4. Bytes In      [total, mean]                     232052167, 386.80
  5. Bytes Out     [total, mean]                     0, 0.00
  6. Success       [ratio]                           100.00%
  7. Status Codes  [code:count]                      200:599930
  8. Error Set:
复制代码
对比之前的测试而言,的确 otlp 遥测对性能有了不小的影响,但这点消耗单次请求看,消耗还是很低微的,总体利大于弊
VKProxy 是使用c#开发的基于 Kestrel 实现 L4/L7的代理(感兴趣的同学烦请点个github小赞赞呢)

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册