VKProxy 集成 OpenTelemetry

OpenTelemetry

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

layered-approach

VKProxy 已集成OpenTelemetry,所以现在可以非常简单采集和导出遥测数据(链路、指标和日志)。

简单回顾asp.net core中如何使用

遥测数据分为链路、指标和日志 ,dotnet中使用可参考OpenTelemetry文档

简单的示例

/* by 01130.hk - online tools website : 01130.hk/zh/imageformats.html */
using Microsoft.Extensions.Options;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;


Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT", "http://127.0.0.1:4317/"); // 配置OpenTelemetry收集器

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();

builder.Services.AddOpenTelemetry()
                    .ConfigureResource(resource => resource.AddService("TestApi", "").AddContainerDetector())
                    .WithTracing(tracing => tracing.AddAspNetCoreInstrumentation())
                    .WithMetrics(builder =>
                    {
                        builder.AddMeter("System.Runtime", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.MemoryPool");
                    })
                    .WithLogging()
                    .UseOtlpExporter();  // 示例使用 Otlp协议

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

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 核心内置指标

这里举个简单例子说明 如何自定义指标

/* by 01130.hk - online tools website : 01130.hk/zh/imageformats.html */
public class ProxyMetrics
{
    private readonly Meter? metrics;
    private readonly Counter<long>? requestsCounter;
    private readonly Histogram<double>? requestDuration;

    public ProxyMetrics(IMeterFactory meterFactory)
    {
        var f = serviceProvider.GetService<IMeterFactory>();
        metrics = f == null ? null : f.Create("VKProxy.ReverseProxy");
        if (metrics != null)
        {
            // 计数器
            requestsCounter = metrics.CreateCounter<long>("vkproxy.requests", unit: "{request}",    "Total number of (HTTP/tcp/udp) requests processed by the reverse proxy.");

            // 直方图
            requestDuration = metrics.CreateHistogram(
                "vkproxy.request.duration",
                unit: "s",
                description: "Proxy handle duration of (HTTP/tcp/udp) requests.",
                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] });
        }
    }

    public void ProxyBegin(IReverseProxyFeature feature)  // 在请求开始调用
    {
        string routeId = GetRouteId(feature);
        GeneralLog.ProxyBegin(generalLogger, routeId);
        if (requestsCounter != null && requestsCounter.Enabled)
        {
            var tags = new TagList
            {
                { "route", routeId }  // 设置 指标 tag,让其粒度到 route 级别
            };
            requestsCounter.Add(1, in tags); // +1 记录总共接受了多少个请求
        }
    }

     public void ProxyEnd(IReverseProxyFeature feature) // 在请求结束调用
    {
        string routeId = GetRouteId(feature);
        GeneralLog.ProxyEnd(generalLogger, routeId);
        if (requestDuration != null && requestDuration.Enabled)
        {
            var endTimestamp = Stopwatch.GetTimestamp();
            var t = Stopwatch.GetElapsedTime(feature.StartTimestamp, endTimestamp);
            var tags = new TagList
                {
                    { "route", routeId }  // 设置 指标 tag,让其粒度到 route 级别
                };
            requestDuration.Record(t.TotalSeconds, in tags); // 记录请求耗时
        }
    }
}

接着在 Program.cs 中向 DI 注册指标类型:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<ProxyMetrics>();

然后在具体地方使用

private async Task DoHttp(HttpContext context, ListenEndPointOptions? options)
{
    try
    {
        logger.ProxyBegin(proxyFeature);
        ///......
    }
    finally
    {
        logger.ProxyEnd(proxyFeature);
    }
}

链路

对于分布式链路追踪,其实dotnet现在已有内置抽象 Activity

这里举个简单例子说明 如何自定义链路

在 Program.cs 中向 DI 注册指标类型:

var builder = WebApplication.CreateBuilder(args);
builder.Services.TryAddSingleton(sp => new ActivitySource("VKProxy"));
builder.Services.TryAddSingleton(DistributedContextPropagator.Current);

使用 Activity 埋点信息

internal class ListenHandler : ListenHandlerBase
{
    internal const string ActivityName = "VKProxy.ReverseProxy";
    private readonly DistributedContextPropagator propagator;
    private readonly ActivitySource activitySource;

     public ListenHandler(...,
     DistributedContextPropagator propagator, ActivitySource activitySource)
    {
        this.propagator = propagator;
        this.activitySource = activitySource;
    }


    private async Task DoHttp(HttpContext context, ListenEndPointOptions? options)
    {
        Activity activity;
        if (activitySource.HasListeners())
        {
            var headers = context.Request.Headers;
            Activity.Current = activity = ActivityCreator.CreateFromRemote(activitySource, propagator, headers,
                static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
            {
                fieldValues = default;
                var headers = (IHeaderDictionary)carrier!;
                fieldValue = headers[fieldName];
            },
            ActivityName,
            ActivityKind.Server,
            tags: null,
            links: null, false);
        }
        else
        {
            activity = null;
        }

        if (activity != null)
        {
            activity.Start();
            context.Features.Set<IHttpActivityFeature>(new HttpActivityFeature(activity));
            context.Features.Set<IHttpMetricsTagsFeature>(new HttpMetricsTagsFeature()
            {
                Method = context.Request.Method,
                Protocol = context.Request.Protocol,
                Scheme = context.Request.Scheme,
                MetricsDisabled = true,
            });
            activity.DisplayName = $"{context.Request.Method} {context.Request.Path.Value}";
            activity.SetTag("http.request.method", context.Request.Method);
            activity.SetTag("network.protocol.name", "http");
            activity.SetTag("url.scheme", context.Request.Scheme);
            activity.SetTag("url.path", context.Request.Path.Value);
            activity.SetTag("url.query", context.Request.QueryString.Value);
            if (ProtocolHelper.TryGetHttpVersion(context.Request.Protocol, out var httpVersion))
            {
                activity.SetTag("network.protocol.version", httpVersion);
            }
            activity.SetTag("http.request.host", context.Request.Host);
            activity.SetTag("http.request.content_type", context.Request.ContentType);
            var l = context.Request.ContentLength;
            if (l.HasValue)
                activity.SetTag("http.request.content_length", l.Value);
        }

        try
        {
            logger.ProxyBegin(proxyFeature);
            ///......
        }
        finally
        {
            if (activity != null)
            {
                var statusCode = context.Response.StatusCode;
                activity.SetTag("http.response.status_code", statusCode);
                activity.Stop();
                Activity.Current = null;
            }

            logger.ProxyEnd(proxyFeature);
        }
    }

仪表盘

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

  • 将 OpenTelemetry 与 OTLP 和独立 Aspire 仪表板配合使用
  • 将 OpenTelemetry 与 Prometheus、Grafana 和 Jaeger 结合使用
  • 将 OpenTelemetry 与 SkyWalking ui 结合使用
  • 等等

大家可以根据自己喜好和实际选择

不过对应效果大致如 Aspire 一般

aspire-dashboard

在VKProxy中如何使用?

默认情况,OpenTelemetry 已经启用,并且配置为 otlp 协议,大家只需配置otlp收集器,

相关配置如下:

Environment variableOtlpExporterOptions property
OTEL_EXPORTER_OTLP_ENDPOINTEndpoint
OTEL_EXPORTER_OTLP_HEADERSHeaders
OTEL_EXPORTER_OTLP_TIMEOUTTimeoutMilliseconds
OTEL_EXPORTER_OTLP_PROTOCOLProtocol (grpc or http/protobuf)

(更多详细配置参见OpenTelemetry.Exporter.OpenTelemetryProtocol)

这里我们用 Aspire 仪表盘举例

因为它有个独立模式,只需启动一个镜像就可以尝试一下,当然真实产线还是需要配置其他存储等等

docker run --rm -it -p 18888:18888 -p 4317:18889 -d --name aspire-dashboard \
    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 以查看仪表板。
// 设置收集器环境变量

set OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4317/  

// 启动vkproxy (具体配置可参见之前的性能测试 https://www.cnblogs.com/fs7744/p/18978275 )

vkproxy proxy -c D:\code\github\VKProxy\samples\CoreDemo\test.json

访问一下

curl --location 'https://localhost:5001/WeatherForecast'

可以在 Aspire 中看到相关链路信息

tracing

指标信息

meters

日志信息

logs

当然你还可以通多如下命令调整过滤记录的信息

     --telemetry (Environment:VKPROXY_TELEMETRY)
         Allow export telemetry data (metrics, logs, and traces) to help you analyze your software’s performance and behavior.

     --meter (Environment:VKPROXY_TELEMETRY_METER)
         Subscribe meters, default is System.Runtime,Microsoft.AspNetCore.Server.Kestrel,Microsoft.AspNetCore.Server.Kestrel.Udp,Microsoft.AspNetCore.MemoryPool,VKProxy.ReverseProxy

     --drop_instrument (Environment:VKPROXY_TELEMETRY_DROP_INSTRUMENT)
         Drop instruments

     --exporter (Environment:VKPROXY_TELEMETRY_EXPORTER)
         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/`

测一测性能

.\vegeta.exe attack -insecure -rate=10000/s -duration=60s -format=http -targets=http2proxy -output=http2proxyresults -http2 
// http2proxy content:
// GET https://127.0.0.1:5001/WeatherForecast

HTTP2

http2plot

汇总

Requests      [total, rate, throughput]         599999, 10000.94, 9992.64
Duration      [total, attack, wait]             59.994s, 59.994s, 0s
Latencies     [min, mean, 50, 90, 95, 99, max]  0s, 3.428ms, 2.015ms, 5.405ms, 6.882ms, 32.941ms, 301.44ms
Bytes In      [total, mean]                     231889817, 386.48
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           99.92%
Status Codes  [code:count]                      0:498  200:599501
Error Set:
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.

之前没有遥测的性能测试汇总

Requests      [total, rate, throughput]         599930, 9998.35, 9998.35
Duration      [total, attack, wait]             1m0s, 1m0s, 0s
Latencies     [min, mean, 50, 90, 95, 99, max]  0s, 676.024µs, 0s, 2.56ms, 3.705ms, 5.367ms, 26.437ms
Bytes In      [total, mean]                     232052167, 386.80
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:599930
Error Set:

对比之前的测试而言,的确 otlp 遥测对性能有了不小的影响,但这点消耗单次请求看,消耗还是很低微的,总体利大于弊

VKProxy 是使用c#开发的基于 Kestrel 实现 L4/L7的代理(感兴趣的同学烦请点个github小赞赞呢)

Java是一种具备卓越性能与广泛平台适应性的高级程序设计语言,最初由Sun Microsystems(现属Oracle公司)的James Gosling及其团队于1995年正式发布。该语言在设计上追求简洁性、稳定性、可移植性以及并发处理能力,同时具备动态执行特性。其核心特征与显著优点可归纳如下: **平台无关性**:遵循“一次编写,随处运行”的理念,Java编写的程序能够在多种操作系统与硬件环境中执行,无需针对不同平台进行修改。这一特性主要依赖于Java虚拟机(JVM)的实现,JVM作为程序与底层系统之间的中间层,负责解释并执行编译后的字节码。 **面向对象范式**:Java全面贯彻面向对象的设计原则,提供对封装、继承、多态等机制的完整支持。这种设计方式有助于构建结构清晰、模块独立的代码,提升软件的可维护性与扩展性。 **并发编程支持**:语言层面集成了多线程处理能力,允许开发者构建能够同时执行多项任务的应用程序。这一特性尤其适用于需要高并发处理的场景,例如服务器端软件、网络服务及大规模分布式系统。 **自动内存管理**:通过内置的垃圾回收机制,Java运行时环境能够自动识别并释放不再使用的对象所占用的内存空间。这不仅降低了开发者在内存管理方面的工作负担,也有效减少了因手动管理内存可能引发的内存泄漏问题。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
一、系统核心功能: (1)在具备管理权限时,可执行公交线路的添加、移除及修改操作,相关数据将持久化存储于本地文件,避免重复输入。 (2)提供对已存储公交线路的检索与查看功能。 (3)依据设定的起点与终点,自动规划出行方案,支持输出换乘次数最少或途经站点最少的路线,同时提供行程预计耗时、线路首班车及末班车时刻等信息。 二、操作规范说明: (1)进行线路新增或编辑时,需按以下格式提交信息: 线路名称 起始站点名称 - 终点站点名称 (注:若运行过程存在疑问,可通过沟通获取进一步指导,支持远程协助。) 本项目所附源码为个人毕业设计成果,所有程序均经过完整测试并稳定运行,毕业答辩中平均评审分数为96分,可供可靠下载与使用。 <项目说明> 1. 资源内包含的项目代码均已通过运行验证,确保功能完整后方才上传,请安心下载使用。 2. 本项目适用于计算机及相关专业(如计算机科学、人工智能、通信工程、自动化、电子信息等)的在校师生、企业职员学习参考,亦适合初学者进阶练习,或作为毕业设计、课程设计、作业及项目初期演示的参考材料。 3. 若具备一定编程基础,也可基于此代码进行二次开发,扩展其他功能,适用于毕业设计、课程作业等场景。 下载后请优先查阅README.md文档(如有),内容仅限学习交流,严禁用于商业用途。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值