开发者社区> 推荐码发放> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

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

简介:
+关注继续查看

实现.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(1);
            }
        }
        catch (System.Exception ex)
        {
            _logger.LogWarning(ex, "Updating sampler failed");
            _metrics.SamplerQueryFailure.Inc(1);
        }
        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(1);
            }
        }
        catch (System.Exception ex)
        {
            _logger.LogWarning(ex, "Updating reporter failed");
            _metrics.ReporterFailure.Inc(1);
        }
        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大概是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
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中还需要自己搞,这个方法会单独写一篇文章,这里就不做介绍了。

原文地址https://www.cnblogs.com/bossma/p/dotnet-opentracing-sampler-reporter-remote-configuration-autoupdate.html

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
程序环境和预处理
程序环境和预处理
4 0
【实战】使用 uniapp 开发一个面试刷题小程序
直至 5 月也依然是互联网寒冬,大厂裁员的消息在微信群漫天飞舞,而招聘网站上的 HC 也越来也少,因此不少小厂也开始纷纷内卷,压低员工绩效,本应该晋级加薪的时间,也变成了杳无音信。难道我们就束手无策了
578 0
「镁客早报」SpaceX载人龙飞船成功返回地球;阿里回应马云退出传闻
载人版“龙”飞船的成功返回,意味着美国终于恢复了发射宇航员到太空的能力。
340 0
《Orange’s 一个操作系统的实现》1.搭建操作系统开发环境
书中给出了两种环境:windows和linux,平台选择根据自己喜好.本人这里选择ubuntu10.04+virtualbox作为开发平台.     1.下载、安装VirtualBox              http://download.
967 0
32位程序对64位进程的远程注入实现
本文讲的是32位程序对64位进程的远程注入实现,要对指定进程进行远程注入,通常使用Windows提供的API CreateRemoteThread创建一个远程线程,进而注入dll或是执行shellcode。
2141 0
StringBuilder的Length和Capacity属性实践
几天前的一次笔试中遇到了关于StringBuilder的Length和Capacity属性的问题,之前忽略了。今天实践下: 1.普通实践 官方解释如下: StringBuilder.Capacity:获取或设置可包含在当前实例所分配的内存中的最大字符数。
693 0
实现自动脱壳被加密的Net程序集
本次脱壳的测试对象是CodeLib 2 V14.9.2468.42911 更新日期是2006-10-5 目前的最新版本。运行脱机程序到如下界面:选中列表中的第二项,codelib.exe,选择一个保存路径,点击dump按钮,即可完成脱壳。
732 0
+关注
推荐码发放
阿里云优惠码阿里云推荐券bieryun.com
381
文章
5
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载