惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
Forbes - Security
Forbes - Security
WordPress大学
WordPress大学
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
Spread Privacy
Spread Privacy
D
Darknet – Hacking Tools, Hacker News & Cyber Security
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
P
Privacy International News Feed
A
About on SuperTechFans
T
Tailwind CSS Blog
I
InfoQ
S
Securelist
云风的 BLOG
云风的 BLOG
罗磊的独立博客
Recent Announcements
Recent Announcements
T
The Exploit Database - CXSecurity.com
B
Blog RSS Feed
V
Visual Studio Blog
Know Your Adversary
Know Your Adversary
The GitHub Blog
The GitHub Blog
Jina AI
Jina AI
腾讯CDC
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
博客园 - 【当耐特】
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Full Disclosure
S
Secure Thoughts
博客园 - 司徒正美
J
Java Code Geeks
Y
Y Combinator Blog
Google Online Security Blog
Google Online Security Blog
GbyAI
GbyAI
N
News and Events Feed by Topic
Help Net Security
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Project Zero
Project Zero
T
Tenable Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Tor Project blog
MyScale Blog
MyScale Blog
Scott Helme
Scott Helme
小众软件
小众软件
K
Kaspersky official blog

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角 .NET Core 原生 DI 扩展之基于名称的注入实现 - 元视角
利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角
飞鸿踏雪 · 2022-04-07 · via 元视角

在此之前,我曾写过一篇博客,《Envoy 集成 Jaeger 实现分布式链路追踪》,主要分享了 ASP.NET Core 应用如何结合 EnvoyJeager 来实现分布式链路追踪,其核心思想是:生成一个全局唯一的 x-request-id ,并在不同的微服务或者子系统中传播该信息。进而,可以使得相关的信息像一条线上的珠子一样串联起来。在此基础上,社区主导并产生了 OpenTracing 规范,在这个 规范 中,一个 Trace,即调用链,是由多个 Span 组成的有向无环图,而每个 Span 则可以含有多个键值对组成的 Tag。不过,当时我们有一个非常尴尬的问题,那就是每个微服务必须显式地传递相关的 HTTP 请求头。那么,是否有一种更优雅的方案呢?而这就是我们今天要分享的内容。首先,我们来回头看看当初的方案,这是一个非常朴实无华的实现:

[HttpPost]
public async Task<IActionResult> Post([FromBody] OrderInfo orderInfo)
{
  var paymentInfo = new PaymentInfo()
  {
    OrderId = orderInfo.OrderId,
    PaymentId = Guid.NewGuid().ToString("N"),
    Remark = orderInfo.Remark,
  };

  // 设置请求头
  _httpClient.DefaultRequestHeaders.Add(
      "x-request-id", Request.Headers["x-request-id"].ToString());
  _httpClient.DefaultRequestHeaders.Add(
      "x-b3-traceid", Request.Headers["x-b3-traceid"].ToString());
  _httpClient.DefaultRequestHeaders.Add(
      "x-b3-spanid", Request.Headers["x-b3-spanid"].ToString());
  _httpClient.DefaultRequestHeaders.Add(
      "x-b3-parentspanid", Request.Headers["x-b3-parentspanid"].ToString());
  _httpClient.DefaultRequestHeaders.Add(
      "x-b3-sampled", Request.Headers["x-b3-sampled"].ToString());
  _httpClient.DefaultRequestHeaders.Add(
      "x-b3-flags", Request.Headers["x-b3-flags"].ToString());
  _httpClient.DefaultRequestHeaders.Add(
      "x-ot-span-context", Request.Headers["x-ot-span-context"].ToString());

  // 调用/Payment接口
  var payload = JsonConvert.SerializeObject(paymentInfo)
  var content = new StringContent(payload, Encoding.UTF8, "application/json");
  var response = await _httpClient.PostAsync("/Payment", content);

  var result = response.IsSuccessStatusCode ? "成功" : "失败";
  return new JsonResult(new { Msg = $"订单创建{result}" });
}

这里,最大的问题是,传递 HTTP 请求头的代码片段对正常的业务代码存在入侵,当别人调用某个微服务或者子系统的接口时,必须要加上这些代码片段,这实在是一件难受的事情。子曰,“己所不欲,勿施于人”,如果一段代码,你自己都感觉看不下去,那就说明这代码该重构啦!下面,我们考虑对其进行重构。首先,通过 NuGet 安装一个微软提供的包:

dotnet add package Microsoft.AspNetCore.HeaderPropagation

接下来,我们在 Startup 中对 HeaderPropagation 进行简单配置:

services.AddHeaderPropagation(options =>
{
    // 如果请求头中含 X-BetaFeatures 字段,则传播该字段对应的值
    options.Headers.Add("X-BetaFeatures");

    // 如果请求头中不含 X-BetaFeatures 字段,则生成一个新的值并进行传播
    // 注意,这里以 GUID 为例
    options.Headers.Add("X-BetaFeatures", context =>
    {
        return Guid.NewGuid().ToString("N");
    });
});

那么,这些请求头会传播到哪里呢?答案是 HttpClient,所以,你可以想到,不管是 RESTful 风格的 API 还是 gRPC 都可以享受到这一便利:

// 传播所有注册过的请求头,如 X-BetaFeatures 
services
    .AddHttpClient("Ezio")
    .AddHeaderPropagation();

// 仅传播指定的请求头,如 X-BetaFeatures、X-Experiments
services
    .AddHttpClient("Altaïr")
    .AddHeaderPropagation(options => {
        options.Headers.Add("X-BetaFeatures", "X-Experiments");
    });

最后,我们还需要在请求管道中启用相应的中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...
    // 注意:请放置在 app.UseRouting(); 前面
    app.UseHeaderPropagation();

    // ...
}

对于我们而言,我们需要让 Jeager 相关的请求头传播下去,因此,我们只需要像下面这样改造即可。当然,这些请求头会由 Envoy 自动生成,所以,我们同样不需要考虑它不存在的情况:

services.AddHeaderPropagation(opt =>
{
    opt.Headers.Add("x-request-id");
    opt.Headers.Add("x-b3-traceid");
    opt.Headers.Add("x-b3-spanid");
    opt.Headers.Add("x-b3-parentspanid");
    opt.Headers.Add("x-b3-sampled");
    opt.Headers.Add("x-b3-flags");
    opt.Headers.Add("x-ot-span-context");
});

services.AddHttpClient("PaymentService", client =>
{
    client.BaseAddress = new Uri("http://127.0.0.1:9090");
})
.AddHeaderPropagation();

改造后的效果如何呢?博主表示,一切非常完美!

It&rsquo;s Amazing! It&rsquo;s Amazing!

关于这个中间件内部是如何运作的,大家可以阅读它的 源代码,博主这里画了一个简单的示意图来辅助说明:

HeaderPropagation 中间件示意图 HeaderPropagation 中间件示意图

可以注意到,这个中间件内部会维护一个叫做 HeaderPropagationValues 的对象实例,其生命周期为 Singleton,当有入站请求产生时,它会尝试从 HttpContext 中读取指定的请求头,并保存到 HeaderPropagationValues 实例的 Headers 属性中中。当我们注入 HttpClient 的时候,中间件内部会创建一个 HeaderPropagationMessageHandler 实例,它继承自 DelegatingHandler。如果你看过我以前的文章,《使用 HttpMessageHandler 实现 HttpClient 请求管道自定义》,相信你会在电光火石间明白我在说什么。总而言之,通过这个 Handler,你就可以把保存下来的请求头添加到 HttpClient 的实例上,相当于我们一开始手动设置请求头的这个环节,这样,这些请求头就可以“自动”传播下去啦!

通过 HeaderPropagation 中间件传递请求头字段 通过 HeaderPropagation 中间件传递请求头字段

其实,除了这个分布式链路追踪的场景,更一般的场景,或许是认证的场景。譬如,客户端通过认证服务拿到了一个令牌,它在向后端发起请求的时候,会把这个令牌添加到请求头中。此时,我们只需要确保所有后端服务都配置了这个中间件,令牌会随着调用链路一路传播下去,这样,是不是比每个服务间都相互协商如何传递身份信息要好的多呢?我想,这是毫无疑问的,做正确的事情永远比单纯的做事情要重要得多。好啦,以上就是这篇博客的全部内容啦,如果大家对博客内容有任何意见或者建议,欢迎大家在评论区留言,谢谢大家!