AsyncLocal<T>在链路追踪中的应用
前言
在项目生产中日志的记录是必不可少的,在.net项目中,要说日志组件,log4net
绝对可有一席之地,随着公司业务的发展,微服务则必定无可避免。在跨服务中通过日志进行分析性能或者排查故障点,如何快速定位日志尤为关键。链路追踪技术的出现正是解决这些痛点的。
分布式链路追踪需要收集单次请求所经过的所有服务,而且为了知道请求细节,还需要将具体的业务日志进行串联,而这一切的基础就是要通过一个traceid
从头传到尾,相当于将该次请求过程产生的所有日志都关联其traceid
,事后排查问题只需要知道traceid
,就可以在日志中拉出与之关联的所有日志。
当然不是所有的公司都需要链路追踪,对于一些小公司,就几个单体系统,压根不需要这些。比如我们使用log4net
时,会在日志模板中加入ThreadId
,例如这样的模板
"%date [%thread] %-5level - %message%newline"
虽然并发高时我们多个用户的请求日志都掺杂在一起,但是我们依然可以根据线程号
将该次请求的日志进行串联。这在大多时候都很好的解决了我们的问题。
老传统做法
即使在体量不大的系统中上面的线程号很好用了,但是哪有一点不用多线程的业务场景呢,当一次请求进来后可能会开多个异步线程去执行,那上面的线程号就显得力不从心了,就是说没法一下将相干日志提取出来了。
但是这难不倒我们,我们可以在业务开始时自定义一个随便字符串作为该次请求的唯一标识,然后将该变量通过参数传给下游方法,下游方法也将其一层一层接力传下去,在打印日志时都将该字段进行输出,这个办法很多人都用过吧。
AspNetCore的TraceIdentifier
难道没有一种优雅的方式能将我们某次请求的过程(包括多线程)进行串联起来的唯一标识吗?
在ASPNetCore
中其实一直有个不起眼的属性HttpContext.TraceIdentifier
,可以说他就是框架给我们提供的traceid
,我们可以在所需要的地方都注入HttpContext
来获取该参数,当然不许那么麻烦,只需要给日志组件获取到该值,在任何leave的日志输出时日志组件将其输出即可,这个完全没问题,大家可以去深入研究,有些日志组件可以直接配置就可以输出该TraceIdentifier
值到每一条日志中,也可以将其使用到跨应用调用时传递到下游服务,如http请求可以通过header携带该值,下游从header中获取并作为它自己的TraceIdentifier
继续传递。
AsyncLocal在链路追踪的应用
ThreadLoacl
倒是熟悉,是每个线程之间隔离的,每个线程操作的都是自己线程的对象,能做到各个线程或不影响。AsyncLocal
并不是一个新特性,只是用的场景不多,很少被使用
定义
Represents ambient data that is local to a given asynchronous control flow, such as an asynchronous method.
表示对于给定异步控制流(如异步方法)是本地数据的环境数据。
示例
using System;
using System.Threading;
using System.Threading.Tasks;
class Example
{
static AsyncLocal<string> _asyncLocalString = new AsyncLocal<string>();
static ThreadLocal<string> _threadLocalString = new ThreadLocal<string>();
static async Task AsyncMethodA()
{
// Start multiple async method calls, with different AsyncLocal values.
// We also set ThreadLocal values, to demonstrate how the two mechanisms differ.
_asyncLocalString.Value = "Value 1";
_threadLocalString.Value = "Value 1";
var t1 = AsyncMethodB("Value 1");
_asyncLocalString.Value = "Value 2";
_threadLocalString.Value = "Value 2";
var t2 = AsyncMethodB("Value 2");
// Await both calls
await t1;
await t2;
}
static async Task AsyncMethodB(string expectedValue)
{
Console.WriteLine("Entering AsyncMethodB.");
Console.WriteLine(" Expected '{0}', AsyncLocal value is '{1}', ThreadLocal value is '{2}'",
expectedValue, _asyncLocalString.Value, _threadLocalString.Value);
await Task.Delay(100);
Console.WriteLine("Exiting AsyncMethodB.");
Console.WriteLine(" Expected '{0}', got '{1}', ThreadLocal value is '{2}'",
expectedValue, _asyncLocalString.Value, _threadLocalString.Value);
}
static async Task Main(string[] args)
{
await AsyncMethodA();
}
}
// The example displays the following output:
// Entering AsyncMethodB.
// Expected 'Value 1', AsyncLocal value is 'Value 1', ThreadLocal value is 'Value 1'
// Entering AsyncMethodB.
// Expected 'Value 2', AsyncLocal value is 'Value 2', ThreadLocal value is 'Value 2'
// Exiting AsyncMethodB.
// Expected 'Value 2', got 'Value 2', ThreadLocal value is ''
// Exiting AsyncMethodB.
// Expected 'Value 1', got 'Value 1', ThreadLocal value is ''
简单理解,就是对该变量赋值后,之影响自己个自己的子线程,即当前线程发起的其他线程,包括线程池中的线程,都能获取到该值,而子线程修改该值,对父线程来说是无影响的。而这种特性貌似就是我们寻找那种能够优雅标记出同一次请求的特性。定义一个全局变量,在每次请求的起点对该变量赋值一个随机字符串,然后本次请求涉及到的所有线程访问该值,都是我们在入口赋的值。
项目应用
我们可以在任意地方定义一个全局变量,最好是放到LogHelper之中
AspNet4
public static class LogHelper{
public static AsyncLocal<string> Traceid = new AsyncLocal<string>();
...
}
在授权过滤器中对该值进行赋值,一般授权过滤最先执行,可作为请求的入口点
LogHelper.TraceId.Value = Guid.NewGuid().ToString();
在log4net
的LogHelper中使用,日志模板为
"%date [%property{trace}] [%thread] %-5level - %message%newline"
public static void Info(object message)
{
ThreadContext.Properties["trace"] = TraceId.Value;
Loger.Info(message);
}
...
AspNetCore
注册中间件进行设置值,将自己的中间件注册靠前点
app.Use(delegate (HttpContext ctx, RequestDelegate next)
{
LogHelper.TraceId.Value = ctx.TraceIdentifier;
return next(ctx);
});
经验证与预期符合,该实现方式不依赖AspnetCore框架HttpContext.TraceIdentifier
,提供一种实现链路追踪中传递TraceId
的一种思路,如有不正确之处欢迎指正,如果该思路对您有帮助,请点赞分享。
AsyncLocal<T>在链路追踪中的应用的更多相关文章
- Jaeger Client Go 链路追踪|入门详解
目录 从何说起 Jaeger 部署 Jaeger 从示例了解 Jaeger Client Go 了解 trace.span tracer 配置 Sampler 配置 Reporter 配置 分布式系统 ...
- 基于Dapper的分布式链路追踪入门——Opencensus+Zipkin+Jaeger
微信搜索公众号 「程序员白泽」,进入白泽的编程知识分享星球 最近做了一些分布式链路追踪有关的东西,写篇文章来梳理一下思路,或许可以帮到想入门的同学.下面我将从原理到demo为大家一一进行讲解,欢迎评论 ...
- 原理分析dubbo分布式应用中使用zipkin做链路追踪
zipkin是什么 Zipkin是一款开源的分布式实时数据追踪系统(Distributed Tracking System),基于 Google Dapper的论文设计而来,由 Twitter 公司开 ...
- 在spring boot中三分钟上手apache顶级分布式链路追踪系统skywalking
原文:https://juejin.im/post/5cd10e81e51d453b560f2d53 skywalking在apache里全票通过成为了apache顶级链路追踪系统 项目地址:gith ...
- 原理分析dubbo分布式应用中使用zipkin做链路追踪(转)
作者:@nele本文为作者原创,转载请注明出处:https://www.cnblogs.com/nele/p/10171794.html 目录 zipkin是什么为什么使用Zipkinzipkin架构 ...
- .NET Core 中的日志与分布式链路追踪
目录 .NET Core 中的日志与分布式链路追踪 .NET Core 中的日志 控制台输出 非侵入式日志 Microsoft.Extensions.Logging ILoggerFactory IL ...
- 基于zipkin分布式链路追踪系统预研第一篇
本文为博主原创文章,未经博主允许不得转载. 分布式服务追踪系统起源于Google的论文“Dapper, a Large-Scale Distributed Systems Tracing Infras ...
- zipkin分布式链路追踪系统
基于zipkin分布式链路追踪系统预研第一篇 分布式服务追踪系统起源于Google的论文“Dapper, a Large-Scale Distributed Systems Tracing Inf ...
- .NET Core----zipkin链路追踪使用
本文主要是说明core怎么使用链路追踪 一.添加nuget包 二.在Startup中添加配置 /// <summary> /// 注册zipkinTrace /// </summar ...
随机推荐
- Spring两种注入方式
1.XML注入 2.标签注入
- Myql 中的事务回滚机制概述 ?
事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个 不可分割的工作单位,事务回滚是指将该事务已经完成的对数据库的更新操作撤 销. 要同时修改数据库中两个不同表时,如果它们不是一个事 ...
- Redis的安装与启动(一)
Redis是c语言开发的. 安装redis需要c语言的编译环境.如果没有gcc需要在线安装.yum install gcc-c++ 安装步骤: 第一步:redis的源码包上传到linux系统.--源码 ...
- 请说出作用域public,private,protected,以及不写时的区别?
这四个作用域的可见范围如下表所示.说明:如果在修饰的元素上面没有写任何访问修饰符,则表示friendly.作用域 当前类 同一package 子孙类 其他packagepublic ...
- String 和 StringBuilder、StringBuffer 的区别?
Java 平台提供了两种类型的字符串:String 和 StringBuffer/StringBuilder,它 们可以储存和操作字符串.其中 String 是只读字符串,也就意味着 String 引 ...
- SpringDataJpa使用审计(Auditing)功能
SpringBoot项目使用SpringDataJpa提供的审计功能的使用流程 SpringDataJpa提供审计注解:@CreatedBy,@LastModifiedBy,@CreatedDate, ...
- MySQL怎么用命令修改字段名
一.SQL MySQL怎么用命令修改字段名的 -- alter table 表名 change 旧属性 新属性 新的数据类型 ==>(可以不写) COMMENT '备注' ALTER TABLE ...
- 详解Mysql事务隔离级别与锁机制
一.概述 我们的数据库一般都会并发执行多个事务,多个事务可能会并发的对相同的一批数据进行增删改查操作,可能 就会导致我们说的脏写. 胀读和不可重复读.幻读这些问题. 这些问题的本质都是数据库的多事务并 ...
- MTK平台电路设计01
一.资料 获取途径MTK官网.一牛网 二.
- SQL之创建表
1.创建表------ (方法一)-------create table Persons(id NUMBER, age NU ...