可观测是衡量现代应用质量的核心指标之一,我们设计了ITracer/ISpan这一套链路追踪规范。开发者可以根据该规范来编写各种关键性代码埋点,在应用项目注入星尘监控后,实现埋点数据的采集与上报分析。NewLife全系列项目(大于30个)均使用了ITracer埋点。
Nuget包:NewLife.Core
源码:https://github.com/NewLifeX/X/blob/master/NewLife.Core/Log/ITracer.cs
快速入门
我们来写第一个手工埋点:
private void Ss_Received(Object? sender, ReceivedEventArgs e)
{
var ns = (this as INetSession).Host;
var tracer = ns?.Tracer;
using var span = tracer?.NewSpan($"net:{ns?.Name}:Receive", e.Message);
try
{
OnReceive(e);
}
catch (Exception ex)
{
span?.SetError(ex, e.Message ?? e.Packet);
throw;
}
}
如上,tracer是ITracer实例,通过NewSpan创建了一个埋点(名称是动态生成)。由于使用了using,这个埋点span将在离开作用域时结束。因此,通过该埋点,我们不仅能够记录收到数据包的次数,还能挤出每一次的耗时。
NewSpan创建埋点时,第二个参数使用e.Message消息作为数据标签,方便在星尘监控中查看分析问题。在catch里面,通过span.SetError方法,把该埋点标记为异常埋点,并记录异常信息。因此,借助ITracer,我们还能够记录异常次数。
监控埋点最佳实践
启动项目引入Nuget包 NewLife.Stardust.Extensions 后,通过 services.AddStardust
注入星尘,其中包括注入星尘的 ITracer 实现,参考《星尘分布式服务平台》。
各层代码中通过构造注入ITracer,或者通过属性设置ITracer,或者直接访问静态成员 DefaultTracer.Instance,即可获得创建埋点的能力。如下就是构造函数注入ITracer,然后通过tracer.NewSpan创建埋点:
public class DataRetentionService : IHostedService
{
private readonly StarServerSetting _setting;
private readonly ITracer _tracer;
private TimerX _timer;
public DataRetentionService(StarServerSetting setting, ITracer tracer)
{
_setting = setting;
_tracer = tracer;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new TimerX(DoWork, null, DateTime.Today.AddMinutes(Rand.Next(60)), 600 * 1000) { Async = true };
// 临时来一次
TimerX.Delay(DoWork, 10_000);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer.TryDispose();
return Task.CompletedTask;
}
private void DoWork(Object state)
{
var set = _setting;
if (set.DataRetention <= 0) return;
// 保留数据的起点
var time = DateTime.Now.AddDays(-set.DataRetention);
var time2 = DateTime.Now.AddDays(-set.DataRetention2);
var time3 = DateTime.Now.AddDays(-set.DataRetention3);
using var span = _tracer?.NewSpan("DataRetention", new { time, time2, time3 });
try
{
// 删除应用分钟统计数据
rs = AppMinuteStat.DeleteBefore(time);
XTrace.WriteLine("删除[{0}]之前的AppMinuteStat共:{1:n0}", time.ToFullString(), rs);
// 删除追踪分钟统计数据
rs = TraceMinuteStat.DeleteBefore(time);
XTrace.WriteLine("删除[{0}]之前的TraceMinuteStat共:{1:n0}", time.ToFullString(), rs);
// 删除追踪小时统计数据
rs = TraceHourStat.DeleteBefore(time2);
XTrace.WriteLine("删除[{0}]之前的TraceHourStat共:{1:n0}", time2.ToFullString(), rs);
}
catch (Exception ex)
{
span?.SetError(ex, null);
}
}
}
如上代码,就是一个定时任务,每600秒清理一次数据。每次触发DataRetention埋点,并捕获异常。内部执行的delete语句埋点,会跟DataRetention埋点构成一个调用链。
或者查看这个调用链的日志视图。
DataRetention埋点的调用总次数、异常数以及耗时等数据,可以在星尘平台查看。
一般来说,星尘监控的ITracer在进程内只有一个实例,也可以使用静态属性保存全局引用。
ITracer接口
性能跟踪器,轻量级APM规范。
常见APM是把收集到的所有埋点数据上传到服务端统一存储分析,数据量极为庞大。而ITracer实现在本地采样后,马上进行一轮统计,仅上报初步统计数据以及一条采样数据,极大的节省了网络传输和数据存储。
该接口定义如下,一般采用星尘监控的完整实现。
#region 属性
/// <summary>采样周期</summary>
Int32 Period { get; set; }
/// <summary>最大正常采样数。采样周期内,最多只记录指定数量的正常事件,用于绘制依赖关系</summary>
Int32 MaxSamples { get; set; }
/// <summary>最大异常采样数。采样周期内,最多只记录指定数量的异常事件,默认10</summary>
Int32 MaxErrors { get; set; }
/// <summary>超时时间。超过该时间时强制采样,毫秒</summary>
Int32 Timeout { get; set; }
/// <summary>最大标签长度。超过该长度时将截断,默认1024字符</summary>
Int32 MaxTagLength { get; set; }
/// <summary>向http/rpc请求注入TraceId的参数名,为空表示不注入,默认W3C标准的traceparent</summary>
String? AttachParameter { get; set; }
#endregion
/// <summary>建立Span构建器</summary>
/// <param name="name"></param>
/// <returns></returns>
ISpanBuilder BuildSpan(String name);
/// <summary>开始一个Span</summary>
/// <param name="name">操作名</param>
/// <returns></returns>
ISpan NewSpan(String name);
/// <summary>开始一个Span,指定数据标签</summary>
/// <param name="name">操作名</param>
/// <param name="tag">数据</param>
/// <returns></returns>
ISpan NewSpan(String name, Object? tag);
/// <summary>截断所有Span构建器数据,重置集合</summary>
/// <returns></returns>
ISpanBuilder[] TakeAll();
这里最常用的就是NewSpan,几个属性参数一般由星尘监控中心来下发调整。
NewSpan得到的每一个ISpan埋点,在结束后加入name为key的一个字典中,累加次数、异常数和耗时,同时计算最大最小值。在每个采样周期结束后,星尘SDK将会把该字典拿走,打包上传给星尘监控中心。
以下是用于控制采样上报的几个参数:
- Period采样周期。定期上报数据的间隔,默认60秒。
- MaxSamples最大正常采样数。采样周期内,最多只记录指定数量的正常事件,用于绘制依赖关系,默认1。
- MaxErrors最大异常采样数。采样周期内,最多只记录指定数量的异常事件,默认10。
- Timeout超时时间。超过该时间时强制采样,默认5000毫秒
- MaxTagLength最大标签长度。超过该长度时将截断,默认1024字符
- AttachParameter。向http/rpc请求注入TraceId的参数名,为空表示不注入,默认W3C标准的traceparent
ISpan接口
性能跟踪片段,轻量级APM规范。
每个ISpan就是一个埋点实例,它在结束后加入ITracer集合,累加调用次数等指标。
ISpan可用成员如下:
/// <summary>唯一标识。随线程上下文、Http、Rpc传递,作为内部片段的父级</summary>
String Id { get; set; }
/// <summary>父级片段标识</summary>
String? ParentId { get; set; }
/// <summary>跟踪标识。可用于关联多个片段,建立依赖关系,随线程上下文、Http、Rpc传递</summary>
String TraceId { get; set; }
/// <summary>开始时间。Unix毫秒</summary>
Int64 StartTime { get; set; }
/// <summary>结束时间。Unix毫秒</summary>
Int64 EndTime { get; set; }
/// <summary>数据标签。记录一些附加数据</summary>
String? Tag { get; set; }
/// <summary>错误信息</summary>
String? Error { get; set; }
/// <summary>设置错误信息,ApiException除外</summary>
/// <param name="ex">异常</param>
/// <param name="tag">标签</param>
void SetError(Exception ex, Object? tag = null);
/// <summary>设置数据标签。内部根据长度截断</summary>
/// <param name="tag">标签</param>
void SetTag(Object tag);
埋点可以嵌套,因此Id标识唯一性,ParentId标识上级,而同一个调用链中的TraceId相同。
StartTime/EndTime记录该埋点的开始时间和结束时间,毫秒级已经足够。
Tag/Error记录该埋点的数据标签和错误内容。
需要注意的是,每一个埋点名的ITracer集合,只会累加次数等指标并保存第一个ISpan作为采样样本。而其它ISpan实例在累加后将会被抛弃,其中的Tag/Erro也不再占用内容。
ISpan扩展
ISpan重要扩展方法如下:
/// <summary>把片段信息附加到http请求头上</summary>
/// <param name="span">片段</param>
/// <param name="request">http请求</param>
/// <returns></returns>
public static HttpRequestMessage Attach(this ISpan span, HttpRequestMessage request);
/// <summary>把片段信息附加到api请求头上</summary>
/// <param name="span">片段</param>
/// <param name="args">api请求参数</param>
/// <returns></returns>
[return: NotNullIfNotNull(nameof(args))]
public static Object? Attach(this ISpan span, Object? args);
/// <summary>从api请求释放片段信息</summary>
/// <param name="span">片段</param>
/// <param name="parameters">参数</param>
public static void Detach(this ISpan span, IDictionary<String, Object?> parameters);
/// <summary>附加Tag信息在原Tag信息后面</summary>
/// <param name="span">片段</param>
/// <param name="tag">Tag信息</param>
public static void AppendTag(this ISpan span, Object tag);
Attach用于把当前调用链信息注入到Http请求头中,服务方收到请求后解析出来,并作为ISpan的延伸,它们具有相同的TraceId。当这些上下游系统都把埋点采样样本上报给星尘监控中心以后,后者将能够根据TraceId形成跨应用系统的完整调用链。