前言
最近我们公司的部分. NET Core 的项目接入了 Jaeger, 也算是稍微完善了一下. NET 团队的技术栈.
至于为什么选择 Jaeger 而不是 Skywalking, 这个问题我只能回答, 大佬们说了算.
前段时间也在 CSharpCorner 写过一篇类似的介绍
Exploring Distributed Tracing Using ASP.NET Core And Jaeger.
下面回到正题, 我们先看一下 Jaeger 的简介
Jaeger 的简单介绍
Jaeger 是 Uber 开源的一个分布式追踪的工具, 主要为基于微服务的分布式系统提供监测和故障诊断. 包含了下面的内容
- Distributed context propagation
- Distributed transaction monitoring
- Root cause analysis
- Service dependency analysis
- Performance / latency optimization
下面就通过一个简单的例子来体验一下.
示例
在这个示例的话, 我们只用了 jaegertracing/all-in-one 这个 docker 的镜像来搭建, 因为是本地的开发测试环境, 不需要搭建额外的存储, 这个感觉还是比较贴心的.
我们会用到两个主要的 nuget 包
Jaeger 这个是官方的 client
OpenTracing.Contrib.NetCore.Unofficial 这个是对. NET Core 探针的处理, 从 opentracing-contrib/csharp-netcore 这个项目移植过来的 (这个项目并不活跃, 只能自己做扩展)
然后我们会建两个 API 的项目, 一个是 AService, 一个是 BService.
其中 BService 会提供一个接口, 从缓存中读数据, 如果读不到就通过 EF Core 去从 SQLite 中读, 然后写入缓存, 最后再返回结果.
AService 会通过 HttpClient 去调用 BService 的接口, 从而会形成调用链.
开始之前, 我们先把 docker-compose.YAML 配置一下
- version: '3.4'
- services:
- aservice:
- image: ${DOCKER_REGISTRY-}aservice
- build:
- context: .
- dockerfile: AService/Dockerfile
- ports:
- - "9898:80"
- depends_on:
- - jagerservice
- - bservice
- networks:
- backend:
- bservice:
- image: ${DOCKER_REGISTRY-}bservice
- build:
- context: .
- dockerfile: BService/Dockerfile
- ports:
- - "9899:80"
- depends_on:
- - jagerservice
- networks:
- backend:
- jagerservice:
- image: jaegertracing/all-in-one:latest
- environment:
- - COLLECTOR_ZIPKIN_HTTP_PORT=9411
- ports:
- - "5775:5775/udp"
- - "6831:6831/udp"
- - "6832:6832/udp"
- - "5778:5778"
- - "16686:16686"
- - "14268:14268"
- - "9411:9411"
- networks:
- backend:
- networks:
- backend:
- driver: bridge
然后就在两个项目的 Startup 加入下面的一些配置, 主要是和 Jaeger 相关的.
- public void ConfigureServices(IServiceCollection services)
- {
- // others ....
- // Adds opentracing
- services.AddOpenTracing();
- // Adds the Jaeger Tracer.
- services.AddSingleton<ITracer>(serviceProvider =>
- {
- string serviceName = serviceProvider.GetRequiredService<IHostingEnvironment>().ApplicationName;
- var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
- var sampler = new ConstSampler(sample: true);
- var reporter = new RemoteReporter.Builder()
- .WithLoggerFactory(loggerFactory)
- .WithSender(new UdpSender("jagerservice", 6831, 0))
- .Build();
- var tracer = new Tracer.Builder(serviceName)
- .WithLoggerFactory(loggerFactory)
- .WithSampler(sampler)
- .WithReporter(reporter)
- .Build();
- GlobalTracer.Register(tracer);
- return tracer;
- });
- }
这里需要注意的是我们要根据情况来选择 sampler, 演示这里用了最简单的 ConstSampler.
回到 BService 这个项目, 我们添加 SQLite 和 EasyCaching 的相关支持.
- public void ConfigureServices(IServiceCollection services)
- {
- // Adds an InMemory-SQLite DB to show EFCore traces.
- services
- .AddEntityFrameworkSqlite()
- .AddDbContext<BDbContext>(options =>
- {
- var connectionStringBuilder = new SqliteConnectionStringBuilder
- {
- DataSource = ":memory:",
- Mode = SqliteOpenMode.Memory,
- Cache = SqliteCacheMode.Shared
- };
- var connection = new SqliteConnection(connectionStringBuilder.ConnectionString);
- connection.Open();
- connection.EnableExtensions(true);
- options.UseSqlite(connection);
- });
- // Add EasyCaching Inmemory provider.
- services.AddEasyCaching(options =>
- {
- options.UseInMemory("m1");
- });
- }
然后控制器上面就比较简单了.
- // GET API/values
- [HttpGet]
- public async Task<IActionResult> GetAsync()
- {
- var provider = _providerFactory.GetCachingProvider("m1");
- var obj = await provider.GetAsync("mykey", async () => await _dbContext.DemoObjs.ToListAsync(), TimeSpan.FromSeconds(30));
- return Ok(obj);
- }
AService 就是通过 HttpClient 去调用上面的这个接口即可.
- // GET API/values
- [HttpGet]
- public async Task<string> GetAsync()
- {
- var res = await GetDemoAsync();
- return res;
- }
- private async Task<string> GetDemoAsync()
- {
- var client = _clientFactory.CreateClient();
- var request = new HttpRequestMessage
- {
- Method = HttpMethod.Get,
- RequestUri = new Uri($"http://bservice/api/values")
- };
- var response = await client.SendAsync(request);
- response.EnsureSuccessStatusCode();
- var body = await response.Content.ReadAsStringAsync();
- return body;
- }
到这里的话, 代码这块是 ok 了, 下面就来看看效果.
先通过 http://localhost:9898/API/values / 访问几次 AService
大概能得到一个这样的结果
然后去 Jaeger 的界面上我们可以看到, 两个服务已经注册上来了.
选 A,B 其中一个去搜索, 就可以看到下面的结果
这个就最外层, 能看到这些请求一些宏观的信息.
我们选界面上最后一个, 也就是第一个请求, 进去看看细节
从上面这个图大概也能看出来, 做了一些什么操作, 请求来到 AService, 它就发起了 HTTP 请求到 BService,BService 则是先通过 EasyCaching 去取缓存, 显然缓存中没数据, 它就去读数据库了.
和另外的请求对比一下, 可以发现是少了查数据库这一步操作的. 这也是为什么上面的是 10 个 span, 而下面的才 8 个.
再来看看两个请求的对比图.
上图中那些红色和绿色的块就是两个请求的差异点了.
回去看看其他细节, 可以发现类似下面的内容
有很多日志相关的东西, 这些东西在这里可能没有太多实际的作用, 我们可以通过调整日志的级别来不让它写入到 Jaeger 中.
或者是通过下面的方法来过滤
- services.AddOpenTracing(new System.Collections.Generic.Dictionary<string,LogLevel>
- {
- {"AService", LogLevel.Information}
- });
最后就是依赖图了.
写在最后
虽说 Jaeger 用起来挺简单的, 但是也是有点美中不足的, 不过这个锅不应该是 Jaeger 来背的, 主要还是很多我们常用的库没有直接的支持 Diagnostic, 所以能监控到的东西还是略少.
不过在 GitHub 发现了 https://github.com/caozhiyuan/ClrProfiler.Trace 这个项目, 可以通过 clrprofiler 来解决上面的问题.
最后是本文的示例代码
总结
来源: https://www.jb51.net/article/159178.htm