前言

OpenTracing是一个链路跟踪的开放协议,已经有开源的.net实现:opentracing-csharp,同时支持.net framework和.net core,Github地址:https://github.com/opentracing/opentracing-csharp

这个库支持多种链路跟踪模式,不过仅提供了最基础的功能,想用在实际项目中还需要做很多增强,还好也有人做了开源项目:opentracing-contrib,Github地址:https://github.com/opentracing-contrib/csharp-netcore

opentracing-contrib中集成了一个名为Jaeger的类库,这个库实现了链路跟踪数据的采样和上报,支持将数据上传到Jaeger进行分析统计。

为了同时保障性能和跟踪关键数据,能够远程调整采样率是很重要的,Jaeger本身也提供了远程配置采样率的支持。

不过我这里用的阿里云链路跟踪不支持,配置的设计也和想要的不同,所以自己做了一个采样和上报配置的动态更新,也才有了这篇文章。

思路

使用Jaeger初始化Tracer大概是这样的:

var tracer = new Tracer.Builder(serviceName)
.WithSampler(sampler)
.WithReporter(reporter)
.Build();
GlobalTracer.Register(tracer);

首先是提供当前服务的名字,然后需要提供一个采样器,再提供一个上报器,Build下生成ITracer的一个实例,最后注册到全局。

可以分析得出,采样和上报配置的更新就是更新采样器和上报器。

不过Tracer并没有提供UpdateSampler和UdapteReporter的方法,被卡住了,怎么办呢?

前文提到Jaeger是支持采样率的动态调整的,看看它怎么做的:

        private RemoteControlledSampler(Builder builder)
{
... _pollTimer = new Timer(_ => UpdateSampler(), null, TimeSpan.Zero, builder.PollingInterval);
} /// <summary>
/// Updates <see cref="Sampler"/> to a new sampler when it is different.
/// </summary>
internal void UpdateSampler()
{
try
{
SamplingStrategyResponse response = _samplingManager.GetSamplingStrategyAsync(_serviceName)
.ConfigureAwait(false).GetAwaiter().GetResult(); ... UpdateRateLimitingOrProbabilisticSampler(response);
}
catch (Exception ex)
{
...
}
} private void UpdateRateLimitingOrProbabilisticSampler(SamplingStrategyResponse response)
{
...
lock (_lock)
{
if (!Sampler.Equals(sampler))
{
Sampler.Close();
Sampler = sampler;
...
}
}
}

这里只留下关键代码,可以看到核心就是:通过一个Timer定时获取采样策略,然后替换原来的Sampler。

这是一个很好理解的办法,下边就按照这个思路来搞。

方案

分别提供一个可更新的Sampler和可更新的Reporter,Build Tracer时使用这两个可更新的类。这里延续开源项目中Samper和Reporter的创建方式,给出这两个类。

可更新的Sampler:

internal class UpdatableSampler : ValueObject, ISampler
{
public const string Type = "updatable"; private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private readonly string _serviceName;
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _logger;
private readonly IMetrics _metrics; internal ISampler Sampler { get; private set; } private UpdatableSampler(Builder builder)
{
_serviceName = builder.ServiceName;
_loggerFactory = builder.LoggerFactory;
_logger = _loggerFactory.CreateLogger<UpdatableSampler>();
_metrics = builder.Metrics;
Sampler = builder.InitialSampler;
} /// <summary>
/// Updates <see cref="Sampler"/> to a new sampler when it is different.
/// </summary>
public void UpdateSampler(ISampler sampler)
{
try
{
_lock.EnterWriteLock();
if (!Sampler.Equals(sampler))
{
Sampler.Close();
Sampler = sampler;
_metrics.SamplerUpdated.Inc();
}
}
catch (System.Exception ex)
{
_logger.LogWarning(ex, "Updating sampler failed");
_metrics.SamplerQueryFailure.Inc();
}
finally
{
_lock.ExitWriteLock();
}
} public SamplingStatus Sample(string operation, TraceId id)
{
try
{
_lock.EnterReadLock();
var status= Sampler.Sample(operation, id);
return status;
}
finally
{
_lock.ExitReadLock();
}
} public override string ToString()
{
try
{
_lock.EnterReadLock();
return $"{nameof(UpdatableSampler)}(Sampler={Sampler})";
}
finally
{
_lock.ExitReadLock();
}
} public void Close()
{
try
{
_lock.EnterWriteLock();
Sampler.Close();
}
finally
{
_lock.ExitWriteLock();
}
} protected override IEnumerable<object> GetAtomicValues()
{
yield return Sampler;
} public sealed class Builder
{
internal string ServiceName { get; }
internal ILoggerFactory LoggerFactory { get; private set; }
internal ISampler InitialSampler { get; private set; }
internal IMetrics Metrics { get; private set; } public Builder(string serviceName)
{
ServiceName = serviceName ?? throw new ArgumentNullException(nameof(serviceName));
} public Builder WithLoggerFactory(ILoggerFactory loggerFactory)
{
LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
return this;
} public Builder WithInitialSampler(ISampler initialSampler)
{
InitialSampler = initialSampler ?? throw new ArgumentNullException(nameof(initialSampler));
return this;
} public Builder WithMetrics(IMetrics metrics)
{
Metrics = metrics ?? throw new ArgumentNullException(nameof(metrics));
return this;
} public UpdatableSampler Build()
{
if (LoggerFactory == null)
{
LoggerFactory = NullLoggerFactory.Instance;
}
if (InitialSampler == null)
{
InitialSampler = new ProbabilisticSampler();
}
if (Metrics == null)
{
Metrics = new MetricsImpl(NoopMetricsFactory.Instance);
} return new UpdatableSampler(this);
}
}
}

可更新的Reporter:

internal class UpdatableReporter : IReporter
{
public const string Type = "updatable"; private readonly string _serviceName;
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _logger;
private readonly IMetrics _metrics;
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); internal IReporter Reporter { get; private set; } private UpdatableReporter(Builder builder)
{
_serviceName = builder.ServiceName;
_loggerFactory = builder.LoggerFactory;
_logger = _loggerFactory.CreateLogger<UpdatableReporter>();
_metrics = builder.Metrics;
Reporter = builder.InitialReporter;
} /// <summary>
/// Updates <see cref="Reporter"/> to a new reporter when it is different.
/// </summary>
public void UpdateReporter(IReporter reporter)
{
try
{
_lock.EnterWriteLock(); if (!Reporter.Equals(reporter))
{
Reporter.CloseAsync(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
Reporter = reporter;
_metrics.SamplerUpdated.Inc();
}
}
catch (System.Exception ex)
{
_logger.LogWarning(ex, "Updating reporter failed");
_metrics.ReporterFailure.Inc();
}
finally
{
_lock.ExitWriteLock();
}
} public void Report(Span span)
{
try
{
_lock.EnterReadLock();
Reporter.Report(span);
}
finally
{
_lock.ExitReadLock();
}
} public override string ToString()
{
try
{
_lock.EnterReadLock();
return $"{nameof(UpdatableReporter)}(Reporter={Reporter})";
}
finally
{
_lock.ExitReadLock();
}
} public async Task CloseAsync(CancellationToken cancellationToken)
{
try
{
_lock.EnterWriteLock();
await Reporter.CloseAsync(cancellationToken);
}
finally
{
_lock.ExitWriteLock();
}
} public sealed class Builder
{
internal string ServiceName { get; }
internal ILoggerFactory LoggerFactory { get; private set; }
internal IReporter InitialReporter { get; private set; }
internal IMetrics Metrics { get; private set; } public Builder(string serviceName)
{
ServiceName = serviceName ?? throw new ArgumentNullException(nameof(serviceName));
} public Builder WithLoggerFactory(ILoggerFactory loggerFactory)
{
LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
return this;
} public Builder WithInitialReporter(IReporter initialReporter)
{
InitialReporter = initialReporter ?? throw new ArgumentNullException(nameof(initialReporter));
return this;
} public Builder WithMetrics(IMetrics metrics)
{
Metrics = metrics ?? throw new ArgumentNullException(nameof(metrics));
return this;
} public UpdatableReporter Build()
{
if (LoggerFactory == null)
{
LoggerFactory = NullLoggerFactory.Instance;
}
if (InitialReporter == null)
{
InitialReporter = new NoopReporter();
}
if (Metrics == null)
{
Metrics = new MetricsImpl(NoopMetricsFactory.Instance);
} return new UpdatableReporter(this);
}
}
}

注意这里边用到了读写锁,因为要做到不停止服务的更新,而且大部分情况下都是读,使用lock就有点大柴小用了。

现在初始化Tracer大概是这样的:

sampler = new UpdatableSampler.Builder(serviceName)
.WithInitialSampler(BuildSampler(configuration))
.Build(); reporter = new UpdatableReporter.Builder(serviceName)
.WithInitialReporter(BuildReporter(configuration))
.Build(); var tracer = new Tracer.Builder(serviceName)
.WithSampler(sampler)
.WithReporter(reporter)
.Build();

当配置发生改变时,调用sampler和reporter的更新方法:

        private void OnTracingConfigurationChanged(TracingConfiguration newConfiguration, TracingConfigurationChangedInfo changedInfo)
{
...
((UpdatableReporter)_reporter).UpdateReporter(BuildReporter(newConfiguration));
((UpdatableSampler)_sampler).UpdateSampler(BuildSampler(newConfiguration));
...
}

这里就不写如何监听配置的改变了,使用Timer或者阻塞查询等等都可以。

后记

opentracing-contrib这个项目只支持.net core,如果想用在.net framwork中还需要自己搞,这个方法会单独写一篇文章,这里就不做介绍了。

实现.Net程序中OpenTracing采样和上报配置的自动更新的更多相关文章

  1. 浅析 .Net Core中Json配置的自动更新

    Pre 很早在看 Jesse 的Asp.net Core快速入门的课程的时候就了解到了在Asp .net core中,如果添加的Json配置被更改了,是支持自动重载配置的,作为一名有着严重" ...

  2. IIS如何避免子web应用程序中继承根目录web.config配置

    1.一种方式,需要改动根目录的web.config(不是很推荐) <?xml version="1.0"?> <configuration> <loc ...

  3. 关于微信小程序中遇到的各种问题汇总(持续更新)

    1.关于 <input />标签容易忽略的问题: 使用<input />标签时容易忘记绑定bindblur()方法(输入框失去焦点时触发),因为用户用键盘输入时不一定会点击完成 ...

  4. .net程序中http请求的超时配置

    请求时的超时 // // 摘要: // 获取或设置 System.Net.HttpWebRequest.GetResponse() 和 System.Net.HttpWebRequest.GetReq ...

  5. Mysql中,update语句引起的时间戳自动更新问题

    前几天遇到一个奇怪的问题. 在Mysql数据库中有一张表,表中有一个字段是timestamp类型的.我在update别的字段时,这个timestamp字段的时间会自动更新为当前时间. 后来发现,是My ...

  6. nw.js桌面程序自动更新(node.js表白记)

    Hello Google Node.js 一个基于Google V8 的JavaScript引擎. 一个伟大的端至端语言,或许我对你的热爱源自于web这门极富情感的技术吧! 注: 光阴似水,人生若梦, ...

  7. C# WINFORM的自动更新程序

    自动更新程序AutoUpdate.exe https://git.oschina.net/victor596jm/AutoUpdate.git 1.获取源码 http://git.oschina.ne ...

  8. 游戏《Minecraft》或其他应用程序 实现 自动更新 客户端版本

    本渣又来写(水)博客了. 先说一下,我这个解决方案的安全性并不是企业级的,咱们就是一群穷开服的Minecraft玩家. 如果你要投入到企业级应用(容易被黑客攻击的场景),请自己写,思路凑合看看.不然安 ...

  9. 如何在程序中调用Caffe做图像分类

    Caffe是目前深度学习比较优秀好用的一个开源库,采样c++和CUDA实现,具有速度快,模型定义方便等优点.学习了几天过后,发现也有一个不方便的地方,就是在我的程序中调用Caffe做图像分类没有直接的 ...

随机推荐

  1. ACM算法--二分法--模板

    // 在单调递增序列a中查找>=x的数中最小的一个(即x或x的后继) while (l < r) { int mid = (l + r) / 2; if (a[mid] >= x) ...

  2. HDU Problem D [ Humble number ]——基础DP丑数序列

    Problem D Time Limit : 2000/1000ms (Java/Other) Memory Limit : 65536/32768K (Java/Other) Total Submi ...

  3. Codeforces 1291 Round #616 (Div. 2) B

    B. Array Sharpening time limit per test1 second memory limit per test256 megabytes inputstandard inp ...

  4. App 抓包代理设置

    1.设置 Fiddler 打开 Fiddler,Tools  -->  Fiddler Options  -->  HTPS (配置完后记得要重启 Fiddler) 选中  "D ...

  5. python json.dumps中ensure_ascii的使用,load与loads的区别

    json模块最常用的两个功能: 一:json.dumps(),用于将dict拆分成str格式,称为序列化,注意序列化后,虽然print出来仍然显示的字典的样子,但是此时已经是str类型了. 其中,有时 ...

  6. ln命令:软链接与硬链接的区别与应用

    ln命令:软链接与硬链接的区别与应用 摘要 Linux系统中,链接是一个十分常见且实用的文件处理命令,它分为软链接和硬链接两种类型.软链接类似于Windows中的快捷方式,硬链接又有着与原文件保持同步 ...

  7. POJ3169(差分约束:转载)

    转载自mengxiang000000传送门 Layout Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 10278   Ac ...

  8. WCF学习(二)

    WCF通道模型 绑定的本质是一个配置好的通道栈,为了方便程序员专著与业务逻辑,WCF提高了一系列常用绑定.随后会有相应的自定义通道栈代码 无论交互的另一方具体位置在哪里,WCF都会为消息的发送和接收建 ...

  9. STM32 CubeIDE快速创建工程(图文详解)

    使用STM32CubeIDE快速创建STM32的HAL库工程. 文章目录 1 STM32CubeIDE Home 2 生成工程 3 程序下载 1 STM32CubeIDE Home 进入到官网的下载界 ...

  10. 数据结构学习:二叉查找树的概念和C语言实现

    什么是二叉查找树? 二叉查找树又叫二叉排序树,缩写为BST,全称Binary Sort Tree或者Binary Search Tree. 以下定义来自百度百科: 二叉排序树或者是一棵空树,或者是具有 ...