<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Truman</title>
    <description>The latest articles on Forem by Truman (@truman_999999999).</description>
    <link>https://forem.com/truman_999999999</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1608677%2Fca7ecbb1-625e-4b76-bcc9-6e18b3e03d21.png</url>
      <title>Forem: Truman</title>
      <link>https://forem.com/truman_999999999</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/truman_999999999"/>
    <language>en</language>
    <item>
      <title>Deep Dive: Using Redis Hash Tags to Reduce Tail Latency in Redis Cluster Batch Reads</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Mon, 12 Jan 2026 10:51:09 +0000</pubDate>
      <link>https://forem.com/truman_999999999/deep-dive-using-redis-hash-tags-to-reduce-tail-latency-in-redis-cluster-batch-reads-552b</link>
      <guid>https://forem.com/truman_999999999/deep-dive-using-redis-hash-tags-to-reduce-tail-latency-in-redis-cluster-batch-reads-552b</guid>
      <description>&lt;p&gt;In &lt;strong&gt;Redis Open Source Cluster&lt;/strong&gt;, the keyspace is partitioned into &lt;strong&gt;16,384 hash slots&lt;/strong&gt;. Each slot is served by exactly one node at any point in time, and the cluster achieves high availability through master/replica replication.&lt;br&gt;
Slots are computed using CRC16 and modulo 16,384.&lt;br&gt;
A key design goal of Cluster is &lt;strong&gt;no proxies&lt;/strong&gt; and no server-side merging: clients are redirected (e.g., via &lt;code&gt;MOVED&lt;/code&gt; / &lt;code&gt;ASK&lt;/code&gt;) to the correct node and execute commands there.&lt;/p&gt;

&lt;p&gt;This architecture often leads to a common production symptom: batch reads that are fast on a standalone Redis (e.g., &lt;code&gt;MGET&lt;/code&gt; / pipelining) show much higher &lt;strong&gt;P99 jitter&lt;/strong&gt; and occasional &lt;strong&gt;timeouts&lt;/strong&gt; on Cluster.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Root Cause: Cross-slot fan-out amplifies tail latency
&lt;/h2&gt;

&lt;p&gt;Redis Cluster has a clear boundary for “complex multi-key operations”: they are supported &lt;strong&gt;only when all involved keys hash to the same slot&lt;/strong&gt;; otherwise, multi-key capabilities are not available.&lt;/p&gt;

&lt;p&gt;Therefore, if a set of logically related keys is spread across multiple slots:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variadic multi-key commands such as &lt;code&gt;MGET/MSET&lt;/code&gt; are constrained (a common symptom is a &lt;code&gt;CROSSSLOT&lt;/code&gt; error).&lt;/li&gt;
&lt;li&gt;Even if &lt;code&gt;MGET&lt;/code&gt; is replaced by multiple &lt;code&gt;GET&lt;/code&gt;s or pipelining, the workload still becomes &lt;strong&gt;multi-node requests + client-side aggregation&lt;/strong&gt; (scatter-gather), and overall latency is dominated by the slowest node.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Core Mechanism: Hash Tags reduce “M nodes” to “1 node”
&lt;/h2&gt;

&lt;p&gt;Cluster provides &lt;strong&gt;hash tags&lt;/strong&gt; to enable &lt;em&gt;data affinity&lt;/em&gt;: force a related group of keys onto the same slot, thereby enabling multi-key operations and significantly reducing cross-node fan-out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hash tag rules (strictly per cluster-spec)
&lt;/h3&gt;

&lt;p&gt;Hash tag processing is enabled only when all of the following are true:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The key contains &lt;code&gt;{&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;There is a &lt;code&gt;}&lt;/code&gt; to the right of that &lt;code&gt;{&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;There is at least one character between the first &lt;code&gt;{&lt;/code&gt; and the first &lt;code&gt;}&lt;/code&gt; to its right&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When valid, Cluster computes the CRC16 only on the substring inside that first valid &lt;code&gt;{...}&lt;/code&gt; to determine the slot.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Empty &lt;code&gt;{}&lt;/code&gt; is not a valid hash tag&lt;/strong&gt;: for example, &lt;code&gt;foo{}{bar}&lt;/code&gt; hashes the &lt;strong&gt;entire key&lt;/strong&gt;, not the empty string.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Where the benefit comes from: less fan-out, not “faster Redis execution”
&lt;/h2&gt;

&lt;p&gt;Example (Favorites scenario):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Before: &lt;code&gt;favorites:123:news001&lt;/code&gt; … &lt;code&gt;favorites:123:news010&lt;/code&gt;&lt;br&gt;
Different full key strings → likely different slots → &lt;code&gt;MGET&lt;/code&gt; may be disallowed or suffer high jitter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After: &lt;code&gt;favorites:{...}:news001&lt;/code&gt; … &lt;code&gt;favorites:{...}:news010&lt;/code&gt;&lt;br&gt;
Same tag → same slot → same node → collapse “multi-node concurrent requests + merge” into “single-node one round trip”.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Related Issues: practical boundaries and common misconceptions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) If &lt;code&gt;{...}&lt;/code&gt; contents are the same across services, will values collide?
&lt;/h3&gt;

&lt;p&gt;No.&lt;br&gt;
Hash tags only influence &lt;strong&gt;which slot/node&lt;/strong&gt; a key maps to; they do not change key uniqueness. Overwrites happen only when the &lt;strong&gt;entire key string&lt;/strong&gt; is identical.&lt;/p&gt;

&lt;p&gt;The real risk is &lt;strong&gt;missing namespaces&lt;/strong&gt; between domains/services, which can cause full-key collisions—not shared tag values.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Must the tag contain only a &lt;code&gt;userId&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;From a correctness standpoint, using only &lt;code&gt;userId&lt;/code&gt; is fine.&lt;br&gt;
From a load and “affinity boundary” standpoint, the tag should express &lt;em&gt;which keys truly must be colocated&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If multiple domains share &lt;code&gt;{userId}&lt;/code&gt;, the same user’s traffic across domains tends to concentrate on the same slot (no value confusion, but potential hot-spot stacking). A safer pattern is a &lt;strong&gt;composite tag&lt;/strong&gt; that constrains affinity within a domain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;favorites:{fav:123}:news001&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;orders:{ord:123}:order8899&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This preserves multi-key capability within the domain while reducing unnecessary cross-domain slot binding.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Even with hash tags, mixing different tags still fails
&lt;/h3&gt;

&lt;p&gt;For example, a single &lt;code&gt;MGET&lt;/code&gt; mixing &lt;code&gt;{u1}&lt;/code&gt; and &lt;code&gt;{u2}&lt;/code&gt; is still cross-slot. Cluster’s constraint remains “same slot”; hash tags only make “same slot” controllable by design.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Key Naming and Tag Design Guidelines (production-ready conventions)
&lt;/h2&gt;

&lt;p&gt;This section can be used directly as an internal engineering guideline.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Namespacing (mandatory)
&lt;/h3&gt;

&lt;p&gt;Prevent collisions across services/modules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended prefix structure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;env&amp;gt;:&amp;lt;service&amp;gt;:&amp;lt;module&amp;gt;:...&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prod:news:favorites:{fav:123}:news:001&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;test:uc:session:{sess:uid123}:token&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2) Tag placement and content (core)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommended structure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;prefix&amp;gt;:{&amp;lt;affinity&amp;gt;}:&amp;lt;suffix&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where &lt;code&gt;&amp;lt;affinity&amp;gt;&lt;/code&gt; defines the “affinity boundary”. Prefer &lt;strong&gt;domain + primary dimension&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{fav:&amp;lt;userId&amp;gt;}&lt;/code&gt;: favorites grouped by user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{ord:&amp;lt;orderId&amp;gt;}&lt;/code&gt;: orders grouped by order&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{cart:&amp;lt;userId&amp;gt;}&lt;/code&gt;: cart grouped by user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This yields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stable same-slot behavior within the domain (enabling multi-key / reducing fan-out)&lt;/li&gt;
&lt;li&gt;Avoids unnecessary cross-domain binding even if the dimension is the same (e.g., same userId)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Cluster uses only the first valid &lt;code&gt;{...}&lt;/code&gt; for hashing; empty &lt;code&gt;{}&lt;/code&gt; is invalid and the whole key is hashed instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3) Fields and hierarchy (readability + extensibility)
&lt;/h3&gt;

&lt;p&gt;Use a consistent delimiter (commonly &lt;code&gt;:&lt;/code&gt;) and make object types/fields explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;prod:news:favorites:{fav:123}:news:001&lt;/code&gt; (object: news, id: 001)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prod:news:favorites:{fav:123}:meta&lt;/code&gt; (aggregate metadata)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prod:uc:profile:{uc:123}:base&lt;/code&gt; (user base profile)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4) Versioning (recommended)
&lt;/h3&gt;

&lt;p&gt;Add versions to handle value schema upgrades safely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prod:news:favorites:v1:{fav:123}:news:001&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prod:news:favorites:v2:{fav:123}:news:001&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5) Skew &amp;amp; hot keys (must evaluate)
&lt;/h3&gt;

&lt;p&gt;Hash tags intentionally pin a group of keys to one slot. If the tag is too coarse, it can create skew. The cluster spec notes that hash tags exist to enable multi-key operations, and forcing too much into one slot leads to imbalance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global tags like &lt;code&gt;{common}&lt;/code&gt; or &lt;code&gt;{config}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Using a single tag to aggregate site-wide data&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rediscluster</category>
      <category>hashtag</category>
      <category>performanceoptimization</category>
      <category>dataaffinity</category>
    </item>
    <item>
      <title>深度解析：利用 Redis Hash Tag 解决 Redis Cluster 下批量读取的延迟抖动</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Mon, 12 Jan 2026 10:47:44 +0000</pubDate>
      <link>https://forem.com/truman_999999999/shen-du-jie-xi-li-yong-redis-hash-tag-jie-jue-redis-cluster-xia-pi-liang-du-qu-de-yan-chi-dou-dong-47ed</link>
      <guid>https://forem.com/truman_999999999/shen-du-jie-xi-li-yong-redis-hash-tag-jie-jue-redis-cluster-xia-pi-liang-du-qu-de-yan-chi-dou-dong-47ed</guid>
      <description>&lt;p&gt;在 Redis Cluster 中，keyspace 会被切分到 &lt;strong&gt;16384 个 hash slot&lt;/strong&gt;，每个 slot 在任意时刻只由一个节点负责；集群通过 master/replica 复制实现高可用。(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;br&gt;
slot 的计算基于 CRC16，并对 16384 取模。(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;br&gt;
同时，Cluster 设计目标之一就是 &lt;strong&gt;无代理（no proxies）&lt;/strong&gt;、不做服务端 merge 操作，客户端通过重定向（如 &lt;code&gt;MOVED&lt;/code&gt; / &lt;code&gt;ASK&lt;/code&gt;）连接到正确节点执行。(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;这套设计带来一个典型现象：单机里很快的批量读（&lt;code&gt;MGET&lt;/code&gt; / pipeline）到 Cluster 上，P99 抖动明显变大，甚至超时。&lt;/p&gt;




&lt;h2&gt;
  
  
  一、问题根因：跨 slot 的 fan-out 放大尾延迟
&lt;/h2&gt;

&lt;p&gt;Redis Cluster 对“复杂多 key 操作”有明确边界：&lt;strong&gt;只有当涉及的 keys 都 hash 到同一 slot 时才支持&lt;/strong&gt;；否则 multi-key 能力会不可用。(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;因此，业务上一批相关 key 如果分散到多个 slot：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MGET/MSET&lt;/code&gt; 这类 variadic multi-key 命令会受限（常见表现是 &lt;code&gt;CROSSSLOT&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;即便不使用 &lt;code&gt;MGET&lt;/code&gt;，而是改成多次 &lt;code&gt;GET&lt;/code&gt; 或 pipeline，本质上仍会变成 &lt;strong&gt;多节点请求 + 合并结果&lt;/strong&gt;（scatter-gather），整体耗时由最慢节点拖累。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  二、核心技术：Hash Tag 的作用是把 M 个节点收敛到 1 个节点
&lt;/h2&gt;

&lt;p&gt;Cluster 通过 &lt;strong&gt;hash tag&lt;/strong&gt; 提供“数据亲和性”：强制一组相关 key 落在同一 slot，从而允许 multi-key 操作并显著降低跨节点 fan-out。(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Hash Tag 规则（严格按 cluster-spec）
&lt;/h3&gt;

&lt;p&gt;当且仅当满足以下条件时启用 &lt;code&gt;{...}&lt;/code&gt;：(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;key 包含 &lt;code&gt;{&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{&lt;/code&gt; 右侧存在 &lt;code&gt;}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第一对 &lt;code&gt;{&lt;/code&gt; 与其右侧第一个 &lt;code&gt;}&lt;/code&gt; 之间至少有 1 个字符&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;满足时：只对这段子串做 CRC16 以计算 slot。(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;空 &lt;code&gt;{}&lt;/code&gt; 不生效&lt;/strong&gt;：例如 &lt;code&gt;foo{}{bar}&lt;/code&gt; 会对整串 key 正常哈希，而不是对空串哈希。(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  三、收益来自哪里：减少跨节点 fan-out，而不是“Redis 执行更快”
&lt;/h2&gt;

&lt;p&gt;示例（收藏场景）：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;优化前：&lt;code&gt;favorites:123:news001&lt;/code&gt; … &lt;code&gt;favorites:123:news010&lt;/code&gt;&lt;br&gt;
key 全名不同 → slot 大概率不同 → 原生 &lt;code&gt;MGET&lt;/code&gt; 会因跨 slot 报错，而客户端模拟的批量操作会因抖动导致延迟剧增。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;优化后：&lt;code&gt;favorites:{...}:news001&lt;/code&gt; … &lt;code&gt;favorites:{...}:news010&lt;/code&gt;&lt;br&gt;
tag 相同 → 同一 slot → 同一节点 → 从“多节点并发 + 合并”收敛为“单节点一次返回”。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  四、相关问题（工程边界与常见误解）
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1）&lt;code&gt;{...}&lt;/code&gt; 内容相同会不会“值乱/冲突”？
&lt;/h3&gt;

&lt;p&gt;不会。&lt;br&gt;
Hash Tag 只影响 &lt;strong&gt;落在哪个 slot/节点&lt;/strong&gt;，不会影响 key 的唯一性；是否覆盖只取决于 &lt;strong&gt;完整 key 字符串是否相同&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;真正需要防的是：不同业务/不同服务&lt;strong&gt;没做命名空间隔离&lt;/strong&gt;导致完整 key 撞名，而不是 tag 重复。&lt;/p&gt;

&lt;h3&gt;
  
  
  2）Tag 是否只能放一个 &lt;code&gt;userId&lt;/code&gt;？
&lt;/h3&gt;

&lt;p&gt;从正确性角度：只放 &lt;code&gt;userId&lt;/code&gt; 完全可行。(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;br&gt;
从负载与“亲和边界”角度：tag 的内容应表达“哪些 key 必须放在一起”。&lt;/p&gt;

&lt;p&gt;如果多个业务域都用 &lt;code&gt;{userId}&lt;/code&gt;，同一用户的跨业务访问会更倾向集中到同一 slot（不乱值，但可能叠加热点）。更稳的做法是使用 &lt;strong&gt;复合 tag&lt;/strong&gt; 限定亲和边界，例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;favorites:{fav:123}:news001&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;orders:{ord:123}:order8899&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这样仍可保证“服务内/业务内” multi-key 能力，同时减少“跨业务不必要绑死在同一 slot”的概率。&lt;/p&gt;

&lt;h3&gt;
  
  
  3）即便用了 Hash Tag，混用不同 Tag 仍会失败
&lt;/h3&gt;

&lt;p&gt;例如一次 &lt;code&gt;MGET&lt;/code&gt; 混合 &lt;code&gt;{u1}&lt;/code&gt; 与 &lt;code&gt;{u2}&lt;/code&gt;，本质上仍是跨 slot，multi-key 约束依然不满足。Redis Cluster 的 multi-key 能力边界是“同 slot”，hash tag 只是让“同 slot”变得可控。(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  五、Key 命名规范与 Tag 设计准则（落地规范）
&lt;/h2&gt;

&lt;p&gt;下面这段可以直接作为团队规范的“统一模板”。&lt;/p&gt;

&lt;h3&gt;
  
  
  1）命名空间（必须）
&lt;/h3&gt;

&lt;p&gt;保证不同服务/不同模块不会撞 key。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;推荐前缀结构：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;env&amp;gt;:&amp;lt;service&amp;gt;:&amp;lt;module&amp;gt;:...&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;示例：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prod:news:favorites:{fav:123}:news:001&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;test:uc:session:{sess:uid123}:token&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2）Tag 放置与内容（核心）
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;推荐结构：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;prefix&amp;gt;:{&amp;lt;affinity&amp;gt;}:&amp;lt;suffix&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其中 &lt;code&gt;&amp;lt;affinity&amp;gt;&lt;/code&gt; 用于定义“亲和边界”，建议采用 &lt;strong&gt;业务域 + 主维度&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{fav:&amp;lt;userId&amp;gt;}&lt;/code&gt;：收藏业务按用户聚合&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{ord:&amp;lt;orderId&amp;gt;}&lt;/code&gt;：订单业务按订单聚合&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{cart:&amp;lt;userId&amp;gt;}&lt;/code&gt;：购物车按用户聚合&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这样做的效果是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;同业务内可 multi-key（同 slot）&lt;/li&gt;
&lt;li&gt;跨业务不会因为同一个 userId 产生不必要的 slot 绑定&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：Cluster 只会使用第一对有效 &lt;code&gt;{...}&lt;/code&gt; 参与哈希；空 &lt;code&gt;{}&lt;/code&gt; 会被当作无效，整串 key 哈希。(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3）字段与层级（可读性 + 可扩展）
&lt;/h3&gt;

&lt;p&gt;建议用固定分隔符（常用 &lt;code&gt;:&lt;/code&gt;），并把“对象类型/字段”显式写出来：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;prod:news:favorites:{fav:123}:news:001&lt;/code&gt;（对象：news，id：001）&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prod:news:favorites:{fav:123}:meta&lt;/code&gt;（聚合元信息）&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prod:uc:profile:{uc:123}:base&lt;/code&gt;（用户基础资料）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4）版本号（建议）
&lt;/h3&gt;

&lt;p&gt;当 value 结构可能升级时，加版本避免回滚/灰度混乱：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prod:news:favorites:v1:{fav:123}:news:001&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prod:news:favorites:v2:{fav:123}:news:001&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5）热点与倾斜（必须评估）
&lt;/h3&gt;

&lt;p&gt;Hash Tag 会把一组 key 固定到同一 slot；tag 粒度过粗会造成倾斜。Cluster-spec 也明确 hash tag 的用途是为了 multi-key，同样需要避免把大量不相关 key 强行塞到一个 slot。(&lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;禁止示例：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{common}&lt;/code&gt;、&lt;code&gt;{config}&lt;/code&gt; 这类全局 tag&lt;/li&gt;
&lt;li&gt;用一个 tag 聚合“全站数据”&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hashtag</category>
      <category>performanceoptimization</category>
      <category>dataaffinity</category>
      <category>rediscluster</category>
    </item>
    <item>
      <title>Gracefully Integrating Sentry-Go Middleware into Fiber v3 Projects</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Tue, 15 Jul 2025 03:49:02 +0000</pubDate>
      <link>https://forem.com/truman_999999999/gracefully-integrating-sentry-go-middleware-into-fiber-v3-projects-565f</link>
      <guid>https://forem.com/truman_999999999/gracefully-integrating-sentry-go-middleware-into-fiber-v3-projects-565f</guid>
      <description>&lt;h2&gt;
  
  
  Gracefully Integrating Sentry-Go Middleware into Fiber v3 Projects
&lt;/h2&gt;

&lt;p&gt;This article explains how to integrate error monitoring functionality using Sentry-Go v0.34.1 in projects built with Go 1.24.3 and Fiber v3. We'll demonstrate how to adapt and rewrite the &lt;code&gt;sentryfiber&lt;/code&gt; middleware to support Fiber v3 and enhance flexibility with configurable path skipping (&lt;code&gt;SkipPaths&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Technology Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Go Version&lt;/strong&gt;: 1.24.3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fiber Version&lt;/strong&gt;: v3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentry-Go Version&lt;/strong&gt;: 0.34.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Currently, the official &lt;code&gt;sentryfiber&lt;/code&gt; middleware supports only Fiber v2. Given our project's upgrade to Fiber v3, middleware adaptation and rewriting are necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of Custom Middleware Features
&lt;/h2&gt;

&lt;p&gt;The rewritten middleware introduces these key enhancements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fiber v3 Compatibility&lt;/strong&gt;: Ensures compatibility with all Fiber v3 APIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SkipPaths Configuration&lt;/strong&gt;: Allows specific request paths to be ignored, reducing redundant or unnecessary logging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customizable Options&lt;/strong&gt;: Includes common configurations such as &lt;code&gt;Repanic&lt;/code&gt;, &lt;code&gt;WaitForDelivery&lt;/code&gt;, and &lt;code&gt;Timeout&lt;/code&gt;, providing a flexible integration experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Here is the key implementation code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Repanic&lt;/span&gt;         &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;WaitForDelivery&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt;         &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;
    &lt;span class="n"&gt;SkipPaths&lt;/span&gt;       &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SkipPaths&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>fiberv3</category>
      <category>sentrygo</category>
      <category>sentryfiber</category>
    </item>
    <item>
      <title>在 Fiber v3 项目中优雅地集成 Sentry-Go 中间件</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Tue, 15 Jul 2025 03:47:52 +0000</pubDate>
      <link>https://forem.com/truman_999999999/zai-fiber-v3-xiang-mu-zhong-you-ya-di-ji-cheng-sentry-go-zhong-jian-jian-3g33</link>
      <guid>https://forem.com/truman_999999999/zai-fiber-v3-xiang-mu-zhong-you-ya-di-ji-cheng-sentry-go-zhong-jian-jian-3g33</guid>
      <description>&lt;h2&gt;
  
  
  在 Fiber v3 项目中优雅地集成 Sentry-Go 中间件
&lt;/h2&gt;

&lt;p&gt;本文将介绍如何在使用 Go 1.24.3 和 Fiber v3 的项目中，使用 Sentry-Go v0.34.1 集成错误监控功能，并通过自定义 &lt;code&gt;sentryfiber&lt;/code&gt; 中间件支持 Fiber v3，增加了灵活的路径跳过配置 (&lt;code&gt;SkipPaths&lt;/code&gt;)。&lt;/p&gt;

&lt;h2&gt;
  
  
  技术选型
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Go 版本&lt;/strong&gt;：1.24.3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fiber 版本&lt;/strong&gt;：v3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentry-Go 版本&lt;/strong&gt;：0.34.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sentry 官方的 &lt;code&gt;sentryfiber&lt;/code&gt; 中间件目前只支持 Fiber v2，考虑到我们项目已经升级至 Fiber v3，因此需要进行适配并重写相关中间件。&lt;/p&gt;

&lt;h2&gt;
  
  
  自定义中间件功能概述
&lt;/h2&gt;

&lt;p&gt;我们重写的中间件新增了以下重要功能：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;兼容 Fiber v3&lt;/strong&gt;：确保所有 API 与 Fiber v3 兼容。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SkipPaths 配置&lt;/strong&gt;：通过路径配置来忽略某些请求，避免冗余或不必要的日志上报。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;自定义选项&lt;/strong&gt;：支持如 &lt;code&gt;Repanic&lt;/code&gt;、&lt;code&gt;WaitForDelivery&lt;/code&gt; 和 &lt;code&gt;Timeout&lt;/code&gt; 等常用配置，提供灵活的集成体验。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  实现方式
&lt;/h2&gt;

&lt;p&gt;以下为关键实现代码：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Repanic&lt;/span&gt;         &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;WaitForDelivery&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt;         &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;
    &lt;span class="n"&gt;SkipPaths&lt;/span&gt;       &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SkipPaths&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;hub&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentHub&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;ctxWithHub&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetHubOnContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;hub&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctxWithHub&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;hub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scope&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toHTTPRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;traceHeader&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SentryTraceHeader&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;baggageHeader&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SentryBaggageHeader&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ctxWithHub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithOpName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http.server"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContinueFromHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;traceHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baggageHeader&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;hub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scope&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;recover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;hub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Recover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;hub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repanic&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;hub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CaptureException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitForDelivery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;hub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SkipPaths 的应用场景
&lt;/h2&gt;

&lt;p&gt;通过配置 &lt;code&gt;SkipPaths&lt;/code&gt; 可以有效避免特定路由请求的监控，比如健康检查接口：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Repanic&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;WaitForDelivery&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SkipPaths&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"/health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/metrics"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  小结
&lt;/h2&gt;

&lt;p&gt;本文通过对官方 &lt;code&gt;sentryfiber&lt;/code&gt; 中间件的重写，提供了 Fiber v3 项目使用 Sentry-Go 的最佳实践，并显著提升了日志监控的灵活性和准确性。&lt;/p&gt;

</description>
      <category>fiberv3</category>
      <category>sentrygo</category>
      <category>sentryfiber</category>
    </item>
    <item>
      <title>WebSocket 请求通过 CDN 返回 403 Forbidden 的问题排查与解决</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Sat, 24 May 2025 05:31:59 +0000</pubDate>
      <link>https://forem.com/truman_999999999/websocket-qing-qiu-tong-guo-cdn-fan-hui-403-forbidden-de-wen-ti-pai-cha-yu-jie-jue-59hb</link>
      <guid>https://forem.com/truman_999999999/websocket-qing-qiu-tong-guo-cdn-fan-hui-403-forbidden-de-wen-ti-pai-cha-yu-jie-jue-59hb</guid>
      <description>&lt;h2&gt;
  
  
  WebSocket 请求通过 CDN 返回 403 Forbidden 的问题排查与解决（wstest.example.com）
&lt;/h2&gt;

&lt;p&gt;在一次使用 Kubernetes + NGINX Ingress + WebSocket 服务部署中，配置了 CDN（Funnell）接入域名：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;wss://wstest.example.com/push/ws?token=xxx&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;服务端为 Go 编写的 &lt;code&gt;go-push&lt;/code&gt; 应用，监听端口 &lt;code&gt;8081&lt;/code&gt;，用于处理 WebSocket 请求。&lt;/p&gt;




&lt;h2&gt;
  
  
  问题描述
&lt;/h2&gt;

&lt;p&gt;客户端（如 Postman、浏览器）访问 WebSocket 接口时，始终收到：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;403 Forbidden&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;响应头极其简洁，仅包含：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Date: ...&lt;br&gt;
Content-Length: 0&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;后端服务日志中无任何请求记录，初步判断 &lt;strong&gt;请求未到达服务层&lt;/strong&gt;。&lt;/p&gt;




&lt;h2&gt;
  
  
  排查过程
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 检查 Ingress 配置
&lt;/h3&gt;

&lt;p&gt;Kubernetes Ingress 使用以下配置：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-http-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.1"&lt;/span&gt;
  &lt;span class="na"&gt;nginx.ingress.kubernetes.io/connection-proxy-header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep-alive&lt;/span&gt;
  &lt;span class="na"&gt;nginx.ingress.kubernetes.io/use-forwarded-headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
  &lt;span class="na"&gt;nginx.ingress.kubernetes.io/rewrite-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/$2&lt;/span&gt;
  &lt;span class="na"&gt;nginx.ingress.kubernetes.io/configuration-snippet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;proxy_set_header Upgrade $http_upgrade;&lt;/span&gt;
    &lt;span class="s"&gt;proxy_set_header Connection "Upgrade";&lt;/span&gt;
    &lt;span class="s"&gt;proxy_set_header X-Request-From gateway;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;路径路由为：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/push(/|$)(.*)&lt;/span&gt;
  &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ImplementationSpecific&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go-push&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8081&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;确认路由正确，路径 &lt;code&gt;/push/ws&lt;/code&gt; 会被重写为 &lt;code&gt;/ws&lt;/code&gt; 并转发给 &lt;code&gt;go-push&lt;/code&gt; 服务。&lt;/p&gt;




&lt;h3&gt;
  
  
  2. 测试绕过 CDN，直连 Ingress
&lt;/h3&gt;

&lt;p&gt;使用 curl 模拟 WebSocket 握手：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Connection: Upgrade"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Upgrade: websocket"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Sec-WebSocket-Version: 13"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Sec-WebSocket-Key: test123=="&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Host: wstest.example.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://&amp;lt;Ingress-IP&amp;gt;:&amp;lt;Port&amp;gt;/push/ws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;返回 &lt;code&gt;101 Switching Protocols&lt;/code&gt;，说明 Kubernetes 服务与 Ingress 正常，问题来自 CDN。&lt;/p&gt;




&lt;h3&gt;
  
  
  3. 确认 CDN（Funnell）拒绝连接
&lt;/h3&gt;

&lt;p&gt;日志及响应特征表明：&lt;strong&gt;403 来自 CDN 层拦截而非应用或 Ingress 返回。主要怀疑：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;WebSocket 未开启支持&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;请求路径未允许回源&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;请求头未透传&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  解决方案（Funnell 配置）
&lt;/h2&gt;

&lt;p&gt;在 Funnell CDN 控制台中，对域名 wstest.example.com 做如下配置调整：&lt;/p&gt;

&lt;h3&gt;
  
  
  开启 WebSocket 支持
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;开启「WebSocket 协议支持」或「连接升级（Upgrade）」功能&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  配置回源路径白名单
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;添加 &lt;code&gt;/push/.*&lt;/code&gt; 至允许回源路径&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;若默认只允许 &lt;code&gt;/api&lt;/code&gt; 或 &lt;code&gt;/static&lt;/code&gt;，未配置的新路径可能被直接 403 拦截&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  请求头透传设置
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;保证以下头部 &lt;strong&gt;不被 CDN 移除或篡改&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Header&lt;/th&gt;
&lt;th&gt;正确值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Connection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Upgrade&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Upgrade&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;websocket&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-WebSocket-*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保留原样&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  验证成功
&lt;/h2&gt;

&lt;p&gt;完成配置后，再次通过 WebSocket 客户端访问：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;wss:&lt;/span&gt;&lt;span class="sr"&gt;//&lt;/span&gt;&lt;span class="nv"&gt;wstest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;example&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;com&lt;/span&gt;&lt;span class="sr"&gt;/push/&lt;/span&gt;&lt;span class="nv"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;xxx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;得到正确响应：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;服务端日志打印握手成功，连接稳定。&lt;/p&gt;




&lt;h2&gt;
  
  
  总结
&lt;/h2&gt;

&lt;p&gt;当 WebSocket 请求经由 CDN（如 Funnell）出现 403 Forbidden，常见原因不在服务本身，而是 CDN 安全策略或协议限制。请务必检查以下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CDN 是否开启 WebSocket 支持&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;回源路径是否白名单允许&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;是否保留关键握手头部（如 Upgrade、Connection）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ingress 是否配置正确的 proxy_set_header 和路径转发规则&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;处理得当后，可实现 CDN 加速 + WebSocket 稳定通信 的双赢方案。&lt;/p&gt;

</description>
      <category>websocket</category>
      <category>cdn</category>
      <category>ingress</category>
      <category>403</category>
    </item>
    <item>
      <title>Troubleshooting and Resolving WebSocket 403 Forbidden Errors Through CDN</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Sat, 24 May 2025 05:09:39 +0000</pubDate>
      <link>https://forem.com/truman_999999999/websocket-qing-qiu-tong-guo-cdn-fan-hui-403-forbidden-de-wen-ti-pai-cha-yu-jie-jue-lo4</link>
      <guid>https://forem.com/truman_999999999/websocket-qing-qiu-tong-guo-cdn-fan-hui-403-forbidden-de-wen-ti-pai-cha-yu-jie-jue-lo4</guid>
      <description>&lt;h2&gt;
  
  
  Troubleshooting and Resolving WebSocket 403 Forbidden Errors Through CDN (wstest.example.com)
&lt;/h2&gt;

&lt;p&gt;）##&lt;/p&gt;

&lt;p&gt;In a Kubernetes deployment using NGINX Ingress and a Go-based WebSocket service (&lt;code&gt;go-push&lt;/code&gt;), we encountered a problem after routing traffic through a CDN provider (Funnell) using the following domain:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;wss://wstest.example.com/push/ws?token=xxx&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The backend service listens on port &lt;code&gt;8081&lt;/code&gt; and is designed to handle WebSocket connections.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When connecting to the WebSocket endpoint via a browser or tools like Postman, we consistently received:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;403 Forbidden&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With minimal response headers:：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Date: ...&lt;br&gt;
Content-Length: 0&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No requests were seen in the backend service logs, indicating the request never reached the application layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Investigation Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Check Ingress Configuration
&lt;/h3&gt;

&lt;p&gt;The Kubernetes Ingress was configured with the following annotations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-http-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.1"&lt;/span&gt;
  &lt;span class="na"&gt;nginx.ingress.kubernetes.io/connection-proxy-header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep-alive&lt;/span&gt;
  &lt;span class="na"&gt;nginx.ingress.kubernetes.io/use-forwarded-headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
  &lt;span class="na"&gt;nginx.ingress.kubernetes.io/rewrite-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/$2&lt;/span&gt;
  &lt;span class="na"&gt;nginx.ingress.kubernetes.io/configuration-snippet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;proxy_set_header Upgrade $http_upgrade;&lt;/span&gt;
    &lt;span class="s"&gt;proxy_set_header Connection "Upgrade";&lt;/span&gt;
    &lt;span class="s"&gt;proxy_set_header X-Request-From gateway;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The routing rule was defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/push(/|$)(.*)&lt;/span&gt;
  &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ImplementationSpecific&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go-push&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8081&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration rewrites &lt;code&gt;/push/ws&lt;/code&gt; to &lt;code&gt;/ws&lt;/code&gt; and forwards it to the backend service.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Bypass the CDN to Verify Ingress Functionality
&lt;/h3&gt;

&lt;p&gt;I used curl to simulate a WebSocket handshake request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Connection: Upgrade"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Upgrade: websocket"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Sec-WebSocket-Version: 13"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Sec-WebSocket-Key: test123=="&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Host: wstest.example.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://&amp;lt;Ingress-IP&amp;gt;:&amp;lt;Port&amp;gt;/push/ws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I received a &lt;code&gt;101 Switching Protocols&lt;/code&gt; response, confirming that both Ingress and the backend were functioning correctly. The issue was clearly upstream—in the CDN layer.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Identify CDN as the Source of the 403
&lt;/h3&gt;

&lt;p&gt;Based on the response and headers, we confirmed the &lt;code&gt;403 Forbidden&lt;/code&gt; was returned by the CDN (Funnell), not by Kubernetes or the backend service. Likely causes included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;WebSocket support not enabled&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;/push/ws&lt;/code&gt; path not allowed in origin routing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Required WebSocket headers being stripped&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Solution (Funnell CDN Configuration)
&lt;/h2&gt;

&lt;p&gt;To resolve this issue, we made the following changes in the Funnell CDN management console for the domain &lt;code&gt;wstest.example.com&lt;/code&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable WebSocket Support
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Activate the &lt;strong&gt;WebSocket support&lt;/strong&gt; or &lt;strong&gt;Connection Upgrade&lt;/strong&gt; option in the CDN settings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Allow the WebSocket Path in Origin Routing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Explicitly allow paths like &lt;code&gt;/push/.*&lt;/code&gt; in the origin path whitelist&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the default routing only allows &lt;code&gt;/api&lt;/code&gt; or &lt;code&gt;/static&lt;/code&gt;, custom paths will be blocked with 403&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Preserve WebSocket Headers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ensure the following headers are forwarded without modification:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Header&lt;/th&gt;
&lt;th&gt;Expected Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Connection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Upgrade&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Upgrade&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;websocket&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-WebSocket-*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;As-is&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Successful Validation
&lt;/h2&gt;

&lt;p&gt;After updating the CDN settings, WebSocket handshake requests to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;wss:&lt;/span&gt;&lt;span class="sr"&gt;//&lt;/span&gt;&lt;span class="nv"&gt;wstest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;example&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;com&lt;/span&gt;&lt;span class="sr"&gt;/push/&lt;/span&gt;&lt;span class="nv"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;xxx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;succeeded with a valid response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the backend logs confirmed the connection was received and processed correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;When WebSocket requests routed through a CDN return 403 Forbidden, the root cause is often not in the backend or Ingress but in the CDN's security and protocol handling rules.&lt;/p&gt;

&lt;p&gt;To ensure WebSocket connections work correctly through a CDN:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Enable WebSocket protocol support&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Allow the WebSocket path in origin routing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Preserve critical handshake headers (Upgrade, Connection)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use proper rewrite and header forwarding rules in Ingress&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these in place, it's possible to enjoy both CDN performance and stable WebSocket connectivity.&lt;/p&gt;

</description>
      <category>websocket</category>
      <category>cdn</category>
      <category>ingress</category>
      <category>403</category>
    </item>
    <item>
      <title>Java 中 Redis 存储泛型对象 Map 的反序列化陷阱</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Thu, 01 May 2025 07:47:19 +0000</pubDate>
      <link>https://forem.com/truman_999999999/java-zhong-redis-cun-chu-fan-xing-dui-xiang-map-de-fan-xu-lie-hua-xian-jing-5972</link>
      <guid>https://forem.com/truman_999999999/java-zhong-redis-cun-chu-fan-xing-dui-xiang-map-de-fan-xu-lie-hua-xian-jing-5972</guid>
      <description>&lt;h2&gt;
  
  
  背景介绍
&lt;/h2&gt;

&lt;p&gt;本项目采用了双层缓存机制：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;本地缓存使用 &lt;strong&gt;Caffeine 2.8.8&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;远程缓存使用 &lt;strong&gt;Redis&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;系统运行在 &lt;strong&gt;JDK 1.8&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们将一个包含多个字段的 &lt;code&gt;Map&amp;lt;String, Object&amp;gt;&lt;/code&gt; 对象整体写入 Redis，其中 &lt;code&gt;"list"&lt;/code&gt; 字段的值为一个 &lt;code&gt;List&amp;lt;NewLiveMatchVO&amp;gt;&lt;/code&gt; 类型的对象列表：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;写入 Redis 前&lt;/strong&gt;：值是 Java 内存中的 &lt;code&gt;List&amp;lt;NewLiveMatchVO&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;从 Redis 反序列化后&lt;/strong&gt;：值变成了 &lt;code&gt;List&amp;lt;LinkedHashMap&amp;gt;&lt;/code&gt;，由于类型信息丢失，强制转换会抛出 &lt;code&gt;ClassCastException&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以下是相关缓存处理代码片段：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// redis序列化代码&lt;/span&gt;
&lt;span class="nd"&gt;@Slf4j&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RedisObjectSerializer&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;RedisSerializer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="no"&gt;EMPTY_BYTE_ARRAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Converter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[],&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;deserializingConverter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DeserializingConverter&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 这个时候没有要序列化的对象出现，所以返回的字节数组应该就是一个空数组&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;EMPTY_BYTE_ARRAY&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// 将对象变为字节数组&lt;/span&gt;
        &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toJSONBytes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SerializerFeature&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WriteClassName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 此时没有对象的内容信息&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Feature&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SupportAutoType&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deserializingConverter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;convert&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"无法反序列化redis数据：{}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;StandardCharsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;UTF_8&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;//数据上游，填充数据阶段，返回map&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenericCacheManager&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getWriteExpireCacheByName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"queryData"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cache-key"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;redisKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"redisKey"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;finalResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;redisCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForValue&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redisKey&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;redisCache&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redisCache&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Maps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newHashMap&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewLiveMatchVO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queryListData&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"list"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewTournamentVO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tCollect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;groupingBy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;NewTournamentVO:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getSportId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tournament"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tCollect&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForValue&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redisKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 获取缓存数据，强制类型转换（潜在风险点）&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewLiveMatchVO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;matchList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewLiveMatchVO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="n"&gt;finalResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"list"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 后续处理&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matchList&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt; &lt;span class="nf"&gt;processResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewLiveMatchVO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CollectionUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;distinct&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// 在这里报错 java.lang.ClassCastException: java.util.ArrayList cannot be cast to com.qiutx.product.model.vo.NewLiveMatchVO&lt;/span&gt;
    &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;List&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  根本原因
&lt;/h2&gt;

&lt;p&gt;Fastjson 在序列化整个 Map 的时候，如果 Map 内的值（例如 List）没有显式类型信息（@type），它就不会自动对 List 内的泛型对象添加类型。&lt;/p&gt;

&lt;p&gt;所以即使你用了 SerializerFeature.WriteClassName，最终只记录了 Map 和 Map 的 key 是 "list"，value 是个 List，而 NewLiveMatchVO 的类型信息丢失了。&lt;/p&gt;

&lt;p&gt;反序列化回来就是这个样子：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;finalResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForValue&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"xxx"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 此时 finalResult.get("list") 实际是 List&amp;lt;LinkedHashMap&amp;gt;（不是 List&amp;lt;NewLiveMatchVO&amp;gt;）&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;去强转 List 就会炸：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewLiveMatchVO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="n"&gt;finalResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"list"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ❌ ClassCastException&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;处理办法有很多，比如：只存 JSON 字符串、使用泛型反序列化（不推荐 Map），使用专门的 VO 包装类&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 总结一句话：
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;当你把整个 &lt;code&gt;Map&lt;/code&gt; 存入 Redis 并反序列化回来后，其中的泛型类型（如 &lt;code&gt;List&amp;lt;NewLiveMatchVO&amp;gt;&lt;/code&gt;）会被擦除为 &lt;code&gt;List&amp;lt;LinkedHashMap&amp;gt;&lt;/code&gt;，&lt;br&gt;
&lt;strong&gt;这会导致 &lt;code&gt;ClassCastException&lt;/code&gt;，必须在使用前重新手动转换。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>分布式缓存实践</category>
      <category>java反序列化</category>
      <category>classcastexception</category>
      <category>redis缓存</category>
    </item>
    <item>
      <title>Native Memory Tracking 参数解释</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Tue, 19 Nov 2024 08:21:19 +0000</pubDate>
      <link>https://forem.com/truman_999999999/native-memory-tracking-can-shu-jie-shi-dlh</link>
      <guid>https://forem.com/truman_999999999/native-memory-tracking-can-shu-jie-shi-dlh</guid>
      <description>&lt;p&gt;此片仅为NMT的输出信息解释，详细内容在&lt;a href="https://dev.to/truman_999999999/jvmnei-cun-shi-yong-chao-chu-dui-nei-cun-xian-zhi-de-wen-ti-pai-cha-26ph"&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;启动命令：&lt;br&gt;
&lt;code&gt;-Xms8G -Xmx8G -Xmn4G -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:NativeMemoryTracking=summary -XX:+UseParallelGC&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;输出信息：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sh-4.4# jcmd 1 VM.native_memory summary
1:

Native Memory Tracking:

(Omitting categories weighting less than 1KB)

Total: reserved=9488872KB, committed=9037604KB
       malloc: 90692KB #530181
       mmap:   reserved=9398180KB, committed=8946912KB

-                 Java Heap (reserved=8388608KB, committed=8388608KB)
                            (mmap: reserved=8388608KB, committed=8388608KB)

-                     Class (reserved=216083KB, committed=19475KB)
                            (classes #24831)
                            (  instance classes #23390, array classes #1441)
                            (malloc=3091KB #81714)
                            (mmap: reserved=212992KB, committed=16384KB)
                            (  Metadata:   )
                            (    reserved=131072KB, committed=126208KB)
                            (    used=125492KB)
                            (    waste=716KB =0.57%)
                            (  Class space:)
                            (    reserved=212992KB, committed=16384KB)
                            (    used=15382KB)
                            (    waste=1002KB =6.12%)

-                    Thread (reserved=97958KB, committed=10654KB)
                            (thread #96)
                            (stack: reserved=97644KB, committed=10340KB)
                            (malloc=205KB #579)
                            (arena=109KB #188)

-                      Code (reserved=254760KB, committed=95700KB)
                            (malloc=7072KB #22671)
                            (mmap: reserved=247688KB, committed=88628KB)

-                        GC (reserved=310290KB, committed=310286KB)
                            (malloc=6542KB #99)
                            (mmap: reserved=303748KB, committed=303744KB)

-                  Compiler (reserved=821KB, committed=821KB)
                            (malloc=657KB #1513)
                            (arena=164KB #4)

-                  Internal (reserved=1608KB, committed=1608KB)
                            (malloc=1572KB #53569)
                            (mmap: reserved=36KB, committed=36KB)

-                     Other (reserved=16890KB, committed=16890KB)
                            (malloc=16890KB #52)

-                    Symbol (reserved=41301KB, committed=41301KB)
                            (malloc=36179KB #334650)
                            (arena=5122KB #1)

-    Native Memory Tracking (reserved=8483KB, committed=8483KB)
                            (malloc=199KB #3578)
                            (tracking overhead=8284KB)

-        Shared class space (reserved=16384KB, committed=12956KB, readonly=0KB)
                            (mmap: reserved=16384KB, committed=12956KB)

-               Arena Chunk (reserved=692KB, committed=692KB)
                            (malloc=692KB #373)

-                    Module (reserved=252KB, committed=252KB)
                            (malloc=252KB #4553)

-                 Safepoint (reserved=8KB, committed=8KB)
                            (mmap: reserved=8KB, committed=8KB)

-           Synchronization (reserved=2592KB, committed=2592KB)
                            (malloc=2592KB #25509)

-            Serviceability (reserved=18KB, committed=18KB)
                            (malloc=18KB #36)

-                 Metaspace (reserved=132121KB, committed=127257KB)
                            (malloc=1049KB #1245)
                            (mmap: reserved=131072KB, committed=126208KB)

-      String Deduplication (reserved=1KB, committed=1KB)
                            (malloc=1KB #8)

-           Object Monitors (reserved=4KB, committed=4KB)
                            (malloc=4KB #19)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  JVM Native Memory Tracking (NMT) 解读
&lt;/h1&gt;

&lt;p&gt;从 &lt;code&gt;jcmd VM.native_memory summary&lt;/code&gt; 输出中分析 JVM 各部分内存使用情况：&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;1. JVM 内存总览&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;总内存&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reserved=9488872KB&lt;/code&gt; (~9GB)：JVM 已保留的虚拟内存。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;committed=9037604KB&lt;/code&gt; (~8.6GB)：JVM 已向操作系统申请并实际使用的物理内存。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;分配方式&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;malloc=90692KB&lt;/code&gt;：通过 C 的 &lt;code&gt;malloc&lt;/code&gt; 分配的内存。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mmap=9398180KB&lt;/code&gt;：通过内存映射（&lt;code&gt;mmap&lt;/code&gt;）分配的内存。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;2. Java 堆（Java Heap）&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;堆内存使用&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reserved=8388608KB&lt;/code&gt; (~8GB)：最大堆大小。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;committed=8388608KB&lt;/code&gt; (~8GB)：已使用堆内存。&lt;/li&gt;
&lt;li&gt;堆内存占用了 JVM 的绝大部分内存。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;堆内存完全被 &lt;code&gt;mmap&lt;/code&gt; 分配。&lt;/li&gt;
&lt;li&gt;堆内存未出现碎片或不足问题。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;3. Class（类加载器内存）&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;总内存&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reserved=216083KB&lt;/code&gt; (~211MB)：已保留。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;committed=19475KB&lt;/code&gt; (~19MB)：实际使用。&lt;/li&gt;
&lt;li&gt;其中，&lt;code&gt;malloc&lt;/code&gt; 使用了 3091KB，&lt;code&gt;mmap&lt;/code&gt; 使用了 16384KB。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;元空间（Metadata）&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reserved=131072KB&lt;/code&gt; (~128MB)：最大元空间大小。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;committed=126208KB&lt;/code&gt; (~123MB)：已使用。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;used=125492KB&lt;/code&gt; (~122.5MB)：元空间中的实际数据。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;元空间碎片率&lt;/strong&gt;：0.57%。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Class Space（类空间）&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reserved=212992KB&lt;/code&gt; (~208MB)。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;committed=16384KB&lt;/code&gt; (~16MB)。&lt;/li&gt;
&lt;li&gt;使用碎片率为 6.12%。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;4. 线程（Thread）&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;线程总内存&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reserved=97958KB&lt;/code&gt; (~95.6MB)：保留的线程相关内存。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;committed=10654KB&lt;/code&gt; (~10.4MB)：实际使用的线程相关内存。&lt;/li&gt;
&lt;li&gt;当前线程数：96。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;栈内存（Stack）&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reserved=97644KB&lt;/code&gt; (~95.3MB)：线程栈的总保留内存。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;committed=10340KB&lt;/code&gt; (~10.1MB)：已实际分配的栈内存。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;每个线程的栈大小&lt;/strong&gt;：
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;平均栈大小 = committed / thread count = 10340KB / 96 ≈ 107.7KB
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;5. GC（垃圾回收器）&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GC 内存使用&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reserved=310290KB&lt;/code&gt; (~303MB)。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;committed=310286KB&lt;/code&gt; (~303MB)。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;分配方式&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;malloc=6542KB&lt;/code&gt; (~6.4MB)。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mmap=303748KB&lt;/code&gt; (~296.7MB)。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;GC 内存分布&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;主要分布在 &lt;code&gt;mmap&lt;/code&gt; 中，可能用于 Parallel GC 的内部数据结构，如标记和回收的任务队列。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;6. Code（代码缓存）&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;代码缓存&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reserved=254760KB&lt;/code&gt; (~248MB)。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;committed=95700KB&lt;/code&gt; (~93MB)。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;代码缓存用于存储 JIT 编译后的方法字节码。&lt;/li&gt;
&lt;li&gt;其中 &lt;code&gt;malloc=7072KB&lt;/code&gt; 和 &lt;code&gt;mmap=247688KB&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;7. Metaspace（元空间）&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;总内存&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reserved=132121KB&lt;/code&gt; (~129MB)。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;committed=127257KB&lt;/code&gt; (~124MB)。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;实际使用&lt;/strong&gt;：&lt;/li&gt;
&lt;li&gt;元数据区使用 &lt;code&gt;used=125492KB&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;类加载空间使用 &lt;code&gt;used=15382KB&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;8. Thread 栈空间的最大使用&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;线程栈使用&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;当前线程数：96。&lt;/li&gt;
&lt;li&gt;每个线程的平均实际栈使用量约为 &lt;strong&gt;107.7KB&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;系统为每个线程预留了约 &lt;strong&gt;1MB&lt;/strong&gt; 的栈内存空间。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;总结&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Java 堆内存&lt;/strong&gt;：堆内存占用 8GB，无碎片，运行正常。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;线程栈使用&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;每个线程的平均实际栈使用量约为 &lt;strong&gt;107.7KB&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;系统为每个线程预留了约 &lt;strong&gt;1MB&lt;/strong&gt; 的栈内存空间。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;元空间（Metaspace）&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;使用了 124MB，运行正常。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;代码缓存（Code Cache）&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;JIT 编译代码占用约 93MB。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果线程栈需要更多内存，但未超出 &lt;code&gt;-Xss&lt;/code&gt; 限制，则分配成功；如果超出限制，会触发 &lt;strong&gt;&lt;code&gt;StackOverflowError&lt;/code&gt;&lt;/strong&gt;。当前设置下，线程运行正常，没有栈不足问题。&lt;/p&gt;

&lt;p&gt;内存增加后命令：&lt;br&gt;
&lt;code&gt;-Xms10G -Xmx10G -Xmn4G -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:NativeMemoryTracking=summary -XX:+UseParallelGC&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 监控发现使用内存 11.86GB。&lt;br&gt;
输出信息：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sh-4.4# jcmd 1 VM.native_memory summary
1:

Native Memory Tracking:

(Omitting categories weighting less than 1KB)

Total: reserved=11658171KB, committed=11214347KB
       malloc: 91023KB #533380
       mmap:   reserved=11567148KB, committed=11123324KB

-                 Java Heap (reserved=10485760KB, committed=10485760KB)
                            (mmap: reserved=10485760KB, committed=10485760KB)

-                     Class (reserved=216153KB, committed=19609KB)
                            (classes #24834)
                            (  instance classes #23386, array classes #1448)
                            (malloc=3161KB #83460)
                            (mmap: reserved=212992KB, committed=16448KB)
                            (  Metadata:   )
                            (    reserved=131072KB, committed=126656KB)
                            (    used=125974KB)
                            (    waste=682KB =0.54%)
                            (  Class space:)
                            (    reserved=212992KB, committed=16448KB)
                            (    used=15385KB)
                            (    waste=1063KB =6.46%)

-                    Thread (reserved=91770KB, committed=9978KB)
                            (thread #90)
                            (stack: reserved=91476KB, committed=9684KB)
                            (malloc=192KB #543)
                            (arena=102KB #176)

-                      Code (reserved=255553KB, committed=97913KB)
                            (malloc=7865KB #23483)
                            (mmap: reserved=247688KB, committed=90048KB)

-                        GC (reserved=388402KB, committed=388398KB)
                            (malloc=6670KB #99)
                            (mmap: reserved=381732KB, committed=381728KB)

-                  Compiler (reserved=739KB, committed=739KB)
                            (malloc=575KB #1481)
                            (arena=164KB #4)

-                  Internal (reserved=1612KB, committed=1612KB)
                            (malloc=1576KB #54081)
                            (mmap: reserved=36KB, committed=36KB)

-                     Other (reserved=16849KB, committed=16849KB)
                            (malloc=16849KB #48)

-                    Symbol (reserved=41308KB, committed=41308KB)
                            (malloc=36186KB #334762)
                            (arena=5122KB #1)

-    Native Memory Tracking (reserved=8535KB, committed=8535KB)
                            (malloc=201KB #3621)
                            (tracking overhead=8334KB)

-        Shared class space (reserved=16384KB, committed=12956KB, readonly=0KB)
                            (mmap: reserved=16384KB, committed=12956KB)

-               Arena Chunk (reserved=69KB, committed=69KB)
                            (malloc=69KB #336)

-                    Module (reserved=252KB, committed=252KB)
                            (malloc=252KB #4553)

-                 Safepoint (reserved=8KB, committed=8KB)
                            (mmap: reserved=8KB, committed=8KB)

-           Synchronization (reserved=2597KB, committed=2597KB)
                            (malloc=2597KB #25558)

-            Serviceability (reserved=18KB, committed=18KB)
                            (malloc=18KB #36)

-                 Metaspace (reserved=132158KB, committed=127742KB)
                            (malloc=1086KB #1281)
                            (mmap: reserved=131072KB, committed=126656KB)

-      String Deduplication (reserved=1KB, committed=1KB)
                            (malloc=1KB #8)

-           Object Monitors (reserved=3KB, committed=3KB)
                            (malloc=3KB #17)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  JVM 内存对比分析（去除堆内存增加后的差异）
&lt;/h1&gt;

&lt;p&gt;通过两次 &lt;code&gt;jcmd VM.native_memory summary&lt;/code&gt; 输出，去除新增的 2GB 堆内存后，分析其他内存占用部分的变化。&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;1. 总内存&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;第一次 (单位: KB)&lt;/th&gt;
&lt;th&gt;第二次 (单位: KB)&lt;/th&gt;
&lt;th&gt;差值 (单位: KB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reserved&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;9488872&lt;/code&gt; (~9.0GB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;11658171&lt;/code&gt; (~11.1GB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+2179299&lt;/code&gt; (~2GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Committed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;9037604&lt;/code&gt; (~8.6GB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;11214347&lt;/code&gt; (~10.7GB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+2176743&lt;/code&gt; (~2GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;分析&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;增长的 &lt;strong&gt;2GB&lt;/strong&gt; 是堆内存的直接增加。&lt;/li&gt;
&lt;li&gt;去除堆内存后，其他部分的变化如下。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;2. 堆内存（Heap Memory）&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;第一次 (单位: KB)&lt;/th&gt;
&lt;th&gt;第二次 (单位: KB)&lt;/th&gt;
&lt;th&gt;差值 (单位: KB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reserved&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;8388608&lt;/code&gt; (~8GB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;10485760&lt;/code&gt; (~10GB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+2097152&lt;/code&gt; (~2GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Committed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;8388608&lt;/code&gt; (~8GB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;10485760&lt;/code&gt; (~10GB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+2097152&lt;/code&gt; (~2GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;分析&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;堆内存完全按照配置变化，增加了 &lt;strong&gt;2GB&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;去除堆内存影响后，以下部分差异进一步分析。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;3. 元空间和类加载器内存&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;第一次 (单位: KB)&lt;/th&gt;
&lt;th&gt;第二次 (单位: KB)&lt;/th&gt;
&lt;th&gt;差值 (单位: KB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reserved&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;216083&lt;/code&gt; (~211MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;216153&lt;/code&gt; (~211MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+70&lt;/code&gt; (~0MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Committed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;19475&lt;/code&gt; (~19MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;19609&lt;/code&gt; (~19MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+134&lt;/code&gt; (~0MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metadata Used&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;125492&lt;/code&gt; (~122MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;125974&lt;/code&gt; (~123MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+482&lt;/code&gt; (~0MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;分析&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;元空间使用量增加了约 &lt;strong&gt;0.5MB&lt;/strong&gt;，主要因类加载数量增加（从 24831 个增加到 24834 个）。&lt;/li&gt;
&lt;li&gt;增幅很小，属于正常的运行时类加载行为。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;4. 线程（Thread Memory）&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;第一次 (单位: KB)&lt;/th&gt;
&lt;th&gt;第二次 (单位: KB)&lt;/th&gt;
&lt;th&gt;差值 (单位: KB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reserved&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;97958&lt;/code&gt; (~95MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;91770&lt;/code&gt; (~89MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-6188&lt;/code&gt; (~6MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Committed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;10654&lt;/code&gt; (~10MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;9978&lt;/code&gt; (~9.7MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-676&lt;/code&gt; (~0.6MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;线程数&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;96&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;90&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-6&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;分析&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;线程数量从 &lt;strong&gt;96&lt;/strong&gt; 减少到 &lt;strong&gt;90&lt;/strong&gt;，线程栈内存减少约 &lt;strong&gt;6MB&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;单个线程栈平均大小保持不变，约为 &lt;strong&gt;107KB&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;5. GC 内存（Garbage Collector Memory）&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;第一次 (单位: KB)&lt;/th&gt;
&lt;th&gt;第二次 (单位: KB)&lt;/th&gt;
&lt;th&gt;差值 (单位: KB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reserved&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;310290&lt;/code&gt; (~303MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;388402&lt;/code&gt; (~379MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+78112&lt;/code&gt; (~76MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Committed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;310286&lt;/code&gt; (~303MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;388398&lt;/code&gt; (~379MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+78112&lt;/code&gt; (~76MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;分析&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GC 内存增加了 &lt;strong&gt;76MB&lt;/strong&gt;，可能是更大的堆导致 GC 数据结构（如标记表和任务队列）扩展。&lt;/li&gt;
&lt;li&gt;属于垃圾回收器调整后的正常行为。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;6. 代码缓存（Code Cache）&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;第一次 (单位: KB)&lt;/th&gt;
&lt;th&gt;第二次 (单位: KB)&lt;/th&gt;
&lt;th&gt;差值 (单位: KB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reserved&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;254760&lt;/code&gt; (~248MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;255553&lt;/code&gt; (~249MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+793&lt;/code&gt; (~0.7MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Committed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;95700&lt;/code&gt; (~93MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;97913&lt;/code&gt; (~96MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+2213&lt;/code&gt; (~2MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;分析&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;代码缓存增加了约 &lt;strong&gt;2MB&lt;/strong&gt;，与 JIT 编译的新代码有关。&lt;/li&gt;
&lt;li&gt;增幅较小，对整体内存影响可以忽略。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;7. Native Memory Tracking (NMT)&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;第一次 (单位: KB)&lt;/th&gt;
&lt;th&gt;第二次 (单位: KB)&lt;/th&gt;
&lt;th&gt;差值 (单位: KB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reserved&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;8483&lt;/code&gt; (~8MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;8535&lt;/code&gt; (~8MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+52&lt;/code&gt; (~0MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Committed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;8483&lt;/code&gt; (~8MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;8535&lt;/code&gt; (~8MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+52&lt;/code&gt; (~0MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;分析&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native Memory Tracking 使用量略有增加，但变化非常小，仅为 &lt;strong&gt;52KB&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;8. 其他部分&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;第一次 (单位: KB)&lt;/th&gt;
&lt;th&gt;第二次 (单位: KB)&lt;/th&gt;
&lt;th&gt;差值 (单位: KB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Other&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;16890&lt;/code&gt; (~16MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;16849&lt;/code&gt; (~16MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-41&lt;/code&gt; (~0MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Symbol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;41301&lt;/code&gt; (~40MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;41308&lt;/code&gt; (~40MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+7&lt;/code&gt; (~0MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metaspace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;132121&lt;/code&gt; (~129MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;132158&lt;/code&gt; (~129MB)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+37&lt;/code&gt; (~0MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;分析&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;这些部分的变化非常小，可以忽略。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;整体对比总结&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;去除新增的堆内存后，其余部分内存的变化情况如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GC 内存&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;增加了约 &lt;strong&gt;76MB&lt;/strong&gt;，主要与更大的堆导致 GC 数据结构扩展有关。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;线程内存&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;减少了约 &lt;strong&gt;6MB&lt;/strong&gt;，线程数量减少了 &lt;strong&gt;6 个&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;元空间和类加载器&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;增加了约 &lt;strong&gt;0.5MB&lt;/strong&gt;，与加载更多类（增加 4 个类）有关。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;代码缓存&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;增加了约 &lt;strong&gt;2MB&lt;/strong&gt;，与 JIT 编译的新代码有关。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Native Memory Tracking 和其他部分&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;增加或减少幅度极小，影响可以忽略。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;关键结论&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;主要变化来源&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GC 内存&lt;/strong&gt; 是增长的主要来源（+76MB）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;线程栈内存&lt;/strong&gt; 减少了约 &lt;strong&gt;6MB&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;总体变化幅度较小&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;去除堆内存的增加后，整体的额外内存变化约为 &lt;strong&gt;+70MB&lt;/strong&gt;，属于正常的运行时动态调整。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优化方向&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GC 内存优化&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;如果需要降低 GC 开销，可以使用 G1GC 或调整 GC 的内存分配策略。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;线程优化&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;避免多余线程的创建，进一步优化线程栈的内存分配。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;总的来说，堆外内存的变化幅度很小，对应用整体内存影响有限。&lt;/p&gt;

</description>
      <category>jvm</category>
      <category>nmt</category>
    </item>
    <item>
      <title>JVM内存使用超出堆内存限制的问题排查</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Tue, 19 Nov 2024 08:14:00 +0000</pubDate>
      <link>https://forem.com/truman_999999999/jvmnei-cun-shi-yong-chao-chu-dui-nei-cun-xian-zhi-de-wen-ti-pai-cha-26ph</link>
      <guid>https://forem.com/truman_999999999/jvmnei-cun-shi-yong-chao-chu-dui-nei-cun-xian-zhi-de-wen-ti-pai-cha-26ph</guid>
      <description>&lt;p&gt;启动命令&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-Xms8G -Xmx8G -Xmn4G -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParallelGC&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes监控中的内存使用情况&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqlqetb1iet8jqb66cru.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqlqetb1iet8jqb66cru.png" alt="内存使用" width="800" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;根据启动命令的配置和 Kubernetes 显示的内存占用，出现了 JVM 内存（堆和非堆内存）与实际进程内存占用不一致 的现象。&lt;/p&gt;
&lt;h3&gt;
  
  
  JVM 配置
&lt;/h3&gt;
&lt;h4&gt;
  
  
  堆内存
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;最小堆大小（-Xms）&lt;/strong&gt;：8GB
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;最大堆大小（-Xmx）&lt;/strong&gt;：8GB
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;年轻代大小（-Xmn）&lt;/strong&gt;：4GB
（年轻代是堆的一部分）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  元空间（Metaspace）
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;初始元空间大小（-XX:MetaspaceSize）&lt;/strong&gt;：256MB
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;最大元空间大小（-XX:MaxMetaspaceSize）&lt;/strong&gt;：256MB
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  垃圾回收器
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GC 类型&lt;/strong&gt;：Parallel GC
（吞吐量优先，但会占用更多内存来优化 GC 性能）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Kubernetes 监控显示
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;应用占用 &lt;strong&gt;9.4GB&lt;/strong&gt; 内存，超出配置的 &lt;strong&gt;8GB&lt;/strong&gt; 堆大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  内存占用组成
&lt;/h3&gt;

&lt;p&gt;JVM 应用的实际内存占用不仅包括堆内存，还包括以下部分：&lt;/p&gt;
&lt;h4&gt;
  
  
  1. 堆内存（Heap Memory）
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;定义为 &lt;code&gt;-Xms&lt;/code&gt; 和 &lt;code&gt;-Xmx&lt;/code&gt; 的 &lt;strong&gt;8GB 堆内存&lt;/strong&gt;。
&lt;/li&gt;
&lt;li&gt;组成部分：

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;年轻代&lt;/strong&gt;：配置为 &lt;code&gt;-Xmn=4G&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;老年代&lt;/strong&gt;：存放从年轻代晋升的长生命周期对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;
  
  
  2. 非堆内存（Non-Heap Memory）
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;元空间（Metaspace）&lt;/strong&gt;：
配置了最大 256MB (&lt;code&gt;-XX:MaxMetaspaceSize=256MB&lt;/code&gt;)，但通常会稍微超过此值。
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;代码缓存区（Code Cache）&lt;/strong&gt;：
存储 JIT 编译后的代码，大小随 JIT 编译器的使用而增长。
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;线程栈内存（Thread Stack Memory）&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;每个线程分配的栈内存由 &lt;code&gt;-Xss&lt;/code&gt; 配置（默认 1MB）。
&lt;/li&gt;
&lt;li&gt;线程数量较多时，线程栈内存可能占用大量内存。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;
  
  
  3. 堆外内存（Direct Memory）
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;使用 &lt;strong&gt;&lt;code&gt;ByteBuffer.allocateDirect&lt;/code&gt;&lt;/strong&gt; 或 NIO 时分配的直接内存。
&lt;/li&gt;
&lt;li&gt;默认情况下，直接内存大小与最大堆大小（&lt;code&gt;-Xmx&lt;/code&gt;）相同，可通过 &lt;code&gt;-XX:MaxDirectMemorySize&lt;/code&gt; 限制。
&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;
  
  
  4. GC 线程和内部内存
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GC 线程&lt;/strong&gt;：
Parallel GC 垃圾回收线程会占用本地内存和线程栈空间。
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;工作缓冲区&lt;/strong&gt;：
Parallel GC 会分配额外的工作缓冲区，用于垃圾回收操作，占用额外内存。&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;
  
  
  5. C 库或第三方库的内存
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JNI 调用的本地库&lt;/strong&gt;：
通过 JNI 调用的库（如 Netty、OpenSSL 等）可能分配堆外内存。
&lt;/li&gt;
&lt;li&gt;这些内存由本地代码管理，不受 JVM 的直接控制。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  可能原因分析
&lt;/h3&gt;

&lt;p&gt;根据当前情况，以下是造成 Kubernetes 显示内存占用高于 JVM 配置的主要原因：&lt;/p&gt;
&lt;h4&gt;
  
  
  1. 线程栈内存消耗
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;默认线程栈大小&lt;/strong&gt;：1MB（通过 &lt;code&gt;-Xss&lt;/code&gt; 配置）。
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;影响&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;如果应用使用大量线程（如处理高并发请求的线程池），线程栈内存会显著增加内存占用。
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;假设线程数量为 1000&lt;/strong&gt;，线程栈内存占用约为：
&lt;code&gt;1MB * 1000 = 1GB&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;
  
  
  2. 堆外内存（Direct Memory）
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;堆外内存的分配&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;默认与最大堆大小一致（8GB）。&lt;/li&gt;
&lt;li&gt;通常由 NIO 或框架（如 Netty）分配。
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;影响&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;操作系统可能为堆外内存保留虚拟地址空间，即使实际使用未达到上限。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;
  
  
  3. Parallel GC 的额外内存需求
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GC 内存使用&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;Parallel GC 的多线程操作会分配额外内存用于内部数据结构（如标记、复制和整理）。
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;相关配置&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;GC 线程数与 CPU 核心数相关，通过 &lt;code&gt;ParallelGCThreads&lt;/code&gt; 调整。
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;影响&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;如果垃圾回收线程过多，内存占用会显著增加。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;
  
  
  4. 元空间和代码缓存区增长
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;元空间的增长&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;虽然配置了最大 256MB (&lt;code&gt;-XX:MaxMetaspaceSize=256MB&lt;/code&gt;)，但 JVM 会动态分配更多内存用于类加载器和其他用途。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;代码缓存区&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;用于存储 JIT 编译后的代码，随着运行时间增加可能增长。
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;影响&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;元空间和代码缓存区的增长可能导致非堆内存膨胀，进一步提升内存占用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  排查方法
&lt;/h3&gt;

&lt;p&gt;以下方法可以帮助确认内存使用的具体来源：&lt;/p&gt;
&lt;h4&gt;
  
  
  1. 使用 Native Memory Tracking (NMT)
&lt;/h4&gt;
&lt;h5&gt;
  
  
  功能
&lt;/h5&gt;

&lt;p&gt;NMT 可以精确追踪 JVM 内存分配，帮助识别内存的分布和使用情况。&lt;/p&gt;
&lt;h5&gt;
  
  
  启用 NMT
&lt;/h5&gt;

&lt;p&gt;在 JVM 启动参数中添加以下配置：&lt;br&gt;&lt;br&gt;
&lt;code&gt;-XX:NativeMemoryTracking=summary&lt;/code&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  查看内存分布
&lt;/h5&gt;

&lt;p&gt;进入容器并运行：&lt;br&gt;&lt;br&gt;
&lt;code&gt;jcmd 1 VM.native_memory summary&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
输出示例：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sh-4.4# jcmd 1 VM.native_memory summary
1:

Native Memory Tracking:

(Omitting categories weighting less than 1KB)

Total: reserved=9488872KB, committed=9037604KB
       malloc: 90692KB #530181
       mmap:   reserved=9398180KB, committed=8946912KB

-                 Java Heap (reserved=8388608KB, committed=8388608KB)
                            (mmap: reserved=8388608KB, committed=8388608KB)

-                     Class (reserved=216083KB, committed=19475KB)
                            (classes #24831)
                            (  instance classes #23390, array classes #1441)
                            (malloc=3091KB #81714)
                            (mmap: reserved=212992KB, committed=16384KB)
                            (  Metadata:   )
                            (    reserved=131072KB, committed=126208KB)
                            (    used=125492KB)
                            (    waste=716KB =0.57%)
                            (  Class space:)
                            (    reserved=212992KB, committed=16384KB)
                            (    used=15382KB)
                            (    waste=1002KB =6.12%)

-                    Thread (reserved=97958KB, committed=10654KB)
                            (thread #96)
                            (stack: reserved=97644KB, committed=10340KB)
                            (malloc=205KB #579)
                            (arena=109KB #188)

-                      Code (reserved=254760KB, committed=95700KB)
                            (malloc=7072KB #22671)
                            (mmap: reserved=247688KB, committed=88628KB)

-                        GC (reserved=310290KB, committed=310286KB)
                            (malloc=6542KB #99)
                            (mmap: reserved=303748KB, committed=303744KB)

-                  Compiler (reserved=821KB, committed=821KB)
                            (malloc=657KB #1513)
                            (arena=164KB #4)

-                  Internal (reserved=1608KB, committed=1608KB)
                            (malloc=1572KB #53569)
                            (mmap: reserved=36KB, committed=36KB)

-                     Other (reserved=16890KB, committed=16890KB)
                            (malloc=16890KB #52)

-                    Symbol (reserved=41301KB, committed=41301KB)
                            (malloc=36179KB #334650)
                            (arena=5122KB #1)

-    Native Memory Tracking (reserved=8483KB, committed=8483KB)
                            (malloc=199KB #3578)
                            (tracking overhead=8284KB)

-        Shared class space (reserved=16384KB, committed=12956KB, readonly=0KB)
                            (mmap: reserved=16384KB, committed=12956KB)

-               Arena Chunk (reserved=692KB, committed=692KB)
                            (malloc=692KB #373)

-                    Module (reserved=252KB, committed=252KB)
                            (malloc=252KB #4553)

-                 Safepoint (reserved=8KB, committed=8KB)
                            (mmap: reserved=8KB, committed=8KB)

-           Synchronization (reserved=2592KB, committed=2592KB)
                            (malloc=2592KB #25509)

-            Serviceability (reserved=18KB, committed=18KB)
                            (malloc=18KB #36)

-                 Metaspace (reserved=132121KB, committed=127257KB)
                            (malloc=1049KB #1245)
                            (mmap: reserved=131072KB, committed=126208KB)

-      String Deduplication (reserved=1KB, committed=1KB)
                            (malloc=1KB #8)

-           Object Monitors (reserved=4KB, committed=4KB)
                            (malloc=4KB #19)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;重点关注：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Thread&lt;/strong&gt;：线程栈内存。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct&lt;/strong&gt;：堆外内存。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. 检查线程数量
&lt;/h4&gt;

&lt;p&gt;进入 Pod 后，运行以下命令检查线程数：&lt;br&gt;
&lt;code&gt;ls /proc/&amp;lt;PID&amp;gt;/task | wc -l&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;输出示例&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sh-4.4# ls /proc/1/task | wc -l
116
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果线程数量较多（数百甚至上千），需要检查线程池配置，避免过多线程占用栈内存。&lt;/p&gt;

&lt;h4&gt;
  
  
  3. 检查 Direct Memory
&lt;/h4&gt;

&lt;p&gt;可以通过以下代码查看直接内存的使用情况：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System.out.println("Max Direct Memory: " + sun.misc.VM.maxDirectMemory() / (1024 * 1024) + " MB");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;或者使用 jcmd 查看堆外内存分布。&lt;/p&gt;

&lt;h4&gt;
  
  
  4. 启用 GC 日志
&lt;/h4&gt;

&lt;p&gt;在 JVM 启动参数中添加：&lt;br&gt;
&lt;code&gt;-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/tmp/gc.log&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;分析垃圾回收日志，确认是否有过多的垃圾回收线程或堆外内存清理未及时释放。&lt;/p&gt;

&lt;h4&gt;
  
  
  优化建议
&lt;/h4&gt;

&lt;h5&gt;
  
  
  1. 减少线程栈内存
&lt;/h5&gt;

&lt;p&gt;设置更小的线程栈大小（例如 512KB）：&lt;br&gt;
-&lt;code&gt;Xss512k&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这可以显著减少线程栈内存占用。&lt;/p&gt;

&lt;h5&gt;
  
  
  2. 限制直接内存
&lt;/h5&gt;

&lt;p&gt;设置直接内存的上限，例如 256MB：&lt;br&gt;
&lt;code&gt;-XX:MaxDirectMemorySize=256m&lt;/code&gt;&lt;/p&gt;

</description>
      <category>jvm</category>
      <category>内存超限</category>
    </item>
    <item>
      <title>JVM内存使用超出堆内存限制的原因与优化方案</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Tue, 19 Nov 2024 04:30:38 +0000</pubDate>
      <link>https://forem.com/truman_999999999/jvmnei-cun-shi-yong-chao-chu-dui-nei-cun-xian-zhi-de-yuan-yin-yu-you-hua-fang-an-46ca</link>
      <guid>https://forem.com/truman_999999999/jvmnei-cun-shi-yong-chao-chu-dui-nei-cun-xian-zhi-de-yuan-yin-yu-you-hua-fang-an-46ca</guid>
      <description>&lt;h1&gt;
  
  
  JVM内存使用超出堆内存限制的原因与优化方案
&lt;/h1&gt;

&lt;h2&gt;
  
  
  引言
&lt;/h2&gt;

&lt;p&gt;在使用 Kubernetes 部署 JVM 应用时，经常会遇到这样的问题：即使在启动命令中明确设置了 JVM 的堆内存大小（例如 8GB），但通过 Kubernetes 监控却发现应用的实际内存使用超出了设置值（如 9.4GB）。更令人困惑的是，当进一步增加堆内存时，监控显示的内存使用量也会随之增长。这种现象可能导致内存超限、Pod 重启甚至应用不可用。&lt;/p&gt;

&lt;p&gt;本文将深入分析 JVM 内存使用超出堆内存限制的原因，并提供针对性的优化方案，帮助开发者在实际项目中更高效地管理和优化内存使用。&lt;/p&gt;




&lt;h2&gt;
  
  
  JVM内存使用组成
&lt;/h2&gt;

&lt;p&gt;JVM 的内存使用不仅仅包括堆内存，还涉及多个部分：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;堆内存（Heap Memory）&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;用于存储 Java 对象，大小由 &lt;code&gt;-Xmx&lt;/code&gt; 和 &lt;code&gt;-Xms&lt;/code&gt; 参数设置。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;堆外内存（Off-Heap Memory）&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;通过 &lt;code&gt;DirectByteBuffer&lt;/code&gt; 或框架（如 Netty）分配的直接内存。&lt;/li&gt;
&lt;li&gt;默认最大值与物理内存相关，可通过 &lt;code&gt;-XX:MaxDirectMemorySize&lt;/code&gt; 调整。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;本地方法区（Native Memory）&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;包括线程栈（Thread Stack）、元空间（Metaspace）等。&lt;/li&gt;
&lt;li&gt;线程栈大小由 &lt;code&gt;-Xss&lt;/code&gt; 参数决定，线程数越多，消耗内存越大。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;垃圾回收器的内存开销&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;垃圾回收器（如 G1 GC）需要额外内存管理分代和回收任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;容器内存开销&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes 监控容器的 RSS（Resident Set Size），包括 JVM 使用的内存和容器级别的内存开销。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  常见问题分析
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kubernetes 监控的内存超出 JVM 堆内存限制&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes 监控的是容器内所有内存，而不仅仅是 JVM 堆内存。&lt;/li&gt;
&lt;li&gt;堆外内存、线程栈等也会计入总内存使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;增加堆内存后内存使用量同步增长&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JVM 的非堆内存使用量可能与堆内存设置比例相关（如垃圾回收器的内存需求）。&lt;/li&gt;
&lt;li&gt;应用本身可能存在内存泄漏或非堆内存过度使用的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  解决方案
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 优化 JVM 内存设置
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;限制堆外内存&lt;/strong&gt;：通过 &lt;code&gt;-XX:MaxDirectMemorySize&lt;/code&gt; 设置堆外内存的最大值。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;调整线程栈大小&lt;/strong&gt;：通过 &lt;code&gt;-Xss&lt;/code&gt; 参数减少线程栈内存（默认 1MB，可适当调低）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;合理设置堆大小&lt;/strong&gt;：根据应用实际需求调整 &lt;code&gt;-Xmx&lt;/code&gt; 和 &lt;code&gt;-Xms&lt;/code&gt;，避免过度分配。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. 加强内存监控
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;使用 JVM 自带工具&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;jstat&lt;/code&gt;：监控堆内存使用和垃圾回收信息。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jmap&lt;/code&gt;：生成堆快照，分析内存分配。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;引入第三方工具&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;VisualVM、JProfiler 等深入分析内存分配与线程使用情况。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. 调整 Kubernetes 配置
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;配置内存限制&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;在 Pod 的 &lt;code&gt;resources&lt;/code&gt; 中设置合理的 &lt;code&gt;memoryLimit&lt;/code&gt; 和 &lt;code&gt;memoryRequest&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;监控探针优化&lt;/strong&gt;：

&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;liveness&lt;/code&gt; 和 &lt;code&gt;readiness&lt;/code&gt; 探针中加入内存使用监控，及时发现异常。&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. 优化代码逻辑
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;检查框架或库是否大量使用堆外内存（如 Netty、RocksDB 等）。&lt;/li&gt;
&lt;li&gt;避免过多线程创建，优化线程池配置。&lt;/li&gt;
&lt;li&gt;定期使用内存泄漏检测工具，如 Eclipse MAT。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  总结
&lt;/h2&gt;

&lt;p&gt;JVM 内存使用超出堆内存限制的现象常见于复杂部署环境中，其原因可能涉及堆外内存、本地方法区、线程开销等多个因素。通过合理优化 JVM 参数、监控内存使用和调整 Kubernetes 配置，可以有效降低内存使用并提高系统稳定性。&lt;/p&gt;

&lt;p&gt;在实际项目中，建议结合工具深入分析内存分配，定位问题来源，以确保 JVM 应用在 Kubernetes 中的高效运行。&lt;/p&gt;

</description>
      <category>jvm</category>
      <category>内存限制</category>
    </item>
    <item>
      <title>问题记录</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Thu, 17 Oct 2024 06:39:44 +0000</pubDate>
      <link>https://forem.com/truman_999999999/wen-ti-ji-lu-3fbo</link>
      <guid>https://forem.com/truman_999999999/wen-ti-ji-lu-3fbo</guid>
      <description>&lt;p&gt;&lt;strong&gt;Question 1&lt;/strong&gt;&lt;br&gt;
web 版本 1.0.1 不可以，1.0.2 就可以访问&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 2&lt;/strong&gt;&lt;br&gt;
1/18 日用户模块用户添加和登陆出现问题，手机号没有查询到，导致重复添加的失败：「数据库主从同步出现问题」&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 3&lt;/strong&gt;&lt;br&gt;
Nacos 2.2.3 服务注册不上的问题，最后通过查看 nacos 的源码，发现配置中的 namespace 已经不是旧版本的命名空间了，需要是命名空间的 ID。在 nacos 的控制台显示中，把 namespace 归为 Namespace ID, namespace showname 归为 namespaces。所以配置文件中的 namespace 要和 nacos 命名中对应的 Namespace ID 一致&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 4&lt;/strong&gt;&lt;br&gt;
发现一个问题， 当方法添加 @Cacheable 和@TimeToLive。缓存注解的时候， 如果方法抛出 businessexception，之前的切面无法捕获并正常返回异常信息。&lt;br&gt;
异常触发点：WarmReplayCacheInterceptorPostProcessor.class -&amp;gt; in.getMethod().invoke(advice, in.getArguments());&lt;br&gt;
异常被捕获，此时的异常为 InvocationTargetException，包裹着 BusinessException。&lt;br&gt;
异常中间流转的过程还没看，最后一次流转在 CglibAopProxy.class -&amp;gt;throw new UndeclaredThrowableException(ex);&lt;br&gt;
到达项目中的异常统一处理方法 handleException，进入方法的异常为 UndeclaredThrowableException，在目前的方法处理中，是没有对 UndeclaredThrowableException 进行处理的，对此异常进行处理就行。&lt;br&gt;
UndeclaredThrowableExceptione.getCause()这是 InvocationTargetException&lt;br&gt;
UndeclaredThrowableExceptione.getCause().getCause()取出 BusinessException&lt;br&gt;
然后按照 BusinessException 正常处理就行了&lt;/p&gt;

&lt;p&gt;目前的问题是：1 没有加 else，返回 500；2:Throwable cause = e.getCause().getCause();&lt;br&gt;
这个 cause 的值会随机出现两种样态&lt;br&gt;
1:class com.qis.base.exception.BusinessException&lt;br&gt;
2:class com.qis.base.exception.BusinessException message: 报错信息&lt;/p&gt;

&lt;p&gt;在最新的测试中，不使用 instanceof 判断异常的类型，使用 cause.getClass().equals(BusinessException.class)进行判断，判断为 BusinessException 更加准确&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 5&lt;/strong&gt;&lt;br&gt;
live_room_online_user 表，非查询操作非常频繁。执行清除直播间过期用户数据时，容易出现死锁问题。具体的操作是删除最后更新时间在 3 分钟前的数据。&lt;br&gt;
第一次: 使用 FOR UPDATE 语句，这将为要删除的行获取排它锁，防止其他事务在删除过程中修改它们。&lt;br&gt;
第二次: 分段删除，先查出主键数据，例如 ID 的值，再根据 ID 进行删除操作。如果删除的数据量太大，可以分段进行，从而降低死锁的可能性。&lt;br&gt;
第三次，批量添加中使用 ON DUPLICATE KEY UPDATE，在遇到唯一键冲突时进行update操作&lt;br&gt;
第四次，排查全部相关的DML，确保没有其他修改或者删除操作影响批量添加，事物propagation使用REQUIRE_NEW&lt;br&gt;
第五次，使用insert ignore 替代 ON DUPLICATE KEY UPDATE，容忍个别数据未更新，避免锁的数据太多，容易死锁&lt;br&gt;
第六次，不使用Transaction注解，改为手动控制；SQL执行操作还是使用MyBatis。批量更新的数组进行拆分，更新更小批量的数据。使用SqlSessionFactory进行更精确的手动控制，&lt;br&gt;
关闭自动提交事物，每次小批量更新数据后手动提交事物，减少事物的长度，手动增加重试次数，第一次失败等待200ms，第二次失败等待400ms，第三次失败等待800ms，继续失败，丢弃数据&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 6&lt;/strong&gt;&lt;br&gt;
Kubernetes 网关出现问题，经运维修复后，部分前端服务调用后端服务还是会出现 499 httpcode，导致前端服务无法正常提供。后端排查后所有服务无异常，可以使用 postman 调用，在 pod 的控制台中调用其他服务的测试接口，正常返回，可以确认后端服务无异常。解决办法是把前端有问题的服务重启，这样服务就恢复了。推测原因可能是网关问题修复后，Kubernetes 中的服务需要重启，在网关中重新注册，才能恢复正常。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 7&lt;/strong&gt;&lt;br&gt;
Kubernetes prometheus 容器挂掉，无法提供服务。原因是什么？集群进行压力测试，导致资源被占用，prometheus 容器无法获取到足够的资源，服务失败，只能运维人员手动重启&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 8&lt;/strong&gt;&lt;br&gt;
查询当天足篮类型一级联赛的所有比赛，查询超时。原因是在 WHERE 语句中使用了 TO_DAYS 函数，导致索引失效，全表扫描。比赛时间本身是有索引的，所以将函数去除，改为传值比较，恢复索引。&lt;br&gt;
并不是所有情况下在 WHERE 子句中使用函数都会导致索引失效。是否失效取决于函数的类型、函数的作用对象以及数据库引擎的优化能力。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;会导致索引失效的情况
对列进行转换或计算的函数：如果在 WHERE 子句中对索引列使用了改变其原始值的函数，那么通常会导致索引失效。例如：
TO_DAYS(column)：将日期时间转换为天数。
UPPER(column)：将字符串转换为大写。
ABS(column)：求绝对值。
这些函数都改变了列的原始值，数据库无法直接利用原始值上的索引，因此会导致索引失效，可能导致全表扫描。&lt;/li&gt;
&lt;li&gt;不会导致索引失效的情况
不改变列值的函数：有些函数不会改变列的实际值或可以被数据库优化器识别，从而不会导致索引失效。例如：
范围查询：使用范围运算符（&amp;gt;、&amp;lt;、BETWEEN）通常不会导致索引失效
函数作用在常量或表达式上 如果函数只作用在常量或表达式上，而不是列上，索引仍然可以有效
数据库优化器的特殊优化 在一些数据库引擎中，某些函数或操作符经过优化后，仍然可以使用索引。例如在 MySQL 中，LIKE 'abc%' 可以利用索引，而 LIKE '%abc' 则不行。&lt;/li&gt;
&lt;li&gt;数据库支持的函数索引 支持基于函数结果的索引（称为函数索引或表达式索引）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Question 9&lt;/strong&gt;&lt;br&gt;
安卓安装包文件体积 100 多 MB，文件使用分片上传的方式传到华为云的 OBS 服务器。在分片上传的请求中，有些请求会出现 504 Gateway Time-out 。&lt;br&gt;
在不使用 VPN 的情况下，请求链路从本地网络到达香港的华为云，从华为云到达项目的 kubernetes 环境，从 Kubernetes 集群进入，到达前端的管理后台服务，前端的管理后台服务在集群内调用后端的 backend 服务，backend 服务再调用 support 服务，在 support 服务中将文件上传到华为的 OBS 服务器。&lt;/p&gt;

&lt;p&gt;第一次解决：推测是 backend 和 support 远程调用服务请求用时太长，导致请求中断连接。在项目通用配置中，设置 backend 和 support 远程调用服务的 FeignHttpClientProperties，将请求超时时间设置为 8 秒。需要设置 &lt;a class="mentioned-user" href="https://dev.to/primary"&gt;@primary&lt;/a&gt; ，不然会报错 KubernetesFeignClientConfiguration required a single bean, but 2 were found，一个是默认的(org.springframework.cloud.openfeign.support.FeignHttpClientProperties)，一个是自己定义的。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    @Bean
    @Primary
    @ConditionalOnExpression("'${spring.application.name:undefined}'=='backend' or '${spring.application.name:undefined}'=='support-rpc'")
    @ConfigurationProperties(prefix = "feign.httpclient")
    public FeignHttpClientProperties feignHttpClientProperties() {
        FeignHttpClientProperties properties = new FeignHttpClientProperties();
        properties.setConnectionTimeout(8000);  // Set connection timeout to 8000 ms
        return properties;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;第二次解决：在将配置发布到生产环境后，依旧会发生问题，说明问题没有真的解决，于是开启日志的逐行分析。在出现 504 请求后，推测分片上传的文件的 ID，根据文件 ID 查询后端的日志，将请求达到时间与上传成功时间记录，正常的请求和异常的请求分别记录三条。经过分析后，正常的请求用时一般在 2 秒以内，异常的请求在 62 到 63 秒之间。可以确定是异常的请求用时太长，客户端主动断开链接。为什么请求用时太长的问题，这个很难排查出一个确定的原因，可以推测是在香港华为云上的项目 kubernetes 环境与华为云 OBS 之间网络会偶尔出现一点问题，导致上传时间用时 62 到 63 秒。不过问题的直接原因是前端的管理后台服务中断了用时超长的请求，具体还是要前段同事去解决问题。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 10&lt;/strong&gt;&lt;br&gt;
项目优化的方式&lt;br&gt;
API 请求分析，GC 信息记录，Kubernetes 服务实例的信息记录，比如内存使用记录和 CPU 使用记录&lt;br&gt;
ZGC jdk 21.0.4: &lt;a href="https://dev.to/truman_999999999/zgc-major-collection-proactive-ri-zhi-xiang-jie-453g"&gt;ZGC Major Collection (Proactive) 日志详解&lt;/a&gt;，&lt;a href="https://dev.to/truman_999999999/zgcde-liang-chong-zhu-yao-de-la-ji-shou-ji-lei-xing-zgcde-liang-chong-zhu-yao-de-gclei-xing-3aod"&gt;ZGC 的两种主要的垃圾收集类型&lt;/a&gt;&lt;br&gt;
ParallelGC jdk 1.8: &lt;a href="https://dev.to/truman_999999999/parallelgc-ri-zhi-xiang-jie-1hl7"&gt;ParallelGC 日志详解&lt;/a&gt;&lt;br&gt;
FullGCListener: &lt;a href="https://dev.to/truman_999999999/fullgclistener-4p0d"&gt;FullGCListener&lt;/a&gt;&lt;br&gt;
SQL 耗时跟踪: &lt;a href="https://dev.to/truman_999999999/sql-hao-shi-gen-zong-5fc5"&gt;SQL 耗时跟踪&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 11&lt;/strong&gt;&lt;br&gt;
Kubernetes 中，流水线正常启动，但是容器无法正常启动，一闪而过 OOMKILLED，然后变成启动失败的重启状态。&lt;br&gt;
原因是容器初始限制最多 2G 内存，但是 JAVA 启动命令请求 4G，年轻代设置 2G，内存请求达到限制门槛，但是还不够正常启动，于是发生 OOMKILLED，容器启动失败。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 12&lt;/strong&gt;&lt;br&gt;
线上服务报错，错误信息 &lt;code&gt;java.lang.StackOverflowError&lt;/code&gt; ，已知设置的运行内存和 metaspace 空间大小都是充足的，查看报错日志，大量的阿里巴巴 &lt;code&gt;fastjson&lt;/code&gt; 报错。 &lt;code&gt;fastjson&lt;/code&gt; 版本为 &lt;code&gt;1.2.60&lt;/code&gt; ，最近的代码变动是使用 &lt;code&gt;fastjson&lt;/code&gt; 中的 &lt;code&gt;JSON.toJSONString&lt;/code&gt; 方法，推测是 &lt;code&gt;JSON.toJSONString&lt;/code&gt; 方法的循环递归调用遇见未知问题，导致发生 &lt;code&gt;java.lang.StackOverflowError&lt;/code&gt;，将代码变动回滚，重新发布，错误消除。&lt;/p&gt;

</description>
    </item>
    <item>
      <title>SQL 耗时跟踪</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Sat, 28 Sep 2024 09:39:02 +0000</pubDate>
      <link>https://forem.com/truman_999999999/sql-hao-shi-gen-zong-5fc5</link>
      <guid>https://forem.com/truman_999999999/sql-hao-shi-gen-zong-5fc5</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Slf4j
public class InspectSqlStackFilter extends FilterEventAdapter {
    private final Set&amp;lt;String&amp;gt; firstStacks = new ConcurrentHashSet&amp;lt;&amp;gt;();

    @Override
    public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            boolean result = super.statement_execute(chain, statement, sql);
            doLogAsExecute(chain, statement, sql, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql, int autoGeneratedKeys) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            boolean result = super.statement_execute(chain, statement, sql, autoGeneratedKeys);
            doLogAsExecute(chain, statement, sql, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql, int[] columnIndexes) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            boolean result = super.statement_execute(chain, statement, sql, columnIndexes);
            doLogAsExecute(chain, statement, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql, String[] columnNames) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            boolean result = super.statement_execute(chain, statement, sql, columnNames);
            doLogAsExecute(chain, statement, sql, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public int[] statement_executeBatch(FilterChain chain, StatementProxy statement) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            int[] result = super.statement_executeBatch(chain, statement);
            doLogAsExecute(chain, statement, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            ResultSetProxy result = super.statement_executeQuery(chain, statement, sql);
            doLogAsExecute(chain, statement, sql, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public int statement_executeUpdate(FilterChain chain, StatementProxy statement, String sql) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            int result = super.statement_executeUpdate(chain, statement, sql);
            doLogAsExecute(chain, statement, sql, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public int statement_executeUpdate(FilterChain chain, StatementProxy statement, String sql, int autoGeneratedKeys) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            int result = super.statement_executeUpdate(chain, statement, sql, autoGeneratedKeys);
            doLogAsExecute(chain, statement, sql, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public int statement_executeUpdate(FilterChain chain, StatementProxy statement, String sql, int[] columnIndexes) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            int result = super.statement_executeUpdate(chain, statement, sql, columnIndexes);
            doLogAsExecute(chain, statement, sql, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public int statement_executeUpdate(FilterChain chain, StatementProxy statement, String sql, String[] columnNames) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            int result = super.statement_executeUpdate(chain, statement, sql, columnNames);
            doLogAsExecute(chain, statement, sql, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public boolean preparedStatement_execute(FilterChain chain, PreparedStatementProxy statement) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            boolean result = super.preparedStatement_execute(chain, statement);
            doLogAsExecute(chain, statement, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public ResultSetProxy preparedStatement_executeQuery(FilterChain chain, PreparedStatementProxy statement) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            ResultSetProxy result = super.preparedStatement_executeQuery(chain, statement);
            doLogAsExecute(chain, statement, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    @Override
    public int preparedStatement_executeUpdate(FilterChain chain, PreparedStatementProxy statement) throws SQLException {
        try {
            long startTime = System.currentTimeMillis();
            int result = super.preparedStatement_executeUpdate(chain, statement);
            doLogAsExecute(chain, statement, System.currentTimeMillis() - startTime);
            return result;
        } catch (SQLException ex) {
            throw unwrapException(ex, chain, statement);
        }
    }

    private SQLException unwrapException(SQLException ex, FilterChain chain, StatementProxy statement){
        String message = String.format("\nurl -&amp;gt; %s\nsql -&amp;gt; %s\nerros-&amp;gt;%s", chain.getDataSource().getUrl(), statement.getLastExecuteSql(), ex.getMessage());
        if (ex instanceof CommunicationsException) {
            Throwable cause = ex.getCause();
            if (cause instanceof SocketTimeoutException) {
                return new SQLException(message, cause);
            } else if (cause instanceof CJCommunicationsException) {
                if (cause.getCause() instanceof SocketTimeoutException) {
                    return new SQLException(message, cause.getCause());
                }
            }
        }
        return new SQLException(message, ex.getCause());
    }

    private void doLogAsExecute(FilterChain chain, StatementProxy statement, long duration) {
        this.doLogAsExecute(chain, statement, null, duration);
    }


    public static void main(String[] args) {
    }

    private void doLogAsExecute(FilterChain chain, StatementProxy statement, String sql, long duration) {


        String applicationName = SpringContextHolder.getApplicationName();
        if (duration &amp;lt; 1000) {
            return;
        }

        if (
                applicationName.startsWith("example-live") ||
                applicationName.startsWith("example-score") ||
                applicationName.startsWith("example-news") ||
                applicationName.startsWith("example-uc") ||
                applicationName.startsWith("example-support") ||
                applicationName.startsWith("example-sms") ||
                applicationName.startsWith("example-integral")
        ) {

            LinkedList&amp;lt;String&amp;gt; lines = new LinkedList&amp;lt;&amp;gt;();

            LinkedList&amp;lt;String&amp;gt; stacks = new LinkedList&amp;lt;&amp;gt;();

            String firstStack = null;
            StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace();
            for (int i = 15; i &amp;lt; stackTraces.length - 1; i++) {
                StackTraceElement stackTrace = stackTraces[i];
                String className = stackTrace.getClassName();
                if (className.startsWith("com.example.base")) {
                    continue;
                }

                if (className.startsWith("com.example")) {
                    if (stackTrace.getLineNumber() == -1) {
                        continue;
                    }
                    int lastPoint = className.lastIndexOf(".");
                    String javaFileName = className.substring(lastPoint + 1);
                    String line = String.format("%s.%s(%s.java:%s)",
                            className, stackTrace.getMethodName(), javaFileName, stackTrace.getLineNumber());

                    if (firstStack == null) {
                        firstStack = line;
                        if (firstStacks.contains(firstStack)) {
                            return;
                        }
                        firstStacks.add(firstStack);
                    }

                    lines.add("\t" + line);
                    stacks.add(line);
                }
                if (className.startsWith("org.springframework.web")) {
                    break;
                }
            }
            try {
                if (lines.isEmpty()) {
                    return;
                }

                lines.addFirst("\nstackTraces: \t");

                if (sql == null) {
                    sql = statement.getLastExecuteSql();
                }
                Map&amp;lt;Integer, JdbcParameter&amp;gt; parameters = statement.getParameters();
                if (!parameters.isEmpty()) {
                    Map&amp;lt;Integer, Object&amp;gt; hashMap = new HashMap&amp;lt;&amp;gt;();
                    for (Map.Entry&amp;lt;Integer, JdbcParameter&amp;gt; entry : parameters.entrySet()) {
                        hashMap.put(entry.getKey(), entry.getValue().getValue());
                    }
                    lines.addFirst("parameters: " + hashMap);
                }
                if (sql != null) {
                    sql = SQLUtils.format(sql, DbType.mysql);
                    lines.addFirst("sql: " + sql);
                    SpringContextHolder.publishEvent(new InspectSqlEvent(sql.replaceAll("\\s+", " "), stacks));
                }

                lines.addFirst("url: " + chain.getDataSource().getUrl());
                lines.addFirst("duration: " + duration + "ms");


                lines.addLast("\n\n-----------------------dividing---------------------\n\n");
                File file = new File("sql_trace_info.txt");
                FileUtils.writeLines(file, "UTF-8", lines, true);

            } catch (IOException ignore) {
            }
        }
    }

}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>java</category>
      <category>mysql</category>
      <category>trace</category>
    </item>
  </channel>
</rss>
