<?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>DEV Community: Truman</title>
    <description>The latest articles on DEV Community by Truman (@truman_999999999).</description>
    <link>https://dev.to/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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1608677%2Fca7ecbb1-625e-4b76-bcc9-6e18b3e03d21.png</url>
      <title>DEV Community: Truman</title>
      <link>https://dev.to/truman_999999999</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/truman_999999999"/>
    <language>en</language>
    <item>
      <title>From the Fantasy of Local Models to a Truly Controllable Sports Agent</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Wed, 08 Jul 2026 14:14:27 +0000</pubDate>
      <link>https://dev.to/truman_999999999/from-the-fantasy-of-local-models-to-a-truly-controllable-sports-agent-3glo</link>
      <guid>https://dev.to/truman_999999999/from-the-fantasy-of-local-models-to-a-truly-controllable-sports-agent-3glo</guid>
      <description>&lt;p&gt;Looking back, this project did not begin as a fully designed AI agent project. In fact, I had not even decided from the outset that I wanted to build a sports question-answering system.&lt;/p&gt;

&lt;p&gt;The original idea was much simpler.&lt;/p&gt;

&lt;p&gt;At the time, I frequently saw people on X sharing their experiences with locally deployed large language models. Local AI was advertised as having many advantages: data could remain on your own device, privacy requirements could be satisfied, you would not be restricted by the service policies of major model providers, and you would not have to worry about your account, API access, or region suddenly being restricted one day.&lt;/p&gt;

&lt;p&gt;The models might not be as capable as the best cloud-based models, but as long as they were good enough for everyday use, the idea still seemed highly attractive.&lt;/p&gt;

&lt;p&gt;I had also been following Mr. Wang from LazyCat Microserver for some time. He promoted the company’s products almost every day, which sparked my interest in the LazyCat AI Pod. Eventually, I ordered one.&lt;/p&gt;

&lt;p&gt;After receiving the device, I began experimenting with various models that could be deployed locally. Following a round of comparisons, I ultimately chose the DeepSeek R1 distilled version of Qwen 32B.&lt;/p&gt;

&lt;p&gt;To extract as much performance as possible from the hardware, I repeatedly adjusted the model’s startup parameters based on recommendations from GPT. I pushed VRAM allocation, context length, concurrency, and inference settings as close as possible to the maximum that the hardware could handle at the time.&lt;/p&gt;

&lt;p&gt;When I first got the model running, I was naturally a little excited. After all, a model with tens of billions of parameters was genuinely running on a device that I owned. It felt as though I finally had an AI that belonged entirely to me.&lt;/p&gt;

&lt;p&gt;But after using it a few times, my most immediate reaction was:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fuck, something was wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Its responses were slow, and its knowledge was outdated. More importantly, there was an enormous gap between it and the GPT experience I was used to when it came to understanding questions, maintaining context, organizing answers, and handling complex tasks.&lt;/p&gt;

&lt;p&gt;It was no longer simply a matter of being “slightly less capable” or “slightly slower.” They were not even operating in the same dimension.&lt;/p&gt;

&lt;p&gt;That was the first reality I encountered during this project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Being able to run a model does not mean that the model has practical value.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Local deployment solves questions about where the model runs, who controls the data, and how dependent you are on a vendor. It does not automatically solve problems involving model capability, knowledge freshness, contextual understanding, reasoning quality, or response speed.&lt;/p&gt;

&lt;p&gt;When the model itself is clearly not capable enough, the idea of “local AI freedom” can easily turn into an expensive, slow machine that produces answers that are not particularly useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting a Chatbot as a Way to Make Use of the Hardware
&lt;/h2&gt;

&lt;p&gt;Since I had already bought the device and successfully deployed the model, I decided to make some use of it by building at least a simple chatbot.&lt;/p&gt;

&lt;p&gt;At first, I did not seriously consider whether it had the conditions required for real-world operation. I simply added several prompt chains around the model, hoping that breaking tasks into multiple prompting stages would compensate for the model’s limited capabilities.&lt;/p&gt;

&lt;p&gt;But I quickly realized that building a general-purpose chatbot was pointless.&lt;/p&gt;

&lt;p&gt;It could never be more general than cloud models such as GPT or Gemini, nor could it compete with them in knowledge, reasoning, or response speed. Once I built it, even I found it frustrating to use.&lt;/p&gt;

&lt;p&gt;A general-purpose chatbot that I did not want to use myself was not worth continuing to develop.&lt;/p&gt;

&lt;p&gt;So I narrowed the scope and decided to build a sports scores chatbot.&lt;/p&gt;

&lt;p&gt;Compared with open-domain question answering, sports scores at least offered a clearly defined data scope, fixed rules, queryable interfaces, and relatively well-defined question types. From a product-scope perspective, it seemed more suitable for a limited local model.&lt;/p&gt;

&lt;p&gt;However, relying solely on an LLM was obviously insufficient. The model did not know the latest scores, and it should not be expected to explain every sports rule from its own memory.&lt;/p&gt;

&lt;p&gt;Following guidance from GPT, I began collecting rules and documentation for major sports and esports. I organized the material into YAML files, built a vector index with FAISS, and integrated it into the question-answering workflow.&lt;/p&gt;

&lt;p&gt;This was probably my first real exposure to the idea of retrieval augmentation.&lt;/p&gt;

&lt;p&gt;My understanding at the time was very straightforward: if the model did not know something, search the knowledge base first, then insert the retrieved results into the prompt.&lt;/p&gt;

&lt;p&gt;The idea itself was not wrong, but after running the system a few times, the problems quickly became apparent.&lt;/p&gt;

&lt;p&gt;Each vector search returned a large volume of rules data. That content could consume roughly 80% of Qwen 32B’s available input space. The remaining context might only be enough for the user’s first question.&lt;/p&gt;

&lt;p&gt;By the second turn, once the conversation history, rules content, and previous answer were all included, the system could easily exceed the context limit.&lt;/p&gt;

&lt;p&gt;That was when I truly realized:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context is not a warehouse. It is a highly limited runtime budget.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Retrieval does not mean stuffing every piece of “potentially relevant” information into the model. More retrieved content does not necessarily produce a more accurate answer.&lt;/p&gt;

&lt;p&gt;Large amounts of low-density information not only consume context, but also dilute the genuinely important details, making it even harder for an already limited model to identify what matters.&lt;/p&gt;

&lt;p&gt;Although I was already using RAG and vector retrieval, I had only completed the step of “search for it and put it into the prompt.” I had not yet truly understood context engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Second Collision with the API Knowledge Base and Vector Retrieval
&lt;/h2&gt;

&lt;p&gt;As the project continued, I began adding more sports scores APIs.&lt;/p&gt;

&lt;p&gt;These APIs were not designed specifically for LLMs. Some were used to search for competitions, while others queried fixtures, standings, lineups, match events, team information, or player details.&lt;/p&gt;

&lt;p&gt;Many of these APIs had explicit hierarchical dependencies: the output of one endpoint served as an input parameter for another.&lt;/p&gt;

&lt;p&gt;To help the system understand these interfaces, I gradually created an &lt;code&gt;api_kb.yaml&lt;/code&gt; file containing API descriptions, input parameters, output fields, enumerations, data relationships, and usage restrictions.&lt;/p&gt;

&lt;p&gt;Since I had already used FAISS, I naturally applied the same idea to the API knowledge base. I hoped that vector search could identify the APIs relevant to a user’s question, together with their query rules and required execution order.&lt;/p&gt;

&lt;p&gt;At the time, I assumed that if the API documentation was sufficiently complete and vector search could retrieve semantically similar endpoints, the LLM would be able to combine those endpoints on its own.&lt;/p&gt;

&lt;p&gt;The actual results were poor.&lt;/p&gt;

&lt;p&gt;FAISS could find API documentation that appeared relevant, but it could not reliably understand the real business dependencies between interfaces.&lt;/p&gt;

&lt;p&gt;It might retrieve an API with a similar field name while ignoring the fact that one of its parameters first had to be obtained from another API. It might also mix together interfaces intended for teams, competitions, seasons, or match details.&lt;/p&gt;

&lt;p&gt;More importantly, the retrieval results themselves were unpredictable.&lt;/p&gt;

&lt;p&gt;The same question could return different documents at different times. A slight change in intent could cause the retrieved results to drift away from what was actually needed.&lt;/p&gt;

&lt;p&gt;The final experience was that the system sometimes worked and sometimes failed completely, with no reliable way to predict which outcome would occur.&lt;/p&gt;

&lt;p&gt;The user experience was terrible.&lt;/p&gt;

&lt;p&gt;At the time, however, I did not abandon vector retrieval. I simply continued adding rules and prompts around it, adjusting similarity thresholds, and changing the number of retrieved documents, hoping that further optimization would solve the problem.&lt;/p&gt;

&lt;p&gt;Looking back, I was asking a retrieval system to take responsibility for something it was not good at.&lt;/p&gt;

&lt;p&gt;Vector retrieval is suitable for finding related knowledge. It is not suitable for independently deciding how a group of APIs with strict parameter dependencies should be executed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowledge relevance and tool executability are two entirely different problems.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Model Routing Did Not Solve the Architectural Problem
&lt;/h2&gt;

&lt;p&gt;During this process, I also integrated the Gemini API. My goal was to classify requests by complexity and route them to different models.&lt;/p&gt;

&lt;p&gt;Simple tasks could be handled by cheaper, faster models, while more complex tasks would be sent to more capable ones. In theory, this would reduce costs while improving overall response time.&lt;/p&gt;

&lt;p&gt;Later, I also integrated the LLM APIs provided by GitHub and the Grok API, further expanding the routing system in the hope that different providers could complement one another.&lt;/p&gt;

&lt;p&gt;However, LLM calls involve many unpredictable situations, particularly when local models are involved.&lt;/p&gt;

&lt;p&gt;Sometimes a request would remain unanswered for a long time. Sometimes the returned format would not meet the requirements. Sometimes the model would misunderstand the intent. Sometimes the generated JSON could not be parsed. Sometimes the service was already in an unhealthy state, while the upstream workflow continued waiting.&lt;/p&gt;

&lt;p&gt;I therefore began assigning different timeout limits to different models and added service health checks, exception handling, response-format validation, and fallback mechanisms.&lt;/p&gt;

&lt;p&gt;When a cheaper model failed, the request would switch to another model. If that model also failed, it would switch again.&lt;/p&gt;

&lt;p&gt;These features did improve availability, but they also helped me understand something:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model routing can address cost and some reliability issues, but it cannot fix an incorrect system architecture.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If intent recognition is unstable, API planning is fundamentally flawed, and the input-output boundaries are unclear, adding more models simply passes the same problem from one model to another.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision Flow and the Debt Created by Keyword Matching
&lt;/h2&gt;

&lt;p&gt;The original workflow included intent detection, rule retrieval, API retrieval, API execution, and final answer generation, but it lacked a node that was genuinely responsible for business planning.&lt;/p&gt;

&lt;p&gt;I therefore decided to add a “decision flow” node.&lt;/p&gt;

&lt;p&gt;This node needed to select the appropriate scores APIs based on user intent, process API inputs and outputs, understand dependencies between different interfaces, and determine which calls needed to run sequentially and which could run in parallel.&lt;/p&gt;

&lt;p&gt;The direction itself was correct.&lt;/p&gt;

&lt;p&gt;The problem was the implementation.&lt;/p&gt;

&lt;p&gt;While building out the decision flow, Codex chose the easiest and most direct development approach: identifying user intent through keyword matching.&lt;/p&gt;

&lt;p&gt;If the user mentioned “standings,” the system selected the standings API. If the user mentioned “lineup,” it selected the lineup API. If a particular fixed term appeared, the request entered a corresponding fixed workflow.&lt;/p&gt;

&lt;p&gt;With a small number of test cases, this approach appeared highly effective. The code was generated quickly, and the functionality could be made to work almost immediately.&lt;/p&gt;

&lt;p&gt;But as the range of question types expanded, the keyword rules grew rapidly.&lt;/p&gt;

&lt;p&gt;The same word could express different intents in different contexts. Different languages, aliases, natural expressions, and conversational follow-up questions could never be exhaustively covered through a finite list of keywords.&lt;/p&gt;

&lt;p&gt;When the user said “What about this match?”, “The second one,” “The one from earlier,” or “This Portugal match,” the system did not need to understand an isolated keyword. It needed to understand the current conversational focus, the candidate list from the previous turn, and the goal the user was continuing to pursue.&lt;/p&gt;

&lt;p&gt;Once keyword matching entered this kind of scenario, it became increasingly difficult to maintain.&lt;/p&gt;

&lt;p&gt;To patch these problems, the decision flow gradually accumulated conversational disambiguation, candidate matching, returning to the original question after candidate selection, user-focus redirection, and new-topic versus old-topic detection.&lt;/p&gt;

&lt;p&gt;The number of features continued to grow, and the code became increasingly bloated.&lt;/p&gt;

&lt;p&gt;At the time, I was still relying heavily on Codex for vibe coding. As long as the current tests passed, I deliberately ignored code structure and long-term maintainability.&lt;/p&gt;

&lt;p&gt;Those problems did not disappear.&lt;/p&gt;

&lt;p&gt;They were merely postponed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sports APIs Were Not Designed for LLMs
&lt;/h2&gt;

&lt;p&gt;Another source of complexity was that the sports scores APIs used by the project were never designed to be called by an LLM.&lt;/p&gt;

&lt;p&gt;Many endpoints required internal IDs, but ordinary users only referred to team names, league names, or particular matches. They did not know, and should not need to know, values such as &lt;code&gt;matchId&lt;/code&gt;, &lt;code&gt;leagueId&lt;/code&gt;, &lt;code&gt;seasonId&lt;/code&gt;, &lt;code&gt;teamId&lt;/code&gt;, or &lt;code&gt;playerId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before calling the actual business API, the system therefore usually needed to call a search API first, converting a team, competition, or player mentioned in natural language into an internal object.&lt;/p&gt;

&lt;p&gt;When the candidates were not unique, the system needed to present them to the user for selection. After the user selected one, the system then had to return to the original question and complete the original task, rather than stopping at “You selected the second item.”&lt;/p&gt;

&lt;p&gt;API responses could not be passed directly to the model either.&lt;/p&gt;

&lt;p&gt;Many endpoints returned large numbers of fields containing &lt;code&gt;null&lt;/code&gt;, internal statuses, irrelevant media URLs, duplicated structures, and data that had no value for the current question.&lt;/p&gt;

&lt;p&gt;Without preprocessing, the response from a single API could consume a large amount of context.&lt;/p&gt;

&lt;p&gt;As a result, the project accumulated an increasing number of pre-call and post-call processing steps: parameter completion, contextual binding, dependency-parameter extraction, removal of null-valued fields, list truncation, field projection, and response normalization.&lt;/p&gt;

&lt;p&gt;I gradually realized that allowing an LLM to call tools was not as simple as asking the model to generate an API name and a set of parameters.&lt;/p&gt;

&lt;p&gt;The genuinely difficult part was establishing a stable boundary between the world of natural language and the world of internal APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deliberately Narrowing the Scope
&lt;/h2&gt;

&lt;p&gt;As development continued, I realized that I could not support every sport at the same time.&lt;/p&gt;

&lt;p&gt;Different sports have different competition structures, match states, rules, statistical fields, and APIs. Mixing football, basketball, esports, and other sports together would cause the intent taxonomy, data structures, and testing scope to expand without limit.&lt;/p&gt;

&lt;p&gt;I therefore modified the user-intent detection node so that only football and basketball questions were allowed to enter the internal sports API workflow. Esports and other sports were rejected directly.&lt;/p&gt;

&lt;p&gt;On the surface, this reduced the product’s capabilities.&lt;/p&gt;

&lt;p&gt;In practice, it was the first time the project established a genuine product boundary.&lt;/p&gt;

&lt;p&gt;A broad system whose correctness cannot be guaranteed is less valuable than a limited system that can be explained, tested, and controlled.&lt;/p&gt;

&lt;p&gt;The project later defined its priorities as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accuracy over controllability, controllability over speed, and speed over completeness.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This principle was not designed at the beginning. It was a conclusion reached only after a large number of failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  From “Continuing to Patch” to Redesigning the System
&lt;/h2&gt;

&lt;p&gt;The project truly began to change when I started reading Google Cloud’s materials on AI agents and learning more about agentic design patterns.&lt;/p&gt;

&lt;p&gt;Before that, I already had prompt chains, intent detection, API calls, contextual state, candidate selection, and model routing. But these elements were merely accumulated features. They did not form a system with clearly defined responsibilities.&lt;/p&gt;

&lt;p&gt;After reading those materials, I began to understand agents differently.&lt;/p&gt;

&lt;p&gt;An agent does not mean allowing a model to decide everything freely. Nor does it mean placing all data, tools, and rules into a prompt at once.&lt;/p&gt;

&lt;p&gt;A genuinely controllable agent system must separate uncertain judgment from deterministic execution.&lt;/p&gt;

&lt;p&gt;LLMs are good at understanding natural language, identifying user goals, handling ambiguous expressions, generating plans, and organizing answers.&lt;/p&gt;

&lt;p&gt;Deterministic code is better suited to checking parameters, validating dependencies, restricting the available toolset, binding internal IDs, executing APIs, and blocking invalid plans.&lt;/p&gt;

&lt;p&gt;Based on this understanding, I redesigned the entire project.&lt;/p&gt;

&lt;p&gt;I disabled rule retrieval by default.&lt;/p&gt;

&lt;p&gt;When a user provides an exact score and asks the system to ground the answer, a passage explaining football rules cannot serve as evidence for that score.&lt;/p&gt;

&lt;p&gt;Match facts should come from real-time APIs. The rules knowledge base can explain rules, fields, and enumerations, but it cannot be treated as evidence for live match facts.&lt;/p&gt;

&lt;p&gt;I also gradually abandoned the practice of integrating large numbers of models to keep the workflow running. I switched entirely to Gemini and divided the runtime into two modes: &lt;code&gt;normal&lt;/code&gt; and &lt;code&gt;optimal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both modes use the same business workflow and safety boundaries. They differ only in the capability and cost of the models used at different stages.&lt;/p&gt;

&lt;p&gt;In this design, differences between models no longer change system behavior. They only affect execution quality and cost.&lt;/p&gt;

&lt;p&gt;The final system was not a freely acting autonomous agent. It was a controlled agentic workflow.&lt;/p&gt;

&lt;p&gt;A user question first passes through goal monitoring, intent understanding, and contextual resolution. A planner then generates a structured plan.&lt;/p&gt;

&lt;p&gt;The Tool Graph Validator checks whether the requested tools exist, whether their parameters are available, and whether their dependency chains are complete.&lt;/p&gt;

&lt;p&gt;The Tool Executor calls the internal sports APIs. The Web Evidence Agent handles official sources and general web searches.&lt;/p&gt;

&lt;p&gt;Reflection determines whether the available data is sufficient and, when necessary, permits only one repair attempt.&lt;/p&gt;

&lt;p&gt;Finally, the Answer Composer produces a unified response, while grounding checks restrict the permitted sources of factual claims.&lt;/p&gt;

&lt;p&gt;Within this architecture, the LLM can perform the tasks it is good at, but it does not have the authority to bypass the validator and execute tools arbitrarily.&lt;/p&gt;

&lt;p&gt;Only then did I finally understand that the “intelligence” of an agent should not come from giving the model unlimited freedom.&lt;/p&gt;

&lt;p&gt;It should come from this principle:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Within clearly defined boundaries, leave uncertainty to the model and certainty to the program.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Evidence Changed How I Understood “Internet Access”
&lt;/h2&gt;

&lt;p&gt;Later in the project, I added Web Evidence.&lt;/p&gt;

&lt;p&gt;When I originally built the rules knowledge base and FAISS retrieval, my understanding of external knowledge was still based on traditional RAG: collect content, build an index, perform vector retrieval, and insert the results into the prompt.&lt;/p&gt;

&lt;p&gt;After the redesign, however, I did not turn Web Evidence into another permanent vector database.&lt;/p&gt;

&lt;p&gt;The current workflow only runs &lt;code&gt;official_source&lt;/code&gt; or &lt;code&gt;general_search&lt;/code&gt; when the LLM planner determines that the user’s question genuinely requires external evidence.&lt;/p&gt;

&lt;p&gt;Official sources are primarily used for websites such as FIFA and UEFA. General search is used for news, injuries, transfers, historical context, and questions that cannot be covered by the internal data.&lt;/p&gt;

&lt;p&gt;Web Evidence is also not allowed to generate the final answer directly.&lt;/p&gt;

&lt;p&gt;It can only return a structured Evidence Pack containing sources, evidence, conflicts, failures, and timing information. The unified Answer Composer must still generate the final response by combining that evidence with data from the internal APIs.&lt;/p&gt;

&lt;p&gt;When internal sports APIs conflict with external official sources, the system cannot simply choose one and overwrite the other. The conflict must be explicitly preserved.&lt;/p&gt;

&lt;p&gt;While reverse-engineering the UEFA and FIFA websites, I also gained a stronger appreciation for the advantages of AI when handling large codebases and web resources.&lt;/p&gt;

&lt;p&gt;The API requests used by these official websites involved many complex restrictions, and their frontend JavaScript files were extremely large.&lt;/p&gt;

&lt;p&gt;However, once the relevant JavaScript files were provided to an AI, it could quickly locate the required keys, endpoint URLs, and parameter logic through search.&lt;/p&gt;

&lt;p&gt;Whether a file contained ten thousand lines, one hundred thousand lines, or even more made little fundamental difference to the AI.&lt;/p&gt;

&lt;p&gt;This is one of the most powerful aspects of AI-assisted programming.&lt;/p&gt;

&lt;p&gt;But the same capability also concealed another problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Can Read One Hundred Thousand Lines of Code. Humans Cannot.
&lt;/h2&gt;

&lt;p&gt;Once the project’s functionality was largely complete, I began seriously reading the code that Codex had generated for me.&lt;/p&gt;

&lt;p&gt;The experience was extremely painful.&lt;/p&gt;

&lt;p&gt;Thousands or even tens of thousands of lines of code had been concentrated into individual files.&lt;/p&gt;

&lt;p&gt;For an AI, this was not a problem. It did not need to read the code from the first line to the last in the way a human would. It could search directly for the relevant locations through commands, symbols, and keywords.&lt;/p&gt;

&lt;p&gt;To an AI, the difference between one hundred thousand lines and ten thousand lines might simply be the size of the search space.&lt;/p&gt;

&lt;p&gt;But that was not true for me.&lt;/p&gt;

&lt;p&gt;I needed to build my own index of the entire project inside my head.&lt;/p&gt;

&lt;p&gt;I needed to know where a request entered the system, which components it passed through, where state was modified, who generated a particular parameter, who called a particular method, which path was taken after a failure, and where the final answer was produced.&lt;/p&gt;

&lt;p&gt;Only after building this index could I genuinely understand the project, rather than merely knowing that “it currently runs.”&lt;/p&gt;

&lt;p&gt;Building that mental index was far more difficult than asking an AI to search through files.&lt;/p&gt;

&lt;p&gt;But I deserved that pain.&lt;/p&gt;

&lt;p&gt;All the design, reading, and organizational work that I had skipped through vibe coding eventually returned in another form.&lt;/p&gt;

&lt;p&gt;AI had helped me generate the code earlier, but it had not taken responsibility for understanding the code on my behalf.&lt;/p&gt;

&lt;p&gt;Technical debt does not disappear simply because the code was written by AI.&lt;/p&gt;

&lt;p&gt;On the contrary, because AI can generate code more quickly, technical debt can accumulate more quickly as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Codex and Defensive Code
&lt;/h2&gt;

&lt;p&gt;While reading the code, I noticed another clear characteristic of Codex-generated implementations: they contained an enormous amount of defensive code.&lt;/p&gt;

&lt;p&gt;At almost every stage, Codex would repeatedly verify whether data existed, whether its type was correct, whether fields matched expectations, whether parameters were empty, and whether the current object had a particular attribute.&lt;/p&gt;

&lt;p&gt;When viewed in isolation, these checks usually made sense.&lt;/p&gt;

&lt;p&gt;Codex did not naturally trust that validation had already been performed upstream, nor did it proactively rely on assumptions that had already been established elsewhere in the call context.&lt;/p&gt;

&lt;p&gt;From its perspective, checking again inside the current function was the safest implementation.&lt;/p&gt;

&lt;p&gt;This is a highly reliable code-generation strategy, but not necessarily a good long-term engineering strategy.&lt;/p&gt;

&lt;p&gt;When the same parameter is repeatedly checked in the Router, Planner, Validator, Executor, and Answer stages, the code becomes extremely verbose.&lt;/p&gt;

&lt;p&gt;The actual business logic is surrounded by layers of protective code, making it difficult for developers to distinguish between checks that define genuine system boundaries and checks that are merely redundant.&lt;/p&gt;

&lt;p&gt;During the reading and refactoring process, I therefore began asking Codex to reduce repetitive validation based on the established context.&lt;/p&gt;

&lt;p&gt;Data that has already passed validation through a Pydantic model at a stable boundary does not need every internal function to revalidate every field.&lt;/p&gt;

&lt;p&gt;Similarly, tool dependencies already guaranteed by the Tool Graph Validator should not be reimplemented as incomplete validation logic inside every execution function.&lt;/p&gt;

&lt;p&gt;More defensive code is not always better.&lt;/p&gt;

&lt;p&gt;What matters is knowing where the system needs to defend itself.&lt;/p&gt;

&lt;p&gt;External input, model output, API responses, persisted data, and component boundaries require strict validation.&lt;/p&gt;

&lt;p&gt;Inside those boundaries, the code should be able to trust the contracts that have already been established.&lt;/p&gt;

&lt;p&gt;Otherwise, so-called defensive programming eventually becomes a large volume of noise that no one can confidently determine is safe to remove.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restructuring the Project for Human Readers
&lt;/h2&gt;

&lt;p&gt;After encountering these oversized files, I began directing Codex to split the code apart.&lt;/p&gt;

&lt;p&gt;However, mechanically dividing one large file into several smaller files was not enough. If smaller files caused the call relationships to become more scattered, they could make the system equally difficult to understand.&lt;/p&gt;

&lt;p&gt;I therefore required detailed comments at critical locations: which component called the current method, which file the method called next, where its inputs came from, and which component received its outputs.&lt;/p&gt;

&lt;p&gt;These comments were not written for the AI.&lt;/p&gt;

&lt;p&gt;AI could search symbols and call relationships directly. I needed this information to build an overall mental model quickly.&lt;/p&gt;

&lt;p&gt;As the restructuring continued, the project gradually developed clearer directory boundaries:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/nlu&lt;/code&gt; became responsible for natural-language understanding and contextual resolution.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/agents&lt;/code&gt; became responsible for planning, execution, reflection, and answer generation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/service_runtime&lt;/code&gt; became responsible for Redis, PostgreSQL, and runtime storage.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/api_runtime&lt;/code&gt; became responsible for the API knowledge base and internal API calls.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/web_evidence&lt;/code&gt; became responsible for external evidence.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/services.py&lt;/code&gt; retained only HTTP adaptation and dependency assembly.&lt;/p&gt;

&lt;p&gt;Stable structures also gradually moved away from anonymous dictionaries and toward explicit Pydantic models.&lt;/p&gt;

&lt;p&gt;Model outputs, API call results, session context, long-term memory, and final-answer inputs all began to have their own typed boundaries.&lt;/p&gt;

&lt;p&gt;At that point, I finally understood that modularity is not about making the directory structure look tidy.&lt;/p&gt;

&lt;p&gt;The value of modularity is that it reduces the amount of information a human must load into their mind at the same time in order to understand the system.&lt;/p&gt;

&lt;p&gt;A file should allow a developer to answer the following questions:&lt;/p&gt;

&lt;p&gt;What is it responsible for?&lt;/p&gt;

&lt;p&gt;What is it not responsible for?&lt;/p&gt;

&lt;p&gt;Which inputs does it trust?&lt;/p&gt;

&lt;p&gt;What outputs does it promise to downstream components?&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Ultimately Learned
&lt;/h2&gt;

&lt;p&gt;This project significantly changed how I understand local models, RAG, agents, and AI-assisted programming.&lt;/p&gt;

&lt;p&gt;I no longer believe that local deployment automatically means autonomous and controllable AI.&lt;/p&gt;

&lt;p&gt;Local deployment only gives you control over the runtime environment. Model capability, knowledge quality, and reasoning performance still determine the final user experience.&lt;/p&gt;

&lt;p&gt;I also no longer believe that RAG simply means placing search results into a prompt.&lt;/p&gt;

&lt;p&gt;Effective retrieval must consider information density, context budgets, task stages, and factual boundaries.&lt;/p&gt;

&lt;p&gt;I no longer allow vector search to determine the order of tool calls.&lt;/p&gt;

&lt;p&gt;Relevant knowledge can be retrieved, but strict API dependencies must be controlled through structured plans and deterministic validators.&lt;/p&gt;

&lt;p&gt;I no longer believe that adding more models can solve system-level problems.&lt;/p&gt;

&lt;p&gt;Model fallback can improve availability, but it cannot repair an incorrect division of responsibilities.&lt;/p&gt;

&lt;p&gt;I also no longer treat “the code runs” as meaning that development is complete.&lt;/p&gt;

&lt;p&gt;Code must be understandable, verifiable, testable, and modifiable. Otherwise, it is merely a collection of implementations that happen to work at the current moment.&lt;/p&gt;

&lt;p&gt;Most importantly, I began to understand vibe coding differently.&lt;/p&gt;

&lt;p&gt;Vibe coding is not without value.&lt;/p&gt;

&lt;p&gt;It allowed me to explore a large number of ideas in a short period of time, build prototypes quickly, validate APIs, integrate models, and cover a range of functionality that would have taken a single developer much longer to implement manually.&lt;/p&gt;

&lt;p&gt;The problem is that the speed borrowed through vibe coding must eventually be repaid by the developer.&lt;/p&gt;

&lt;p&gt;You can temporarily avoid reading the code. You can temporarily ignore the fact that the files are becoming larger and larger. You can allow AI to keep patching failed tests.&lt;/p&gt;

&lt;p&gt;But once the project becomes real, every structural problem that was ignored will return.&lt;/p&gt;

&lt;p&gt;AI can help me generate code, search code, reverse-engineer websites, add tests, and perform refactoring.&lt;/p&gt;

&lt;p&gt;But it cannot replace my responsibility to understand the system.&lt;/p&gt;

&lt;p&gt;Architectural boundaries, sources of truth, runtime rules, and final trade-offs must still be determined by me.&lt;/p&gt;

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

&lt;p&gt;In the end, the project did satisfy my requirements.&lt;/p&gt;

&lt;p&gt;However, compared with the final achievement of building a sports scores agent, the more valuable outcome was experiencing firsthand how a project could evolve from a local-model experiment, a simple chatbot, FAISS retrieval, an API knowledge base, a keyword-based decision flow, and multi-model fallback into a controlled, testable, and explainable agentic workflow.&lt;/p&gt;

&lt;p&gt;Many of the early implementations do not look elegant in retrospect. Some of the designs were wrong from the very beginning.&lt;/p&gt;

&lt;p&gt;But I do not believe the process was entirely wasted.&lt;/p&gt;

&lt;p&gt;If I had not personally encountered the problem of rules data consuming the entire context window, I would never have genuinely understood context engineering.&lt;/p&gt;

&lt;p&gt;If I had not experienced vector search selecting the wrong API, I would not have understood the difference between knowledge retrieval and tool planning.&lt;/p&gt;

&lt;p&gt;If I had not maintained a keyword-based decision flow, I would not have understood why natural-language intent cannot be exhaustively represented through rules.&lt;/p&gt;

&lt;p&gt;If I had not faced files containing tens of thousands of lines generated by Codex, I would not have truly appreciated human readability and module boundaries.&lt;/p&gt;

&lt;p&gt;This project was not completed in a single pass according to one correct architecture diagram.&lt;/p&gt;

&lt;p&gt;It only gradually became what it is today after I repeatedly discovered that “this approach does not work.”&lt;/p&gt;

&lt;p&gt;And that may be the most honest summary of my entire development experience:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI can dramatically accelerate trial and error, but it cannot allow developers to skip growth. Every piece of understanding temporarily sacrificed for speed must eventually be recovered through their own effort.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>agentaichallenge</category>
      <category>vibecoding</category>
    </item>
    <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://dev.to/truman_999999999/deep-dive-using-redis-hash-tags-to-reduce-tail-latency-in-redis-cluster-batch-reads-552b</link>
      <guid>https://dev.to/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://dev.to/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://dev.to/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://dev.to/truman_999999999/gracefully-integrating-sentry-go-middleware-into-fiber-v3-projects-565f</link>
      <guid>https://dev.to/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://dev.to/truman_999999999/zai-fiber-v3-xiang-mu-zhong-you-ya-di-ji-cheng-sentry-go-zhong-jian-jian-3g33</link>
      <guid>https://dev.to/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://dev.to/truman_999999999/websocket-qing-qiu-tong-guo-cdn-fan-hui-403-forbidden-de-wen-ti-pai-cha-yu-jie-jue-59hb</link>
      <guid>https://dev.to/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://dev.to/truman_999999999/websocket-qing-qiu-tong-guo-cdn-fan-hui-403-forbidden-de-wen-ti-pai-cha-yu-jie-jue-lo4</link>
      <guid>https://dev.to/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://dev.to/truman_999999999/java-zhong-redis-cun-chu-fan-xing-dui-xiang-map-de-fan-xu-lie-hua-xian-jing-5972</link>
      <guid>https://dev.to/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://dev.to/truman_999999999/native-memory-tracking-can-shu-jie-shi-dlh</link>
      <guid>https://dev.to/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://dev.to/truman_999999999/jvmnei-cun-shi-yong-chao-chu-dui-nei-cun-xian-zhi-de-wen-ti-pai-cha-26ph</link>
      <guid>https://dev.to/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://dev.to/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://dev.to/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://dev.to/truman_999999999/wen-ti-ji-lu-3fbo</link>
      <guid>https://dev.to/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>
  </channel>
</rss>
