实现.Net程序中OpenTracing采样和上报配置的自动更新
前言
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采样和上报配置的自动更新的更多相关文章
- 浅析 .Net Core中Json配置的自动更新
Pre 很早在看 Jesse 的Asp.net Core快速入门的课程的时候就了解到了在Asp .net core中,如果添加的Json配置被更改了,是支持自动重载配置的,作为一名有着严重" ...
- IIS如何避免子web应用程序中继承根目录web.config配置
1.一种方式,需要改动根目录的web.config(不是很推荐) <?xml version="1.0"?> <configuration> <loc ...
- 关于微信小程序中遇到的各种问题汇总(持续更新)
1.关于 <input />标签容易忽略的问题: 使用<input />标签时容易忘记绑定bindblur()方法(输入框失去焦点时触发),因为用户用键盘输入时不一定会点击完成 ...
- .net程序中http请求的超时配置
请求时的超时 // // 摘要: // 获取或设置 System.Net.HttpWebRequest.GetResponse() 和 System.Net.HttpWebRequest.GetReq ...
- Mysql中,update语句引起的时间戳自动更新问题
前几天遇到一个奇怪的问题. 在Mysql数据库中有一张表,表中有一个字段是timestamp类型的.我在update别的字段时,这个timestamp字段的时间会自动更新为当前时间. 后来发现,是My ...
- nw.js桌面程序自动更新(node.js表白记)
Hello Google Node.js 一个基于Google V8 的JavaScript引擎. 一个伟大的端至端语言,或许我对你的热爱源自于web这门极富情感的技术吧! 注: 光阴似水,人生若梦, ...
- C# WINFORM的自动更新程序
自动更新程序AutoUpdate.exe https://git.oschina.net/victor596jm/AutoUpdate.git 1.获取源码 http://git.oschina.ne ...
- 游戏《Minecraft》或其他应用程序 实现 自动更新 客户端版本
本渣又来写(水)博客了. 先说一下,我这个解决方案的安全性并不是企业级的,咱们就是一群穷开服的Minecraft玩家. 如果你要投入到企业级应用(容易被黑客攻击的场景),请自己写,思路凑合看看.不然安 ...
- 如何在程序中调用Caffe做图像分类
Caffe是目前深度学习比较优秀好用的一个开源库,采样c++和CUDA实现,具有速度快,模型定义方便等优点.学习了几天过后,发现也有一个不方便的地方,就是在我的程序中调用Caffe做图像分类没有直接的 ...
随机推荐
- spring源码阅读笔记10:bean生命周期
前面的文章主要集中在分析Spring IOC容器部分的原理,这部分的核心逻辑是和bean创建及管理相关,对于单例bean的管理,从创建好到缓存起来再到销毁,其是有一个完整的生命周期,并且Spring也 ...
- opencv基于PCA降维算法的人脸识别
opencv基于PCA降维算法的人脸识别(att_faces) 一.数据提取与处理 # 导入所需模块 import matplotlib.pyplot as plt import numpy as n ...
- 支付宝小程序serverless---插入数据后获取数据的主键_id(mongodb)
支付宝小程序serverless---插入数据后获取数据的主键_id(mongodb) 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除, ...
- Nginx+Uwsgi+Django 项目部署到服务器。
首先先说一下思路: 1.本地django项目打包 主要用到的是 python自带的distutils.core 下的 setup,具体代码在下面,主要讲的两个问题是package主要打包为和目录同级的 ...
- MySQL(二)MySQL中的存储引擎
前言 数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建.查询.更新和删除数据.不同的存储引擎提供不同的存储机制.索引技巧.锁定水平等功能,使用不同的存储引擎,还可以 ...
- Proteus传感器+气体浓度检测的报警方式控制仿真
Proteus传感器+气体浓度检测的报警方式控制仿真 目录 Proteus传感器+气体浓度检测的报警方式控制仿真 1 实验意义理解 2 主要实验器件 3 实验参考电路 4 实验中的问题思考 4.1 实 ...
- Java常用关键字总结
1.abstract abstract修饰类,表示抽象的意思,抽象类可以含有非抽象变量和成员变量,也可以有普通方法和构造方法,但是不能被实例化(接口),但是可以被子类继承. public abstra ...
- 移动端网站开发要点-meta设置
<!DOCTYPE html> <!-- 使用 HTML5 doctype,不区分大小写 --> <html lang="zh-cmn-Hans"&g ...
- 存储过程——公用表表达式(CTE)
目录 0. 背景说明 1. 定义及语法细节 1.1 基本定义 1.2 基本语法 1.3 多个CTE同时声明 1.4 CTE嵌套使用 2. CTE递归查询 2.1 简介 2.2 准备工作 2.3 计算每 ...
- webpack指南(三)缓存
缓存 把 /dist 目录中的内容部署到服务器上,客户端(通常是浏览器)就能够访问网站此服务器的网站及其资源.而通过网络获取资源是比较耗时的,这就是为什么浏览器要使用缓存这种技术.可以通过命中缓存,以 ...