<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Dotnet on Coding with Denglei</title>
    <link>https://blog.denglei.me/tags/dotnet/</link>
    <description>Recent content in Dotnet on Coding with Denglei</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Thu, 25 Jun 2026 23:06:22 +0800</lastBuildDate>
    <atom:link href="https://blog.denglei.me/tags/dotnet/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>.NET 10 使用 Microsoft.AspNetCore.OpenApi 实现 API 版本管理</title>
      <link>https://blog.denglei.me/posts/dotnet10-openapi-versioning/</link>
      <pubDate>Tue, 16 Jun 2026 17:04:23 +0800</pubDate>
      <guid>https://blog.denglei.me/posts/dotnet10-openapi-versioning/</guid>
      <description>&lt;h2 id=&#34;为什么-api-版本管理如此重要&#34;&gt;为什么 API 版本管理如此重要？&lt;/h2&gt;&#xA;&lt;p&gt;API 版本管理的核心目标是：在不破坏现有用户的前提下，持续迭代和改进 API。通过版本管理，我们可以：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;引入新功能：在新版本中添加字段、接口等，而不影响旧版本的用户。&lt;/li&gt;&#xA;&lt;li&gt;修复 bug：在新版本中修复问题，而不冒破坏旧版本的风险。&lt;/li&gt;&#xA;&lt;li&gt;逐步淘汰：在新版本中移除过时的功能，给用户足够的时间迁移。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;常见的版本策略有这几种：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;URL 路径版本：&lt;code&gt;/api/v1/users&lt;/code&gt;，直观，最常见&lt;/li&gt;&#xA;&lt;li&gt;查询参数版本：&lt;code&gt;/api/users?api-version=1.0&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;请求头版本：&lt;code&gt;X-API-Version: 1.0&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;媒体类型版本：&lt;code&gt;Accept: application/json; v=1.0&lt;/code&gt;（GitHub 在用这种方式）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;每种方式都有适用场景，没有绝对的优劣。&lt;/p&gt;&#xA;&lt;p&gt;在 C# 生态中，长期以来的事实标准是 &lt;code&gt;Swashbuckle.AspNetCore&lt;/code&gt;，但它并没有内置版本管理支持，需要配合 &lt;code&gt;Asp.Versioning&lt;/code&gt; 来实现。&lt;/p&gt;&#xA;&lt;p&gt;终于，在 .NET 10 中，微软推出了自己的 OpenAPI 库 &lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt;，并且 &lt;code&gt;Asp.Versioning&lt;/code&gt; v10 也正式支持了这个库，版本管理和文档生成终于可以无缝结合了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;上手-microsoftaspnetcoreopenapi-和-aspversioning&#34;&gt;上手 Microsoft.AspNetCore.OpenApi 和 Asp.Versioning&lt;/h2&gt;&#xA;&lt;p&gt;要使用 Microsoft.AspNetCore.OpenApi 和 Asp.Versioning 来实现 API 版本管理，首先需要安装相关 NuGet 包：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#package: Asp.Versioning.Http 10.0.0&#xA;#package: Asp.Versioning.Mvc 10.0.0&#xA;#package: Asp.Versioning.Mvc.ApiExplorer 10.0.0&#xA;#package: Microsoft.AspNetCore.OpenApi 10.0.0&#xA;#package: Scalar.AspNetCore 2.6.0&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;安装完成后，在 Program.cs 中进行如下配置：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddApiVersioning&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;options&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DefaultApiVersion&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ApiVersion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AssumeDefaultVersionWhenUnspecified&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ReportApiVersions&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ApiVersionReader&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;UrlSegmentApiVersionReader&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddMvc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddApiExplorer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;options&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GroupNameFormat&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#39;v&amp;#39;V&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SubstituteApiVersionInUrl&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddOpenApi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ShouldInclude&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;apiDescription&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;apiDescription&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GroupName&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddOpenApi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;v2&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ShouldInclude&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;apiDescription&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;apiDescription&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GroupName&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;v2&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;MapOpenApi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;MapScalarApiReference&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;options&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;WithTitle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Users API - {documentName}&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddDocuments&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;v2&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在上面的代码中，我们首先配置了 API 版本管理，指定了默认版本、版本读取方式等。然后，我们为每个版本配置了 OpenAPI 文档生成，确保每个版本都有独立的文档。最后，我们映射了 OpenAPI 和 Scalar API Reference 的路由。&lt;/p&gt;</description>
    </item>
    <item>
      <title>.NET 中 .Result 避坑指南：不同框架下的死锁与线程池饥饿</title>
      <link>https://blog.denglei.me/posts/dotnet-result-deadlock-threadpool-starvation/</link>
      <pubDate>Tue, 16 Jun 2026 17:04:23 +0800</pubDate>
      <guid>https://blog.denglei.me/posts/dotnet-result-deadlock-threadpool-starvation/</guid>
      <description>&lt;p&gt;这篇只讲一个知识点：在 .NET 代码里用 &lt;code&gt;.Result&lt;/code&gt;（或 &lt;code&gt;GetAwaiter().GetResult()&lt;/code&gt;）同步阻塞异步任务，为什么在不同框架下会触发不同类型的事故。&lt;/p&gt;&#xA;&lt;h2 id=&#34;问题背景&#34;&gt;问题背景&lt;/h2&gt;&#xA;&lt;p&gt;同样一行代码，在两个系统里出现了完全不同的故障：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;老系统（ASP.NET MVC 5）请求直接卡死，不返回&lt;/li&gt;&#xA;&lt;li&gt;新系统（ASP.NET Core）不是直接死锁，而是高峰期吞吐突然掉到很低，请求排队超时&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;两边都有这段写法：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GetData&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GetDataAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GetDataAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Delay&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;50&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;原理同一个坑两种后果&#34;&gt;原理：同一个坑，两种后果&lt;/h2&gt;&#xA;&lt;h3 id=&#34;场景-1aspnet-classic--winforms--wpf有-synchronizationcontext&#34;&gt;场景 1：ASP.NET Classic / WinForms / WPF（有 SynchronizationContext）&lt;/h3&gt;&#xA;&lt;p&gt;这类框架默认要求 continuation 回到原上下文（UI 线程或请求上下文）。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;.Result&lt;/code&gt; 先把当前线程阻塞住，Task 完成后 continuation 又想回到这条线程，结果互相等待：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;当前线程在 &lt;code&gt;.Result&lt;/code&gt; 处阻塞&lt;/li&gt;&#xA;&lt;li&gt;continuation 需要回到当前线程继续执行&lt;/li&gt;&#xA;&lt;li&gt;当前线程被阻塞，continuation 进不来&lt;/li&gt;&#xA;&lt;li&gt;死锁&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;所以你会看到&amp;quot;请求一直转圈&amp;quot;或&amp;quot;界面完全卡死&amp;quot;。&lt;/p&gt;&#xA;&lt;h3 id=&#34;场景-2aspnet-core默认无-synchronizationcontext&#34;&gt;场景 2：ASP.NET Core（默认无 SynchronizationContext）&lt;/h3&gt;&#xA;&lt;p&gt;在默认配置下，ASP.NET Core 没有传统的请求级 &lt;code&gt;SynchronizationContext&lt;/code&gt;，所以通常不会触发上面的经典互锁。&lt;/p&gt;&#xA;&lt;p&gt;它会把线程池工作线程同步阻塞住。并发一上来，越来越多线程被卡在 &lt;code&gt;.Result&lt;/code&gt;，线程池来不及补充，新请求拿不到线程，就出现线程饥饿：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;CPU 不一定高&lt;/li&gt;&#xA;&lt;li&gt;数据库不一定慢&lt;/li&gt;&#xA;&lt;li&gt;但接口耗时和超时数暴涨&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这就是&amp;quot;看起来不像死锁，但系统几乎不可用&amp;quot;的典型表现。&lt;/p&gt;&#xA;&lt;h2 id=&#34;最小对照示例&#34;&gt;最小对照示例&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;sealed&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;DemoService&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// ❌ 错误：同步包装异步&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GetNumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GetNumberAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// ✅ 正确：异步到底&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GetNumberAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Task&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Delay&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;42&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;如何避坑只保留最关键三条&#34;&gt;如何避坑（只保留最关键三条）&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;不要在任何业务调用链上使用 &lt;code&gt;.Result&lt;/code&gt; / &lt;code&gt;.Wait()&lt;/code&gt; / &lt;code&gt;GetAwaiter().GetResult()&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;API、Service、Repository 全链路改成 &lt;code&gt;async&lt;/code&gt;，不要做&amp;quot;同步方法包异步&amp;quot;。&lt;/li&gt;&#xA;&lt;li&gt;如果历史包袱必须保留同步签名，就让边界层同步，内部仍然异步，避免层层阻塞传染。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;一句结论&#34;&gt;一句结论&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;.Result&lt;/code&gt; 在老框架里更容易直接死锁，在 ASP.NET Core 里更容易演化成线程池饥饿；表现不同，本质相同，都是&amp;quot;阻塞等待异步&amp;quot;导致的。&lt;/p&gt;</description>
    </item>
    <item>
      <title>.NET 内存性能优化实战：Span&lt;T&gt;、ArrayPool、GC 与 LOH 控制</title>
      <link>https://blog.denglei.me/posts/dotnet-memory-optimization-span-arraypool-gc-loh/</link>
      <pubDate>Tue, 16 Jun 2026 17:04:23 +0800</pubDate>
      <guid>https://blog.denglei.me/posts/dotnet-memory-optimization-span-arraypool-gc-loh/</guid>
      <description>&lt;p&gt;很多服务在压测里吞吐不错，一上生产就抖。根因常常不是业务逻辑，而是内存分配模式不健康。&lt;/p&gt;&#xA;&lt;p&gt;典型表现：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;GC 频率偏高&lt;/li&gt;&#xA;&lt;li&gt;P99 周期性尖峰&lt;/li&gt;&#xA;&lt;li&gt;单机吞吐不稳定&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这篇文章重点讲“如何减少无意义分配”，并把优化控制在团队可维护范围内。&lt;/p&gt;&#xA;&lt;h2 id=&#34;问题背景&#34;&gt;问题背景&lt;/h2&gt;&#xA;&lt;p&gt;真实现场：一次常规版本上线后，接口平均耗时变化不大，但 P99 从 120ms 拉到 380ms，最后定位到的是字符串拼接和临时数组在高峰时段触发了更频繁的 GC。&lt;/p&gt;&#xA;&lt;p&gt;一个日志清洗或协议解析接口，经常会写出这样的代码：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;parts&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;line&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Split&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sc&#34;&gt;&amp;#39;|&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;user&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;parts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Trim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;action&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;parts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Trim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ts&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;DateTime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Parse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;parts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;逻辑简单，但每次请求都在制造短命对象。高并发下，这些小分配会形成大压力。&lt;/p&gt;&#xA;&lt;h2 id=&#34;原理解析&#34;&gt;原理解析&lt;/h2&gt;&#xA;&lt;h3 id=&#34;代际-gc&#34;&gt;代际 GC&lt;/h3&gt;&#xA;&lt;p&gt;短命对象主要在 Gen0/Gen1 回收。分配速率过高时，GC 会频繁触发，吞吐被打断。&lt;/p&gt;&#xA;&lt;h3 id=&#34;loh大对象堆&#34;&gt;LOH（大对象堆）&lt;/h3&gt;&#xA;&lt;p&gt;大于约 85KB 的对象进入 LOH。大对象频繁创建和释放，会导致更重的回收成本和内存碎片问题。&lt;/p&gt;&#xA;&lt;h3 id=&#34;spant-与-arraypoolt&#34;&gt;&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; 与 &lt;code&gt;ArrayPool&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; 在不额外分配的前提下切片处理内存&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;ArrayPool&amp;lt;T&amp;gt;&lt;/code&gt; 复用数组，避免重复 new 大缓冲区&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;示例代码&#34;&gt;示例代码&lt;/h2&gt;&#xA;&lt;p&gt;先定义记录：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;sealed&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;record&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;AuditLog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;User&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Action&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;DateTime&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Timestamp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;使用 &lt;code&gt;ReadOnlySpan&amp;lt;char&amp;gt;&lt;/code&gt; 解析：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TryParseAuditLog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;line&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;out&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;AuditLog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;log&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;ReadOnlySpan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;line&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AsSpan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;p1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IndexOf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sc&#34;&gt;&amp;#39;|&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;p1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;p2&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;p1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)..].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IndexOf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sc&#34;&gt;&amp;#39;|&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;p2&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;p2&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;p1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;user&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[..&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;p1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Trim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;action&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;p1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)..&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;p2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Trim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tsText&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;p2&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)..].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Trim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(!&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DateTime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TryParse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tsText&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;out&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;log&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;AuditLog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ToString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;action&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ToString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于大文本拼接，使用池化数组：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Microsoft Agent Framework &#43; Kimi API 实战：控制台应用跑通单次与多轮 Agent 对话</title>
      <link>https://blog.denglei.me/posts/microsoft-agent-framework-kimi-api-console/</link>
      <pubDate>Tue, 16 Jun 2026 17:04:23 +0800</pubDate>
      <guid>https://blog.denglei.me/posts/microsoft-agent-framework-kimi-api-console/</guid>
      <description>&lt;h2 id=&#34;引言&#34;&gt;引言&lt;/h2&gt;&#xA;&lt;p&gt;如果你的技术栈主要是 .NET，想要快速上手 Agent 开发，那么 Microsoft Agent Framework 是一个非常不错的选择。它提供了对话管理、上下文保持、工具调用等一系列功能，让你能专注于业务逻辑实现。&lt;/p&gt;&#xA;&lt;p&gt;这篇文章用一个最小控制台应用，通过 maf + kimi ai 合作，完成下面的目标：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;使用 Microsoft Agent Framework（NuGet 包名为 &lt;code&gt;Microsoft.Agents.AI&lt;/code&gt; 体系）&lt;/li&gt;&#xA;&lt;li&gt;使用 Kimi 的 OpenAI 兼容接口&lt;/li&gt;&#xA;&lt;li&gt;实现单次对话&lt;/li&gt;&#xA;&lt;li&gt;实现多轮对话（基于 Session 保留上下文）&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;你把代码复制后，只要配置好 &lt;code&gt;KIMI_API_KEY&lt;/code&gt; 就能跑起来。&lt;/p&gt;&#xA;&lt;h2 id=&#34;环境准备&#34;&gt;环境准备&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;.NET SDK 9.0+&lt;/li&gt;&#xA;&lt;li&gt;Kimi API Key&lt;/li&gt;&#xA;&lt;li&gt;一个控制台项目&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;创建项目并安装依赖：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;dotnet new console -n AgentConsoleApp&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; AgentConsoleApp&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;dotnet add package Microsoft.Agents.AI&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;dotnet add package Microsoft.Agents.AI.OpenAI&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;dotnet add package OpenAI&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;开发实战&#34;&gt;开发实战&lt;/h2&gt;&#xA;&lt;h3 id=&#34;一配置-kimi-api-的访问参数&#34;&gt;一、配置 Kimi API 的访问参数&lt;/h3&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;注意：在实际开发中，确保妥善保管 API 密钥，避免泄露。&#xA;我这里把密钥放在了环境变量中，代码中通过读取环境变量来获取密钥。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Serilog &#43; OpenTelemetry：从请求日志到链路追踪的关联落地</title>
      <link>https://blog.denglei.me/posts/serilog-opentelemetry-trace-correlation/</link>
      <pubDate>Tue, 16 Jun 2026 17:04:23 +0800</pubDate>
      <guid>https://blog.denglei.me/posts/serilog-opentelemetry-trace-correlation/</guid>
      <description>&lt;p&gt;这篇直接给落地方案，不再讲结构化日志的背景概念。目标只有一个：在 ASP.NET Core 服务里，把 Serilog 日志和 OpenTelemetry 链路追踪打通，排障时可以从一条错误日志直接跳到完整 Trace。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1-问题背景这篇要交付什么&#34;&gt;1. 问题背景：这篇要交付什么&lt;/h2&gt;&#xA;&lt;p&gt;按下面步骤做完，你会得到一条可执行排障链路：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;日志里稳定带上 &lt;code&gt;TraceId&lt;/code&gt;、&lt;code&gt;SpanId&lt;/code&gt;、&lt;code&gt;RequestPath&lt;/code&gt;、&lt;code&gt;RequestMethod&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;业务日志统一输出 &lt;code&gt;OrderId&lt;/code&gt;、&lt;code&gt;TenantId&lt;/code&gt; 这类主键字段&lt;/li&gt;&#xA;&lt;li&gt;下游 HTTP 调用失败时，日志可直接回跳到同一 &lt;code&gt;TraceId&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;现场排障固定为 3 步：查日志 -&amp;gt; 拿 TraceId -&amp;gt; 打开链路&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;这篇示例默认运行环境：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;.NET 8&lt;/li&gt;&#xA;&lt;li&gt;Serilog&lt;/li&gt;&#xA;&lt;li&gt;OpenTelemetry + OTLP&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;2-原理解析实施前约定与字段契约&#34;&gt;2. 原理解析：实施前约定与字段契约&lt;/h2&gt;&#xA;&lt;p&gt;先把约定定下来，后面配置才不会反复返工。&lt;/p&gt;&#xA;&lt;h3 id=&#34;21-平台层字段必须统一&#34;&gt;2.1 平台层字段（必须统一）&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;TraceId&lt;/code&gt;：整条请求链路唯一键&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;SpanId&lt;/code&gt;：当前节点唯一键&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;RequestPath&lt;/code&gt;：请求路径&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;RequestMethod&lt;/code&gt;：请求方法&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;StatusCode&lt;/code&gt;：响应码&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;22-业务层字段按场景补充&#34;&gt;2.2 业务层字段（按场景补充）&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;订单域：&lt;code&gt;OrderId&lt;/code&gt;、&lt;code&gt;CustomerId&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;多租户：&lt;code&gt;TenantId&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;外部依赖：&lt;code&gt;DependencyName&lt;/code&gt;、&lt;code&gt;DownstreamStatusCode&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;23-字段命名规则一次定死&#34;&gt;2.3 字段命名规则（一次定死）&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;统一使用 PascalCase&lt;/li&gt;&#xA;&lt;li&gt;同义字段只保留一个名字，比如只用 &lt;code&gt;TraceId&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;业务字段保持稳定，不随文案调整&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;3-示例代码按步骤落地&#34;&gt;3. 示例代码：按步骤落地&lt;/h2&gt;&#xA;&lt;h3 id=&#34;31-安装依赖&#34;&gt;3.1 安装依赖&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;Serilog.AspNetCore&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;Serilog.Sinks.Console&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;Serilog.Enrichers.Environment&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;OpenTelemetry.Extensions.Hosting&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;OpenTelemetry.Instrumentation.AspNetCore&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;OpenTelemetry.Instrumentation.Http&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;OpenTelemetry.Exporter.OpenTelemetryProtocol&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;32-第一步配置-serilog-与-opentelemetry&#34;&gt;3.2 第一步：配置 Serilog 与 OpenTelemetry&lt;/h3&gt;&#xA;&lt;p&gt;把这段放进 &lt;code&gt;Program.cs&lt;/code&gt;，先打通基础链路：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Serilog：从结构化日志认知到 .NET 工程落地</title>
      <link>https://blog.denglei.me/posts/serilog-structured-logging-sink-enricher/</link>
      <pubDate>Tue, 16 Jun 2026 17:04:23 +0800</pubDate>
      <guid>https://blog.denglei.me/posts/serilog-structured-logging-sink-enricher/</guid>
      <description>&lt;h2 id=&#34;问题背景&#34;&gt;问题背景&lt;/h2&gt;&#xA;&lt;p&gt;很多项目不缺日志，缺的是有用的日志。&lt;/p&gt;&#xA;&lt;p&gt;平时接口跑得顺，大家都觉得日志够用。真到线上出问题，日志的短板会一下子暴露出来。&lt;/p&gt;&#xA;&lt;p&gt;比如订单接口偶发超时，日志里只剩这么一句：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Create order failed for customer 1024, cost 3800ms, trace abc123&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种日志平时看着还行，真到线上排障时基本帮不上太多忙：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;你没法直接按 CustomerId、TraceId、ElapsedMs 做过滤和聚合，只能全文搜索&lt;/li&gt;&#xA;&lt;li&gt;同一个字段今天写 customerId，明天写 userId，后天又换成 client_id，查询条件根本沉淀不下来&lt;/li&gt;&#xA;&lt;li&gt;想统计某类错误的数量、平均耗时、失败占比，往往还得写额外脚本二次清洗&lt;/li&gt;&#xA;&lt;li&gt;日志平台能做的分析能力很弱，因为它拿到的只是一段文本，不是一条可分析的数据&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;问题往往不在于日志打得不够多，而在于日志从一开始就没按可检索、可聚合、可关联的方式去设计。&lt;/p&gt;&#xA;&lt;p&gt;传统文本日志更像写给人看的备注，结构化日志才像写给系统消费的数据。业务一旦进入多人协作、线上排障、统一观测这些阶段，日志就不再只是打出来看一眼，而是排障、审计、告警、指标补充、链路追踪的一部分。走到这一步，结构化日志就不是锦上添花，而是该补的基础课。&lt;/p&gt;&#xA;&lt;h2 id=&#34;原理解析&#34;&gt;原理解析&lt;/h2&gt;&#xA;&lt;h3 id=&#34;什么是结构化日志&#34;&gt;什么是结构化日志&lt;/h3&gt;&#xA;&lt;p&gt;很多人第一次接触结构化日志，会下意识把重点放在 JSON 输出上。其实 JSON 只是表现形式，核心不在日志长什么样，而在日志里的字段有没有明确语义，后续能不能被系统稳定识别。&lt;/p&gt;&#xA;&lt;p&gt;先看两种写法的差异。&lt;/p&gt;&#xA;&lt;p&gt;普通文本日志：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;logger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;LogInformation&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s&#34;&gt;$&amp;#34;Order {order.Id} created for customer {order.CustomerId}, amount {order.Amount}, cost {elapsedMs}ms&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这行代码最终只会产出一段字符串。人类看没问题，但日志平台拿到以后，并不知道哪一段是订单号，哪一段是金额，哪一段是耗时。&lt;/p&gt;&#xA;&lt;p&gt;结构化日志：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;logger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;LogInformation&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s&#34;&gt;&amp;#34;Order {OrderId} created for customer {CustomerId}, amount {Amount}, cost {ElapsedMs}ms&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CustomerId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;elapsedMs&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;换成这种写法以后，日志框架记录的就不再是一整段普通字符串，而是一个日志事件：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;模板是 Order {OrderId} created for customer {CustomerId}, amount {Amount}, cost {ElapsedMs}ms&lt;/li&gt;&#xA;&lt;li&gt;属性是 OrderId、CustomerId、Amount、ElapsedMs&lt;/li&gt;&#xA;&lt;li&gt;元数据还包括时间、级别、异常、来源、线程、TraceId 这些上下文&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;输出到控制台时，它可以渲染成人能直接看的句子；送到 Elasticsearch、Seq、Loki 或 OpenTelemetry 后端时，它又能以字段化数据的形式被检索和聚合。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
