VKProxy 集成 OpenTelemetry
OpenTelemetry
OpenTelemetry 是各类 API、SDK 和工具形成的集合。可用于插桩、生成、采集和导出遥测数据(链路、指标和日志),帮助你分析软件的性能和行为。
VKProxy 已集成OpenTelemetry,所以现在可以非常简单采集和导出遥测数据(链路、指标和日志)。
简单回顾asp.net core中如何使用
遥测数据分为链路、指标和日志 ,dotnet中使用可参考OpenTelemetry文档
简单的示例
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 核心内置指标
这里举个简单例子说明 如何自定义指标
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 一般

在VKProxy中如何使用?
默认情况,OpenTelemetry 已经启用,并且配置为 otlp 协议,大家只需配置otlp收集器,
相关配置如下:
| Environment variable | OtlpExporterOptions property |
|---|---|
| OTEL_EXPORTER_OTLP_ENDPOINT | Endpoint |
| OTEL_EXPORTER_OTLP_HEADERS | Headers |
| OTEL_EXPORTER_OTLP_TIMEOUT | TimeoutMilliseconds |
| OTEL_EXPORTER_OTLP_PROTOCOL | Protocol (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 中看到相关链路信息

指标信息

日志信息

当然你还可以通多如下命令调整过滤记录的信息
--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


汇总
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小赞赞呢)
VKProxy 集成 OpenTelemetry的更多相关文章
- 分布式链路追踪Jaeger + 微服务Pig在Rainbond上的实践分享
随着微服务架构的流行,客户端发起的一次请求可能需要涉及到多个或 N 个服务,致使我们对服务之间的监控和排查变得更加复杂. 举个例子: 某条业务线的某个接口调用服务端时快时慢,这时就需要排查各个服务的日 ...
- OpenTelemetry - 云原生下可观测性的新标准
CNCF 简介 CNCF(Cloud Native Computing Foundation),中文为"云原生计算基金会",CNCF是Linux基金会旗下的基金会,可以理解为一个非 ...
- CAP 6.0 版本发布通告 - 支持 OpenTelemetry
前言 今天,我们很高兴宣布 CAP 发布 6.0 版本正式版,在这个版本中,我们主要致力于对 OpenTelemetry 提供支持,以及更好的适配 .NET 6. 那么,接下来我们具体看一下吧. 总览 ...
- Go微服务框架go-kratos实战05:分布式链路追踪 OpenTelemetry 使用
一.分布式链路追踪发展简介 1.1 分布式链路追踪介绍 关于分布式链路追踪的介绍,可以查看我前面的文章 微服务架构学习与思考(09):分布式链路追踪系统-dapper论文学习(https://www. ...
- 基于 OpenTelemetry 的链路追踪
链路追踪的前世今生 分布式跟踪(也称为分布式请求跟踪)是一种用于分析和监控应用程序的方法,尤其是使用微服务架构构建的应用程序.分布式跟踪有助于精确定位故障发生的位置以及导致性能差的原因. 起源 链路追 ...
- go-zero docker-compose 搭建课件服务(八):集成jaeger链路追踪
0.转载 go-zero docker-compose 搭建课件服务(八):集成jaeger链路追踪 0.1源码地址 https://github.com/liuyuede123/go-zero-co ...
- 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成
阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...
- 构建一个基本的前端自动化开发环境 —— 基于 Gulp 的前端集成解决方案(四)
通过前面几节的准备工作,对于 npm / node / gulp 应该已经有了基本的认识,本节主要介绍如何构建一个基本的前端自动化开发环境. 下面将逐步构建一个可以自动编译 sass 文件.压缩 ja ...
- 常用 Gulp 插件汇总 —— 基于 Gulp 的前端集成解决方案(三)
前两篇文章讨论了 Gulp 的安装部署及基本概念,借助于 Gulp 强大的 插件生态 可以完成很多常见的和不常见的任务.本文主要汇总常用的 Gulp 插件及其基本使用,需要读者对 Gulp 有一个基本 ...
- Travis CI用来持续集成你的项目
这里持续集成基于GitHub搭建的博客为项目 工具: zqz@ubuntu:~$ node --version v4.2.6 zqz@ubuntu:~$ git --version git versi ...
随机推荐
- C++ 11之std::bind用法
#include <iostream> #include <functional> #include <stdio.h> int funcA( int a, int ...
- IIS设置发布公告页面
IIS原有站点停用 IIS新增里新增一个站点,端口及域名和原有站点一致 新增公告提示页面,如:index.html 新增web.config文件,并设置web.config 1 <system. ...
- elasticsearch RestHighLevelClient 关于document的常用操作 ---------- 编辑篇
es中的编辑分为:基于id的单条件编辑.自定义条件的编辑 基于id的单条件编辑:UpdateRequest 基于自定义条件的编辑:需借助底层脚本语言来实现有高低版本区分(见文章尾部)更新于2021-0 ...
- Joomla设计理念探讨系列2 -程序员要如何用代码实现“白纸幻想”?
客户幻想拖拖拽拽就建站?程序员连夜拆解出网格化背后的技术深渊. 1. 破灭的白纸幻想?不,是技术逻辑的碰撞 当客户兴奋地描述"白纸网格"时,程序员的第一反应往往是: "需 ...
- 一文搞懂K8s中的RBAC认证授权
概述 官方文档: https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/authorization/ https://kubern ...
- js加密手机号码中间四位方法
一.实现效果: 二.方法代码封装: 方法一: //encryptPhoneNumber.ts /** * 加密手机号码中间四位 * @param phone 手机号 * @returns { stri ...
- 题解:P1763 埃及分数
题目链接:link. 先放上代码,然后再讲解: #include<bits/stdc++.h> using namespace std; typedef long long ll; ll ...
- 袋鼠云:拥抱DeepSeek大模型,做Data+AI的长期主义者
<数据资产管理白皮书>下载地址:https://www.dtstack.com/resources/1073/?src=szsm <行业指标体系白皮书>下载地址:https:/ ...
- X6在数栈指标管理中的应用
一.需求背景 产品成立之初,产品的需求是需要对各种指标进行公式运算,组合成一个新的复合指标,供后续使用.当时产品提出的形式是有两种: 一种是直接让用户输入,不作任何其他操作,但这种方式带来的问题一 ...
- 深入理解 Taier:MR on Yarn 的实现原理
我们今天常说的大数据技术,它的理论基础来自于2003年 Google 发表的三篇论文,<The Google File System>.<MapReduce: Simplified ...